[
  {
    "path": ".gitignore",
    "content": "# 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\nbin/\ngen/\n\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n#Eclipse\n.project\n.classpath\n.settings\n\n#OSX\n.DS_Store\n\n#Android Studio\nbuild/\n\n# Intellij project files\n*.iml\n*.ipr\n*.iws\n.idea/\n\n#gradle\n.gradle/\n"
  },
  {
    "path": "README.md",
    "content": "IPicker\n======================================\nA material design style pictures selector.\n\n## Screenshot\n<img src=\"arts/1.png\" width=\"30%\"> <img src=\"arts/2.png\" width=\"30%\"> <img src=\"arts/clipping.png\" width=\"30%\">\n\n## Usage\n### Gradle\n\n```\ndependencies {\n   \timplementation 'com.liuguangqiang.ipicker:IPicker:1.1.0'\n}\n```\n\n### Maven\n```\n<dependency>\n  <groupId>com.liuguangqiang.ipicker</groupId>\n  <artifactId>IPicker</artifactId>\n  <version>1.1.0</version>\n  <type>pom</type>\n</dependency>\n```\n\n### Manifest\n\n```\n<activity\n     android:name=\"com.liuguangqiang.ipicker.IPickerActivity\"\n     android:screenOrientation=\"portrait\"\n     android:theme=\"@style/IPickerTheme\" />\n```\n\n### Theme\n```\n<style name=\"IPickerTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n     <item name=\"colorPrimary\">@color/color_primary</item>\n     <item name=\"colorPrimaryDark\">@color/color_primary_dark</item>\n     <item name=\"colorAccent\">@color/color_primary</item>\n </style>\n```\n\n### Open the picker\n```java\nIPicker.setLimit(1);\nIPicker.open(context);\n```\n\nReturn the selected images by EventBus.\n\n```\n@Subscribe\npublic void onEvent(IPickerEvent event) {\n}\n```\n\nAlso support to get the selected images by a listener.\n\n```\nIPicker.setOnSelectedListener(new IPicker.OnSelectedListener() {\n\n      @Override\n      public void onSelected(List<String> paths) {}\n\n});\n```\n\n## License\n\n    Copyright 2016 Eric Liu\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License."
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 28\n    buildToolsVersion \"28.0.0\"\n    defaultConfig {\n        applicationId \"com.liuguangqiang.ipicker.sample\"\n        minSdkVersion 15\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    implementation 'com.android.support:design:28.0.0'\n    implementation 'com.github.liuguangqiang.permissionhelper:permissionhelper:0.1.0'\n    implementation 'com.github.bumptech.glide:glide:4.11.0'\n\n    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n    testImplementation 'junit:junit:4.12'\n    implementation project(':library')\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/Eric/Dev/android-sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "app/src/androidTest/java/com/liuguangqiang/ipicker/sample/ExampleInstrumentedTest.java",
    "content": "package com.liuguangqiang.ipicker.sample;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumentation test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() throws Exception {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getTargetContext();\n\n        assertEquals(\"com.liuguangqiang.ipicker.sample\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.liuguangqiang.ipicker.sample\">\n\n    <uses-permission\n        android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"\n        tools:ignore=\"ProtectedPermissions\" />\n\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/liuguangqiang/ipicker/sample/MainActivity.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.sample;\n\nimport android.os.Bundle;\nimport android.view.View;\nimport android.widget.Button;\n\nimport com.liuguangqiang.ipicker.IPicker;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * A Sample\n */\npublic class MainActivity extends AppCompatActivity {\n\n    private RecyclerView recyclerView;\n    private SelectedAdapter adapter;\n    private ArrayList<String> selectPictures = new ArrayList<>();\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        initViews();\n    }\n\n    private void initViews() {\n        IPicker.setLimit(1);\n        IPicker.setOnSelectedListener(new IPicker.OnSelectedListener() {\n            @Override\n            public void onSelected(List<String> paths) {\n                selectPictures.clear();\n                selectPictures.addAll(paths);\n                adapter.notifyDataSetChanged();\n            }\n        });\n        Button button = findViewById(R.id.open_picker);\n        button.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                IPicker.open(getApplicationContext());\n            }\n        });\n        adapter = new SelectedAdapter(getApplicationContext(), selectPictures);\n        recyclerView = findViewById(R.id.rv_photos);\n        recyclerView.setLayoutManager(new GridLayoutManager(getApplicationContext(), 4, GridLayoutManager.VERTICAL, false));\n        recyclerView.setAdapter(adapter);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/liuguangqiang/ipicker/sample/SelectedAdapter.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.sample;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport com.bumptech.glide.Glide;\nimport com.liuguangqiang.ipicker.adapters.BaseAdapter;\n\nimport java.util.List;\n\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * Created by Eric on 16/9/12.\n */\npublic class SelectedAdapter extends BaseAdapter<String, SelectedAdapter.ViewHolder> {\n\n    public SelectedAdapter(Context context, List<String> data) {\n        super(context, data);\n    }\n\n    @Override\n    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        return new ViewHolder(layoutInflater, parent, false);\n    }\n\n    @Override\n    public void onBindViewHolder(ViewHolder holder, int position) {\n        super.onBindViewHolder(holder, position);\n        holder.bindData(data.get(position));\n    }\n\n    public static class ViewHolder extends RecyclerView.ViewHolder {\n\n        private ImageView ivPhoto;\n\n        public ViewHolder(LayoutInflater layoutInflater, ViewGroup parent, boolean attachToRoot) {\n            super(layoutInflater.inflate(R.layout.item_selected, parent, attachToRoot));\n            ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo);\n        }\n\n        public void bindData(String path) {\n            Glide.with(itemView.getContext()).load(path).into(ivPhoto);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/activity_main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"com.liuguangqiang.ipicker.sample.MainActivity\">\n\n    <Button\n        android:id=\"@+id/open_picker\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"选择照片\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_photos\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_weight=\"1\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_selected.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.liuguangqiang.ipicker.widgets.SquareRelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:id=\"@+id/iv_photo\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerCrop\" />\n\n</com.liuguangqiang.ipicker.widgets.SquareRelativeLayout>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">IPicker</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorPrimary</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-w820dp/dimens.xml",
    "content": "<resources>\n    <!-- Example customization of dimensions originally defined in res/values/dimens.xml\n         (such as screen margins) for screens with more than 820dp of available width. This\n         would include 7\" and 10\" devices in landscape (~960dp and ~1280dp respectively). -->\n    <dimen name=\"activity_horizontal_margin\">64dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/src/test/java/com/liuguangqiang/ipicker/sample/ExampleUnitTest.java",
    "content": "package com.liuguangqiang.ipicker.sample;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        jcenter()\n\n        google()\n    }\n\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.6.2'\n        classpath 'com.novoda:bintray-release:0.9.2'\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n        google()\n    }\n    tasks.withType(Javadoc) {\n        options.addStringOption('Xdoclint:none', '-quiet')\n        options.addStringOption('encoding', 'UTF-8')\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "gradle/gradle-mvn-push.gradle",
    "content": "apply plugin: 'maven'\napply plugin: 'signing'\n\ndef isReleaseBuild() {\n    return VERSION_NAME.contains(\"SNAPSHOT\") == false\n}\n\ndef getReleaseRepositoryUrl() {\n    return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL\n            : \"https://oss.sonatype.org/service/local/staging/deploy/maven2/\"\n}\n\ndef getSnapshotRepositoryUrl() {\n    return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL\n            : \"https://oss.sonatype.org/content/repositories/snapshots/\"\n}\n\ndef getRepositoryUsername() {\n    return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : \"\"\n}\n\ndef getRepositoryPassword() {\n    return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : \"\"\n}\n\nafterEvaluate { project ->\n    uploadArchives {\n        repositories {\n            mavenDeployer {\n                beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }\n\n                pom.groupId = GROUP\n                pom.artifactId = POM_ARTIFACT_ID\n                pom.version = VERSION_NAME\n\n                repository(url: getReleaseRepositoryUrl()) {\n                    authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())\n                }\n                snapshotRepository(url: getSnapshotRepositoryUrl()) {\n                    authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())\n                }\n\n                pom.project {\n                    name POM_NAME\n                    packaging POM_PACKAGING\n                    description POM_DESCRIPTION\n                    url POM_URL\n\n                    scm {\n                        url POM_SCM_URL\n                        connection POM_SCM_CONNECTION\n                        developerConnection POM_SCM_DEV_CONNECTION\n                    }\n\n                    licenses {\n                        license {\n                            name POM_LICENCE_NAME\n                            url POM_LICENCE_URL\n                            distribution POM_LICENCE_DIST\n                        }\n                    }\n\n                    developers {\n                        developer {\n                            id POM_DEVELOPER_ID\n                            name POM_DEVELOPER_NAME\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    signing {\n        required { isReleaseBuild() && gradle.taskGraph.hasTask(\"uploadArchives\") }\n        sign configurations.archives\n    }\n\n    task androidJavadocs(type: Javadoc) {\n        source = android.sourceSets.main.java.srcDirs\n        classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n    }\n\n    task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {\n        classifier = 'javadoc'\n        from androidJavadocs.destinationDir\n    }\n\n    task androidSourcesJar(type: Jar) {\n        classifier = 'sources'\n        from android.sourceSets.main.java.sourceFiles\n    }\n\n    artifacts {\n        archives androidSourcesJar\n//        archives androidJavadocsJar\n    }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Wed Apr 08 10:04:11 CST 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.4-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\nandroid.useAndroidX=true\nandroid.enableJetifier=true\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n\nVERSION_NAME=1.0.1\nVERSION_CODE=101\nGROUP=com.liuguangqiang.ipicker\n\nPOM_NAME=IPicker\nPOM_ARTIFACT_ID=library\nPOM_PACKAGING=aar\n\nPOM_DESCRIPTION=A material design style pictures selector.\n\nPOM_URL=https://github.com/liuguangqiang/IPicker\nPOM_SCM_URL=https://github.com/liuguangqiang/IPicker\nPOM_SCM_CONNECTION=https://github.com/liuguangqiang/IPicker.git\nPOM_SCM_DEV_CONNECTION=https://github.com/liuguangqiang/IPicker.git\n\nPOM_LICENCE_NAME=The Apache Software License, Version 2.0\nPOM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt\nPOM_LICENCE_DIST=repo\n\nPOM_DEVELOPER_ID=EricLiu\nPOM_DEVELOPER_NAME=EricLiu"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "library/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "library/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'com.novoda.bintray-release'\napply from: rootProject.file('gradle/gradle-mvn-push.gradle')\n\nandroid {\n    compileSdkVersion 28\n    buildToolsVersion \"28.0.0\"\n\n    lintOptions {\n        abortOnError false\n    }\n\n    defaultConfig {\n        minSdkVersion 15\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\npublish {\n    userOrg = 'liuguangqiang'\n    groupId = 'com.liuguangqiang.ipicker'\n    artifactId = 'IPicker'\n    publishVersion = '1.1.0'\n    desc = 'A material design style pictures selector.'\n    website = 'https://github.com/liuguangqiang/IPicker'\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n\n    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {\n        exclude group: 'com.android.support', module: 'support-annotations'\n    })\n    implementation 'com.android.support:design:28.0.0'\n    implementation 'com.github.liuguangqiang.permissionhelper:permissionhelper:0.1.0'\n    implementation 'com.github.bumptech.glide:glide:4.11.0'\n    testImplementation 'junit:junit:4.12'\n}\n"
  },
  {
    "path": "library/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/Eric/Dev/android-sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "library/src/androidTest/java/com/liuguangqiang/ipicker/ExampleInstrumentedTest.java",
    "content": "package com.liuguangqiang.ipicker;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.runner.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumentation test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() throws Exception {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getTargetContext();\n\n        assertEquals(\"com.liuguangqiang.ipicker.test\", appContext.getPackageName());\n    }\n}\n"
  },
  {
    "path": "library/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.liuguangqiang.ipicker\">\n\n    <uses-permission\n        android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"\n        tools:ignore=\"ProtectedPermissions\" />\n\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:supportsRtl=\"true\">\n\n        <activity\n            android:name=\".IPickerActivity\"\n            android:screenOrientation=\"portrait\" />\n\n        <activity\n            android:name=\".CropImageActivity\"\n            android:screenOrientation=\"portrait\" />\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/CropImageActivity.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker;\n\nimport android.annotation.TargetApi;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.BitmapRegionDecoder;\nimport android.graphics.Matrix;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.net.Uri;\nimport android.opengl.GLES10;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.provider.MediaStore;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.Window;\nimport android.view.WindowManager;\n\nimport com.liuguangqiang.ipicker.crop.Crop;\nimport com.liuguangqiang.ipicker.crop.CropImageView;\nimport com.liuguangqiang.ipicker.crop.CropUtil;\nimport com.liuguangqiang.ipicker.crop.HighlightView;\nimport com.liuguangqiang.ipicker.crop.ImageViewTouchBase;\nimport com.liuguangqiang.ipicker.crop.Log;\nimport com.liuguangqiang.ipicker.crop.MonitoredActivity;\nimport com.liuguangqiang.ipicker.crop.RotateBitmap;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.concurrent.CountDownLatch;\n\n/*\n * Modified from original in AOSP.\n */\npublic class CropImageActivity extends MonitoredActivity {\n\n    private static final int ACTION_DONE = 0;\n\n    private static final int SIZE_DEFAULT = 2048;\n    private static final int SIZE_LIMIT = 4096;\n\n    private final Handler handler = new Handler();\n\n    private int aspectX;\n    private int aspectY;\n\n    // Output image\n    private int maxX;\n    private int maxY;\n    private int exifRotation;\n    private boolean saveAsPng;\n\n    private Uri sourceUri;\n    private Uri saveUri;\n\n    private boolean isSaving;\n\n    private int sampleSize;\n    private RotateBitmap rotateBitmap;\n    private CropImageView imageView;\n    private HighlightView cropView;\n\n    @Override\n    public void onCreate(Bundle icicle) {\n        super.onCreate(icicle);\n        setupWindowFlags();\n        setupViews();\n\n        loadInput();\n        if (rotateBitmap == null) {\n            finish();\n            return;\n        }\n        startCrop();\n\n        initToolbar();\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        menu.add(0, ACTION_DONE, 0, R.string.action_done).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);\n        return super.onCreateOptionsMenu(menu);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                finish();\n                return true;\n            case ACTION_DONE:\n                onSaveClicked();\n                return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n\n    private void initToolbar() {\n        setTitle(R.string.crop__title);\n        getSupportActionBar().setHomeAsUpIndicator(R.mipmap.ic_close_white);\n        getSupportActionBar().setDisplayShowHomeEnabled(true);\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n        getSupportActionBar().setHomeButtonEnabled(true);\n    }\n\n    @TargetApi(Build.VERSION_CODES.KITKAT)\n    private void setupWindowFlags() {\n        requestWindowFeature(Window.FEATURE_NO_TITLE);\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);\n        }\n    }\n\n    private void setupViews() {\n        setContentView(R.layout.activity_ipicker_crop);\n\n        imageView = (CropImageView) findViewById(R.id.ipicker_crop_image);\n        imageView.context = this;\n        imageView.setRecycler(new ImageViewTouchBase.Recycler() {\n            @Override\n            public void recycle(Bitmap b) {\n                b.recycle();\n                System.gc();\n            }\n        });\n    }\n\n    private void loadInput() {\n        Intent intent = getIntent();\n        Bundle extras = intent.getExtras();\n\n        if (extras != null) {\n            aspectX = extras.getInt(Crop.Extra.ASPECT_X);\n            aspectY = extras.getInt(Crop.Extra.ASPECT_Y);\n            maxX = extras.getInt(Crop.Extra.MAX_X);\n            maxY = extras.getInt(Crop.Extra.MAX_Y);\n            saveAsPng = extras.getBoolean(Crop.Extra.AS_PNG, false);\n            saveUri = extras.getParcelable(MediaStore.EXTRA_OUTPUT);\n        }\n\n        sourceUri = intent.getData();\n        if (sourceUri != null) {\n            exifRotation = CropUtil.getExifRotation(CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri));\n\n            InputStream is = null;\n            try {\n                sampleSize = calculateBitmapSampleSize(sourceUri);\n                is = getContentResolver().openInputStream(sourceUri);\n                BitmapFactory.Options option = new BitmapFactory.Options();\n                option.inSampleSize = sampleSize;\n                rotateBitmap = new RotateBitmap(BitmapFactory.decodeStream(is, null, option), exifRotation);\n            } catch (IOException e) {\n                Log.e(\"Error reading image: \" + e.getMessage(), e);\n                setResultException(e);\n            } catch (OutOfMemoryError e) {\n                Log.e(\"OOM reading image: \" + e.getMessage(), e);\n                setResultException(e);\n            } finally {\n                CropUtil.closeSilently(is);\n            }\n        }\n    }\n\n    private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException {\n        InputStream is = null;\n        BitmapFactory.Options options = new BitmapFactory.Options();\n        options.inJustDecodeBounds = true;\n        try {\n            is = getContentResolver().openInputStream(bitmapUri);\n            BitmapFactory.decodeStream(is, null, options); // Just get image size\n        } finally {\n            CropUtil.closeSilently(is);\n        }\n\n        int maxSize = getMaxImageSize();\n        int sampleSize = 1;\n        while (options.outHeight / sampleSize > maxSize || options.outWidth / sampleSize > maxSize) {\n            sampleSize = sampleSize << 1;\n        }\n        return sampleSize;\n    }\n\n    private int getMaxImageSize() {\n        int textureLimit = getMaxTextureSize();\n        if (textureLimit == 0) {\n            return SIZE_DEFAULT;\n        } else {\n            return Math.min(textureLimit, SIZE_LIMIT);\n        }\n    }\n\n    private int getMaxTextureSize() {\n        // The OpenGL texture size is the maximum size that can be drawn in an ImageView\n        int[] maxSize = new int[1];\n        GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);\n        return maxSize[0];\n    }\n\n    private void startCrop() {\n        if (isFinishing()) {\n            return;\n        }\n        imageView.setImageRotateBitmapResetBase(rotateBitmap, true);\n        CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__wait),\n                new Runnable() {\n                    public void run() {\n                        final CountDownLatch latch = new CountDownLatch(1);\n                        handler.post(new Runnable() {\n                            public void run() {\n                                if (imageView.getScale() == 1F) {\n                                    imageView.center();\n                                }\n                                latch.countDown();\n                            }\n                        });\n                        try {\n                            latch.await();\n                        } catch (InterruptedException e) {\n                            throw new RuntimeException(e);\n                        }\n                        new Cropper().crop();\n                    }\n                }, handler\n        );\n    }\n\n    private class Cropper {\n\n        private void makeDefault() {\n            if (rotateBitmap == null) {\n                return;\n            }\n\n            HighlightView hv = new HighlightView(imageView);\n            final int width = rotateBitmap.getWidth();\n            final int height = rotateBitmap.getHeight();\n\n            Rect imageRect = new Rect(0, 0, width, height);\n\n            // Make the default size about 4/5 of the width or height\n            int cropWidth = Math.min(width, height) * 4 / 5;\n            @SuppressWarnings(\"SuspiciousNameCombination\")\n            int cropHeight = cropWidth;\n\n            if (aspectX != 0 && aspectY != 0) {\n                if (aspectX > aspectY) {\n                    cropHeight = cropWidth * aspectY / aspectX;\n                } else {\n                    cropWidth = cropHeight * aspectX / aspectY;\n                }\n            }\n\n            int x = (width - cropWidth) / 2;\n            int y = (height - cropHeight) / 2;\n\n            RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);\n            hv.setup(imageView.getUnrotatedMatrix(), imageRect, cropRect, aspectX != 0 && aspectY != 0);\n            imageView.add(hv);\n        }\n\n        public void crop() {\n            handler.post(new Runnable() {\n                public void run() {\n                    makeDefault();\n                    imageView.invalidate();\n                    if (imageView.highlightViews.size() == 1) {\n                        cropView = imageView.highlightViews.get(0);\n                        cropView.setFocus(true);\n                    }\n                }\n            });\n        }\n    }\n\n    private void onSaveClicked() {\n        if (cropView == null || isSaving) {\n            return;\n        }\n        isSaving = true;\n\n        Bitmap croppedImage;\n        Rect r = cropView.getScaledCropRect(sampleSize);\n        int width = r.width();\n        int height = r.height();\n\n        int outWidth = width;\n        int outHeight = height;\n        if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {\n            float ratio = (float) width / (float) height;\n            if ((float) maxX / (float) maxY > ratio) {\n                outHeight = maxY;\n                outWidth = (int) ((float) maxY * ratio + .5f);\n            } else {\n                outWidth = maxX;\n                outHeight = (int) ((float) maxX / ratio + .5f);\n            }\n        }\n\n        try {\n            croppedImage = decodeRegionCrop(r, outWidth, outHeight);\n        } catch (IllegalArgumentException e) {\n            setResultException(e);\n            finish();\n            return;\n        }\n\n        if (croppedImage != null) {\n            imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);\n            imageView.center();\n            imageView.highlightViews.clear();\n        }\n        saveImage(croppedImage);\n    }\n\n    private void saveImage(Bitmap croppedImage) {\n        if (croppedImage != null) {\n            final Bitmap b = croppedImage;\n            CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving),\n                    new Runnable() {\n                        public void run() {\n                            saveOutput(b);\n                        }\n                    }, handler\n            );\n        } else {\n            finish();\n        }\n    }\n\n    private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {\n        // Release memory now\n        clearImageView();\n\n        InputStream is = null;\n        Bitmap croppedImage = null;\n        try {\n            is = getContentResolver().openInputStream(sourceUri);\n            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);\n            final int width = decoder.getWidth();\n            final int height = decoder.getHeight();\n\n            if (exifRotation != 0) {\n                // Adjust crop area to account for image rotation\n                Matrix matrix = new Matrix();\n                matrix.setRotate(-exifRotation);\n\n                RectF adjusted = new RectF();\n                matrix.mapRect(adjusted, new RectF(rect));\n\n                // Adjust to account for origin at 0,0\n                adjusted.offset(adjusted.left < 0 ? width : 0, adjusted.top < 0 ? height : 0);\n                rect = new Rect((int) adjusted.left, (int) adjusted.top, (int) adjusted.right, (int) adjusted.bottom);\n            }\n\n            try {\n                croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());\n                if (croppedImage != null && (rect.width() > outWidth || rect.height() > outHeight)) {\n                    Matrix matrix = new Matrix();\n                    matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());\n                    croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);\n                }\n            } catch (IllegalArgumentException e) {\n                // Rethrow with some extra information\n                throw new IllegalArgumentException(\"Rectangle \" + rect + \" is outside of the image (\"\n                        + width + \",\" + height + \",\" + exifRotation + \")\", e);\n            }\n\n        } catch (IOException e) {\n            Log.e(\"Error cropping image: \" + e.getMessage(), e);\n            setResultException(e);\n        } catch (OutOfMemoryError e) {\n            Log.e(\"OOM cropping image: \" + e.getMessage(), e);\n            setResultException(e);\n        } finally {\n            CropUtil.closeSilently(is);\n        }\n        return croppedImage;\n    }\n\n    private void clearImageView() {\n        imageView.clear();\n        if (rotateBitmap != null) {\n            rotateBitmap.recycle();\n        }\n        System.gc();\n    }\n\n    private void saveOutput(Bitmap croppedImage) {\n        if (saveUri != null) {\n            OutputStream outputStream = null;\n            try {\n                outputStream = getContentResolver().openOutputStream(saveUri);\n                if (outputStream != null) {\n                    croppedImage.compress(saveAsPng ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG,\n                            90,     // note: quality is ignored when using PNG\n                            outputStream);\n                }\n            } catch (IOException e) {\n                setResultException(e);\n                Log.e(\"Cannot open file: \" + saveUri, e);\n            } finally {\n                CropUtil.closeSilently(outputStream);\n            }\n\n            CropUtil.copyExifRotation(\n                    CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),\n                    CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)\n            );\n\n            setResultUri(saveUri);\n        }\n\n        final Bitmap b = croppedImage;\n        handler.post(new Runnable() {\n            public void run() {\n                imageView.clear();\n                b.recycle();\n            }\n        });\n\n        finish();\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        if (rotateBitmap != null) {\n            rotateBitmap.recycle();\n        }\n    }\n\n    @Override\n    public boolean onSearchRequested() {\n        return false;\n    }\n\n    public boolean isSaving() {\n        return isSaving;\n    }\n\n    private void setResultUri(Uri uri) {\n        setResult(RESULT_OK, new Intent().putExtra(MediaStore.EXTRA_OUTPUT, uri));\n    }\n\n    private void setResultException(Throwable throwable) {\n        setResult(Crop.RESULT_ERROR, new Intent().putExtra(Crop.Extra.ERROR, throwable));\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/IPicker.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Created by Eric on 16/9/18.\n */\npublic class IPicker {\n\n    /**\n     * Limit the count of picture selected.\n     */\n    private static int limit = 1;\n\n    private static boolean cropEnable = false;\n\n    private static OnSelectedListener onSelectedListener;\n\n    private IPicker() {\n    }\n\n    /**\n     * Limit the count of picture selected.\n     *\n     * @param limit\n     */\n    public static void setLimit(int limit) {\n        IPicker.limit = limit;\n    }\n\n    public static void setCropEnable(boolean cropEnable) {\n        IPicker.cropEnable = cropEnable;\n    }\n\n    public static void setOnSelectedListener(OnSelectedListener listener) {\n        IPicker.onSelectedListener = listener;\n    }\n\n    /**\n     * Finish the IPicker and post a event to observer.\n     *\n     * @param path\n     */\n    public static void finish(String path) {\n        List<String> paths = new ArrayList<>();\n        paths.add(path);\n        finish(paths);\n    }\n\n    /**\n     * Finish the IPicker and post a event to observer.\n     *\n     * @param paths\n     */\n    public static void finish(List<String> paths) {\n        if (onSelectedListener != null) {\n            onSelectedListener.onSelected(paths);\n        }\n    }\n\n    /**\n     * Open the IPicker to select photos or take pictures.\n     *\n     * @param context\n     */\n    public static void open(Context context) {\n        open(context, null);\n    }\n\n    /**\n     * Open the IPicker to select photos or take pictures.\n     *\n     * @param context\n     * @param selected\n     */\n    public static void open(Context context, ArrayList<String> selected) {\n        Intent intent = new Intent(context, IPickerActivity.class);\n        if (!(context instanceof Activity)) {\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n\n        Bundle bundle = new Bundle();\n        bundle.putInt(IPickerActivity.ARG_LIMIT, limit);\n        bundle.putBoolean(IPickerActivity.ARG_CROP_ENABLE, cropEnable);\n        if (selected != null && !selected.isEmpty()) {\n            bundle.putStringArrayList(IPickerActivity.ARG_SELECTED, selected);\n\n        }\n        intent.putExtras(bundle);\n        context.startActivity(intent);\n    }\n\n    public interface OnSelectedListener {\n\n        void onSelected(List<String> paths);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/IPickerActivity.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.ContentValues;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.provider.MediaStore;\nimport android.provider.Settings;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.RelativeLayout;\n\nimport com.google.android.material.snackbar.Snackbar;\nimport com.liuguangqiang.ipicker.adapters.BaseAdapter;\nimport com.liuguangqiang.ipicker.adapters.PhotosAdapter;\nimport com.liuguangqiang.ipicker.crop.Crop;\nimport com.liuguangqiang.ipicker.entities.Photo;\nimport com.liuguangqiang.ipicker.internal.ImageMedia;\nimport com.liuguangqiang.permissionhelper.PermissionHelper;\nimport com.liuguangqiang.permissionhelper.annotations.PermissionDenied;\nimport com.liuguangqiang.permissionhelper.annotations.PermissionGranted;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.StringRes;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.recyclerview.widget.GridLayoutManager;\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * Created by Eric on 16/9/12.\n */\npublic class IPickerActivity extends AppCompatActivity implements BaseAdapter.OnItemClickListener {\n\n    public static final String ARG_SELECTED = \"ARG_SELECTED\";\n    public static final String ARG_LIMIT = \"ARG_LIMIT\";\n    public static final String ARG_CROP_ENABLE = \"ARG_CROP_ENABLE\";\n\n    private static final int REQUEST_CAMERA = 1;\n    private static final int ACTION_DONE = 0;\n\n    private RelativeLayout layoutContainer;\n    private RecyclerView recyclerView;\n    private PhotosAdapter adapter;\n    private List<Photo> photoList = new ArrayList<>();\n    private int limit = 1;\n    private boolean cropEnable = false;\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_ipicker);\n        initToolbar();\n        initViews();\n        getArguments();\n        requestPhotos();\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        switch (item.getItemId()) {\n            case android.R.id.home:\n                finish();\n                return true;\n            case ACTION_DONE:\n                IPicker.finish(adapter.getSelected());\n                finish();\n                return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        menu.add(0, ACTION_DONE, 0, R.string.action_done).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);\n        return super.onCreateOptionsMenu(menu);\n    }\n\n    @Override\n    public boolean onPrepareOptionsMenu(Menu menu) {\n        menu.findItem(ACTION_DONE).setVisible(limit > 1 ? true : false);\n        return super.onPrepareOptionsMenu(menu);\n    }\n\n    private void initToolbar() {\n        setTitle(R.string.title_act_picker);\n        getSupportActionBar().setHomeAsUpIndicator(R.mipmap.ic_close_white);\n        getSupportActionBar().setDisplayShowHomeEnabled(true);\n        getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n        getSupportActionBar().setHomeButtonEnabled(true);\n    }\n\n    private void initViews() {\n        getWindow().setBackgroundDrawable(null);\n        recyclerView = findViewById(R.id.rv_photos);\n        recyclerView.setLayoutManager(new GridLayoutManager(getApplicationContext(), 4, GridLayoutManager.VERTICAL, false));\n        adapter = new PhotosAdapter(this, photoList);\n        adapter.setOnItemClickListener(this);\n        recyclerView.setAdapter(adapter);\n        layoutContainer = findViewById(R.id.layout_container);\n    }\n\n    private void getArguments() {\n        Bundle bundle = getIntent().getExtras();\n        if (bundle != null) {\n            if (bundle.containsKey(ARG_LIMIT)) {\n                limit = bundle.getInt(ARG_LIMIT);\n                if (limit == 0) limit = 1;\n\n                adapter.setSingleSelection(isSingleSelection());\n            }\n\n            if (bundle.containsKey(ARG_SELECTED)) {\n                adapter.addSelected(bundle.getStringArrayList(ARG_SELECTED));\n                updateTitle();\n            }\n\n            if (bundle.containsKey(ARG_CROP_ENABLE)) {\n                cropEnable = bundle.getBoolean(ARG_CROP_ENABLE, false);\n            }\n        }\n    }\n\n    private boolean isSingleSelection() {\n        return limit == 1;\n    }\n\n    @Override\n    public void onItemClick(View view, int position) {\n        if (position == 0) {\n            requestCamera();\n        } else if (isSingleSelection()) {\n            if (cropEnable) {\n                cropImage(Uri.parse(\"file://\" + photoList.get(position).path));\n            } else {\n                IPicker.finish(photoList.get(position).path);\n                finish();\n            }\n        } else if (adapter.isSelected(photoList.get(position).path)) {\n            removeSelected(position);\n        } else {\n            addSelected(position);\n        }\n    }\n\n    private void removeSelected(int position) {\n        adapter.removeSelected(photoList.get(position));\n        adapter.notifyItemChanged(position);\n        updateTitle();\n    }\n\n    private void addSelected(int position) {\n        if ((adapter.getSelectedTotal() < limit)) {\n            adapter.addSelected(photoList.get(position));\n            adapter.notifyItemChanged(position);\n            updateTitle();\n        } else {\n            Snackbar.make(layoutContainer, getString(R.string.format_max_size, limit), Snackbar.LENGTH_LONG).show();\n        }\n    }\n\n    private void updateTitle() {\n        if (limit > 1) {\n            if (adapter.getSelectedTotal() == 0) {\n                setTitle(R.string.title_act_picker);\n            } else {\n                setTitle(\"\" + adapter.getSelectedTotal());\n            }\n        }\n    }\n\n    private void requestPhotos() {\n        PermissionHelper.getInstance().requestPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);\n    }\n\n    private void requestCamera() {\n        PermissionHelper.getInstance().requestPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        PermissionHelper.getInstance().onRequestPermissionsResult(this, permissions, grantResults);\n    }\n\n    @PermissionGranted(permission = Manifest.permission.READ_EXTERNAL_STORAGE)\n    public void galleryGranted() {\n        getPhotos();\n    }\n\n    @PermissionDenied(permission = Manifest.permission.READ_EXTERNAL_STORAGE)\n    public void galleryDenied() {\n        promptNoPermission(R.string.no_permission_gallery);\n    }\n\n    @PermissionGranted(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)\n    public void cameraGranted() {\n        takePicture();\n    }\n\n    @PermissionDenied(permission = Manifest.permission.WRITE_EXTERNAL_STORAGE)\n    public void cameraDenied() {\n        promptNoPermission(R.string.no_permission_camera);\n    }\n\n    private void getPhotos() {\n        photoList.add(new Photo(true));\n        photoList.addAll(ImageMedia.queryAll(this));\n        adapter.notifyDataSetChanged();\n    }\n\n    private Uri tempUri;\n\n    private void deleteTemp() {\n        if (tempUri != null) {\n            getContentResolver().delete(tempUri, null, null);\n        }\n    }\n\n    private void takePicture() {\n        ContentValues values = new ContentValues(1);\n        values.put(MediaStore.Images.Media.MIME_TYPE, \"image/jpg\");\n        values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());\n        tempUri = getContentResolver()\n                .insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);\n\n        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION\n                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);\n        if (tempUri != null) {\n            intent.putExtra(MediaStore.EXTRA_OUTPUT, tempUri);\n            intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);\n        }\n        startActivityForResult(intent, REQUEST_CAMERA);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n        if (resultCode == Activity.RESULT_OK) {\n            switch (requestCode) {\n                case REQUEST_CAMERA:\n                    if (isSingleSelection()) {\n                        if (cropEnable) {\n                            cropImage(tempUri);\n                        } else {\n                            IPicker.finish(ImageMedia.getFilePath(getApplicationContext(), tempUri));\n                            finish();\n                        }\n                    } else {\n                        String path = ImageMedia.getFilePath(getApplicationContext(), tempUri);\n                        Photo photo = new Photo(path);\n                        photoList.add(1, photo);\n                        if (adapter.getSelectedTotal() < limit) {\n                            adapter.addSelected(photo);\n                        }\n                        adapter.notifyDataSetChanged();\n                        updateTitle();\n                    }\n                    break;\n                case Crop.REQUEST_CROP:\n                    IPicker.finish(Crop.getOutput(data).toString());\n                    finish();\n                    break;\n            }\n        } else if (resultCode == Activity.RESULT_CANCELED) {\n            switch (requestCode) {\n                case REQUEST_CAMERA:\n                    deleteTemp();\n                    break;\n            }\n        }\n    }\n\n    private void promptNoPermission(@StringRes int res) {\n        Snackbar.make(layoutContainer, res, Snackbar.LENGTH_LONG).setAction(R.string.btn_setting, new View.OnClickListener() {\n            @Override\n            public void onClick(View view) {\n                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,\n                        Uri.parse(\"package:\" + getPackageName()));\n                startActivity(intent);\n            }\n        }).show();\n    }\n\n    private void cropImage(Uri source) {\n        Uri destination = Uri.fromFile(new File(getCacheDir(), \"cropped\" + System.currentTimeMillis()));\n        Crop.of(source, destination).asSquare().start(this);\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/adapters/BaseAdapter.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.adapters;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * Created by Eric on 15/7/7.\n */\npublic abstract class BaseAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {\n\n    public Context mContext;\n    public List<T> data = new ArrayList<>();\n    public final LayoutInflater layoutInflater;\n\n    public OnItemClickListener onItemClickListener;\n    public OnItemLongClickListener onItemLongClickListener;\n\n    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {\n        this.onItemClickListener = onItemClickListener;\n    }\n\n    public void setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) {\n        this.onItemLongClickListener = onItemLongClickListener;\n    }\n\n    public BaseAdapter(Context context, List<T> data) {\n        this.mContext = context;\n        this.data = data;\n        this.layoutInflater = LayoutInflater.from(context);\n    }\n\n    public LayoutInflater getLayoutInflater() {\n        return layoutInflater;\n    }\n\n    @Override\n    public int getItemCount() {\n        return data.size();\n    }\n\n    @Override\n    public void onBindViewHolder(VH holder, final int position) {\n        if (holder.itemView != null) {\n            if (onItemClickListener != null) {\n                holder.itemView.setOnClickListener(new View.OnClickListener() {\n                    @Override\n                    public void onClick(View v) {\n                        onItemClickListener.onItemClick(v, position);\n                    }\n                });\n            }\n\n            if (onItemLongClickListener != null) {\n                holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {\n                    @Override\n                    public boolean onLongClick(View v) {\n                        onItemLongClickListener.onItemLongClick(position);\n                        return false;\n                    }\n                });\n            }\n        }\n    }\n\n    @Override\n    public VH onCreateViewHolder(ViewGroup parent, int viewType) {\n        return null;\n    }\n\n    @Override\n    public void onViewRecycled(VH holder) {\n        super.onViewRecycled(holder);\n    }\n\n    public interface OnItemClickListener {\n\n        void onItemClick(View view, int position);\n\n    }\n\n    public interface OnItemLongClickListener {\n\n        void onItemLongClick(int position);\n\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/adapters/PhotosAdapter.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.adapters;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ImageView;\n\nimport com.bumptech.glide.Glide;\nimport com.liuguangqiang.ipicker.R;\nimport com.liuguangqiang.ipicker.entities.Photo;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport androidx.recyclerview.widget.RecyclerView;\n\n/**\n * Created by Eric on 16/9/12.\n */\npublic class PhotosAdapter extends BaseAdapter<Photo, PhotosAdapter.ViewHolder> {\n\n    private List<String> selected = new ArrayList<>();\n    private boolean isSingleSelection = true;\n\n    public PhotosAdapter(Context context, List<Photo> data) {\n        super(context, data);\n    }\n\n    public void setSingleSelection(boolean singleSelection) {\n        this.isSingleSelection = singleSelection;\n    }\n\n    public void addSelected(List<String> list) {\n        selected.addAll(list);\n    }\n\n    public void addSelected(Photo photo) {\n        selected.add(photo.path);\n    }\n\n    public void removeSelected(Photo photo) {\n        selected.remove(photo.path);\n    }\n\n    public List<String> getSelected() {\n        return selected;\n    }\n\n    public boolean isSelected(String path) {\n        return selected.contains(path);\n    }\n\n    public int getSelectedTotal() {\n        return selected.size();\n    }\n\n    @Override\n    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {\n        return new ViewHolder(layoutInflater, parent, false);\n    }\n\n    @Override\n    public void onBindViewHolder(ViewHolder holder, int position) {\n        super.onBindViewHolder(holder, position);\n        holder.bindData(data.get(position), !isSingleSelection && isSelected(data.get(position).path));\n    }\n\n    public static class ViewHolder extends RecyclerView.ViewHolder {\n\n        private ImageView ivPhoto;\n        private ImageView ivCamera;\n        private ImageView ivCheckbox;\n\n        public ViewHolder(LayoutInflater layoutInflater, ViewGroup parent, boolean attachToRoot) {\n            super(layoutInflater.inflate(R.layout.item_photo, parent, attachToRoot));\n            ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo);\n            ivCamera = (ImageView) itemView.findViewById(R.id.iv_camera);\n            ivCheckbox = (ImageView) itemView.findViewById(R.id.iv_checkbox);\n        }\n\n        public void bindData(Photo entity, boolean selected) {\n            if (entity.showCamera) {\n                ivPhoto.setVisibility(View.GONE);\n                ivCamera.setVisibility(View.VISIBLE);\n                ivCheckbox.setVisibility(View.GONE);\n            } else {\n                ivPhoto.setVisibility(View.VISIBLE);\n                ivCamera.setVisibility(View.GONE);\n                ivCheckbox.setVisibility(selected ? View.VISIBLE : View.GONE);\n                Glide.with(itemView.getContext()).load(entity.path).into(ivPhoto);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/Crop.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.crop;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.content.ActivityNotFoundException;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.provider.MediaStore;\nimport android.widget.Toast;\n\nimport com.liuguangqiang.ipicker.CropImageActivity;\nimport com.liuguangqiang.ipicker.R;\n\n/**\n * Builder for crop Intents and utils for handling result\n */\npublic class Crop {\n\n    public static final int REQUEST_CROP = 6709;\n    public static final int REQUEST_PICK = 9162;\n    public static final int RESULT_ERROR = 404;\n\n    public interface Extra {\n        String ASPECT_X = \"aspect_x\";\n        String ASPECT_Y = \"aspect_y\";\n        String MAX_X = \"max_x\";\n        String MAX_Y = \"max_y\";\n        String AS_PNG = \"as_png\";\n        String ERROR = \"error\";\n    }\n\n    private Intent cropIntent;\n\n    /**\n     * Create a crop Intent builder with source and destination image Uris\n     *\n     * @param source      Uri for image to crop\n     * @param destination Uri for saving the cropped image\n     */\n    public static Crop of(Uri source, Uri destination) {\n        return new Crop(source, destination);\n    }\n\n    private Crop(Uri source, Uri destination) {\n        cropIntent = new Intent();\n        cropIntent.setData(source);\n        cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);\n    }\n\n    /**\n     * Set fixed aspect ratio for crop area\n     *\n     * @param x Aspect X\n     * @param y Aspect Y\n     */\n    public Crop withAspect(int x, int y) {\n        cropIntent.putExtra(Extra.ASPECT_X, x);\n        cropIntent.putExtra(Extra.ASPECT_Y, y);\n        return this;\n    }\n\n    /**\n     * Crop area with fixed 1:1 aspect ratio\n     */\n    public Crop asSquare() {\n        cropIntent.putExtra(Extra.ASPECT_X, 1);\n        cropIntent.putExtra(Extra.ASPECT_Y, 1);\n        return this;\n    }\n\n    /**\n     * Set maximum crop size\n     *\n     * @param width  Max width\n     * @param height Max height\n     */\n    public Crop withMaxSize(int width, int height) {\n        cropIntent.putExtra(Extra.MAX_X, width);\n        cropIntent.putExtra(Extra.MAX_Y, height);\n        return this;\n    }\n\n    /**\n     * Set whether to save the result as a PNG or not. Helpful to preserve alpha.\n     *\n     * @param asPng whether to save the result as a PNG or not\n     */\n    public Crop asPng(boolean asPng) {\n        cropIntent.putExtra(Extra.AS_PNG, asPng);\n        return this;\n    }\n\n    /**\n     * Send the crop Intent from an Activity\n     *\n     * @param activity Activity to receive result\n     */\n    public void start(Activity activity) {\n        start(activity, REQUEST_CROP);\n    }\n\n    /**\n     * Send the crop Intent from an Activity with a custom request code\n     *\n     * @param activity    Activity to receive result\n     * @param requestCode requestCode for result\n     */\n    public void start(Activity activity, int requestCode) {\n        activity.startActivityForResult(getIntent(activity), requestCode);\n    }\n\n    /**\n     * Send the crop Intent from a Fragment\n     *\n     * @param context  Context\n     * @param fragment Fragment to receive result\n     */\n    public void start(Context context, Fragment fragment) {\n        start(context, fragment, REQUEST_CROP);\n    }\n\n    /**\n     * Send the crop Intent with a custom request code\n     *\n     * @param context     Context\n     * @param fragment    Fragment to receive result\n     * @param requestCode requestCode for result\n     */\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n    public void start(Context context, Fragment fragment, int requestCode) {\n        fragment.startActivityForResult(getIntent(context), requestCode);\n    }\n\n    /**\n     * Get Intent to start crop Activity\n     *\n     * @param context Context\n     * @return Intent for CropImageActivity\n     */\n    public Intent getIntent(Context context) {\n        cropIntent.setClass(context, CropImageActivity.class);\n        return cropIntent;\n    }\n\n    /**\n     * Retrieve URI for cropped image, as set in the Intent builder\n     *\n     * @param result Output Image URI\n     */\n    public static Uri getOutput(Intent result) {\n        return result.getParcelableExtra(MediaStore.EXTRA_OUTPUT);\n    }\n\n    /**\n     * Retrieve error that caused crop to fail\n     *\n     * @param result Result Intent\n     * @return Throwable handled in CropImageActivity\n     */\n    public static Throwable getError(Intent result) {\n        return (Throwable) result.getSerializableExtra(Extra.ERROR);\n    }\n\n    /**\n     * Pick image from an Activity\n     *\n     * @param activity Activity to receive result\n     */\n    public static void pickImage(Activity activity) {\n        pickImage(activity, REQUEST_PICK);\n    }\n\n    /**\n     * Pick image from a Fragment\n     *\n     * @param context  Context\n     * @param fragment Fragment to receive result\n     */\n    public static void pickImage(Context context, Fragment fragment) {\n        pickImage(context, fragment, REQUEST_PICK);\n    }\n\n    /**\n     * Pick image from an Activity with a custom request code\n     *\n     * @param activity    Activity to receive result\n     * @param requestCode requestCode for result\n     */\n    public static void pickImage(Activity activity, int requestCode) {\n        try {\n            activity.startActivityForResult(getImagePicker(), requestCode);\n        } catch (ActivityNotFoundException e) {\n            showImagePickerError(activity);\n        }\n    }\n\n    /**\n     * Pick image from a Fragment with a custom request code\n     *\n     * @param context     Context\n     * @param fragment    Fragment to receive result\n     * @param requestCode requestCode for result\n     */\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n    public static void pickImage(Context context, Fragment fragment, int requestCode) {\n        try {\n            fragment.startActivityForResult(getImagePicker(), requestCode);\n        } catch (ActivityNotFoundException e) {\n            showImagePickerError(context);\n        }\n    }\n\n    private static Intent getImagePicker() {\n        return new Intent(Intent.ACTION_GET_CONTENT).setType(\"image/*\");\n    }\n\n    private static void showImagePickerError(Context context) {\n        Toast.makeText(context.getApplicationContext(), R.string.crop__pick_error, Toast.LENGTH_SHORT).show();\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/CropImageView.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.crop;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\n\nimport com.liuguangqiang.ipicker.CropImageActivity;\n\nimport java.util.ArrayList;\n\nimport androidx.annotation.NonNull;\n\npublic class CropImageView extends ImageViewTouchBase {\n\n    public ArrayList<HighlightView> highlightViews = new ArrayList<HighlightView>();\n    public HighlightView motionHighlightView;\n    public Context context;\n\n    private float lastX;\n    private float lastY;\n    private int motionEdge;\n    private int validPointerId;\n\n    public CropImageView(Context context) {\n        super(context);\n    }\n\n    public CropImageView(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public CropImageView(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n        if (bitmapDisplayed.getBitmap() != null) {\n            for (HighlightView hv : highlightViews) {\n\n                hv.matrix.set(getUnrotatedMatrix());\n                hv.invalidate();\n                if (hv.hasFocus()) {\n                    centerBasedOnHighlightView(hv);\n                }\n            }\n        }\n    }\n\n    @Override\n    protected void zoomTo(float scale, float centerX, float centerY) {\n        super.zoomTo(scale, centerX, centerY);\n        for (HighlightView hv : highlightViews) {\n            hv.matrix.set(getUnrotatedMatrix());\n            hv.invalidate();\n        }\n    }\n\n    @Override\n    protected void zoomIn() {\n        super.zoomIn();\n        for (HighlightView hv : highlightViews) {\n            hv.matrix.set(getUnrotatedMatrix());\n            hv.invalidate();\n        }\n    }\n\n    @Override\n    protected void zoomOut() {\n        super.zoomOut();\n        for (HighlightView hv : highlightViews) {\n            hv.matrix.set(getUnrotatedMatrix());\n            hv.invalidate();\n        }\n    }\n\n    @Override\n    protected void postTranslate(float deltaX, float deltaY) {\n        super.postTranslate(deltaX, deltaY);\n        for (HighlightView hv : highlightViews) {\n            hv.matrix.postTranslate(deltaX, deltaY);\n            hv.invalidate();\n        }\n    }\n\n    @Override\n    public boolean onTouchEvent(@NonNull MotionEvent event) {\n        CropImageActivity cropImageActivity = (CropImageActivity) context;\n        if (cropImageActivity.isSaving()) {\n            return false;\n        }\n\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN:\n                for (HighlightView hv : highlightViews) {\n                    int edge = hv.getHit(event.getX(), event.getY());\n                    if (edge != HighlightView.GROW_NONE) {\n                        motionEdge = edge;\n                        motionHighlightView = hv;\n                        lastX = event.getX();\n                        lastY = event.getY();\n                        // Prevent multiple touches from interfering with crop area re-sizing\n                        validPointerId = event.getPointerId(event.getActionIndex());\n                        motionHighlightView.setMode((edge == HighlightView.MOVE)\n                                ? HighlightView.ModifyMode.Move\n                                : HighlightView.ModifyMode.Grow);\n                        break;\n                    }\n                }\n                break;\n            case MotionEvent.ACTION_UP:\n                if (motionHighlightView != null) {\n                    centerBasedOnHighlightView(motionHighlightView);\n                    motionHighlightView.setMode(HighlightView.ModifyMode.None);\n                }\n                motionHighlightView = null;\n                center();\n                break;\n            case MotionEvent.ACTION_MOVE:\n                if (motionHighlightView != null && event.getPointerId(event.getActionIndex()) == validPointerId) {\n                    motionHighlightView.handleMotion(motionEdge, event.getX()\n                            - lastX, event.getY() - lastY);\n                    lastX = event.getX();\n                    lastY = event.getY();\n                }\n\n                // If we're not zoomed then there's no point in even allowing the user to move the image around.\n                // This call to center puts it back to the normalized location.\n                if (getScale() == 1F) {\n                    center();\n                }\n                break;\n        }\n\n        return true;\n    }\n\n    // Pan the displayed image to make sure the cropping rectangle is visible.\n    private void ensureVisible(HighlightView hv) {\n        Rect r = hv.drawRect;\n\n        int panDeltaX1 = Math.max(0, getLeft() - r.left);\n        int panDeltaX2 = Math.min(0, getRight() - r.right);\n\n        int panDeltaY1 = Math.max(0, getTop() - r.top);\n        int panDeltaY2 = Math.min(0, getBottom() - r.bottom);\n\n        int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;\n        int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;\n\n        if (panDeltaX != 0 || panDeltaY != 0) {\n            panBy(panDeltaX, panDeltaY);\n        }\n    }\n\n    // If the cropping rectangle's size changed significantly, change the\n    // view's center and scale according to the cropping rectangle.\n    private void centerBasedOnHighlightView(HighlightView hv) {\n        Rect drawRect = hv.drawRect;\n\n        float width = drawRect.width();\n        float height = drawRect.height();\n\n        float thisWidth = getWidth();\n        float thisHeight = getHeight();\n\n        float z1 = thisWidth / width * .6F;\n        float z2 = thisHeight / height * .6F;\n\n        float zoom = Math.min(z1, z2);\n        zoom = zoom * this.getScale();\n        zoom = Math.max(1F, zoom);\n\n        if ((Math.abs(zoom - getScale()) / zoom) > .1) {\n            float[] coordinates = new float[]{hv.cropRect.centerX(), hv.cropRect.centerY()};\n            getUnrotatedMatrix().mapPoints(coordinates);\n            zoomTo(zoom, coordinates[0], coordinates[1], 300F);\n        }\n\n        ensureVisible(hv);\n    }\n\n    @Override\n    protected void onDraw(@NonNull Canvas canvas) {\n        super.onDraw(canvas);\n        for (HighlightView highlightView : highlightViews) {\n            highlightView.draw(canvas);\n        }\n    }\n\n    public void add(HighlightView hv) {\n        highlightViews.add(hv);\n        invalidate();\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/CropUtil.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.crop;\n\nimport android.app.ProgressDialog;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.media.ExifInterface;\nimport android.net.Uri;\nimport android.os.Handler;\nimport android.os.ParcelFileDescriptor;\nimport android.provider.MediaStore;\nimport android.text.TextUtils;\n\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.FileDescriptor;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\n\nimport androidx.annotation.Nullable;\n\n/*\n * Modified from original in AOSP.\n */\npublic class CropUtil {\n\n    private static final String SCHEME_FILE = \"file\";\n    private static final String SCHEME_CONTENT = \"content\";\n\n    public static void closeSilently(@Nullable Closeable c) {\n        if (c == null) return;\n        try {\n            c.close();\n        } catch (Throwable t) {\n            // Do nothing\n        }\n    }\n\n    public static int getExifRotation(File imageFile) {\n        if (imageFile == null) return 0;\n        try {\n            ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());\n            // We only recognize a subset of orientation tag values\n            switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {\n                case ExifInterface.ORIENTATION_ROTATE_90:\n                    return 90;\n                case ExifInterface.ORIENTATION_ROTATE_180:\n                    return 180;\n                case ExifInterface.ORIENTATION_ROTATE_270:\n                    return 270;\n                default:\n                    return ExifInterface.ORIENTATION_UNDEFINED;\n            }\n        } catch (IOException e) {\n            Log.e(\"Error getting Exif data\", e);\n            return 0;\n        }\n    }\n\n    public static boolean copyExifRotation(File sourceFile, File destFile) {\n        if (sourceFile == null || destFile == null) return false;\n        try {\n            ExifInterface exifSource = new ExifInterface(sourceFile.getAbsolutePath());\n            ExifInterface exifDest = new ExifInterface(destFile.getAbsolutePath());\n            exifDest.setAttribute(ExifInterface.TAG_ORIENTATION, exifSource.getAttribute(ExifInterface.TAG_ORIENTATION));\n            exifDest.saveAttributes();\n            return true;\n        } catch (IOException e) {\n            Log.e(\"Error copying Exif data\", e);\n            return false;\n        }\n    }\n\n    @Nullable\n    public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) {\n        if (uri == null) return null;\n\n        if (SCHEME_FILE.equals(uri.getScheme())) {\n            return new File(uri.getPath());\n        } else if (SCHEME_CONTENT.equals(uri.getScheme())) {\n            final String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME};\n            Cursor cursor = null;\n            try {\n                cursor = resolver.query(uri, filePathColumn, null, null, null);\n                if (cursor != null && cursor.moveToFirst()) {\n                    final int columnIndex = (uri.toString().startsWith(\"content://com.google.android.gallery3d\")) ?\n                            cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) :\n                            cursor.getColumnIndex(MediaStore.MediaColumns.DATA);\n                    // Picasa images on API 13+\n                    if (columnIndex != -1) {\n                        String filePath = cursor.getString(columnIndex);\n                        if (!TextUtils.isEmpty(filePath)) {\n                            return new File(filePath);\n                        }\n                    }\n                }\n            } catch (IllegalArgumentException e) {\n                // Google Drive images\n                return getFromMediaUriPfd(context, resolver, uri);\n            } catch (SecurityException ignored) {\n                // Nothing we can do\n            } finally {\n                if (cursor != null) cursor.close();\n            }\n        }\n        return null;\n    }\n\n    private static String getTempFilename(Context context) throws IOException {\n        File outputDir = context.getCacheDir();\n        File outputFile = File.createTempFile(\"image\", \"tmp\", outputDir);\n        return outputFile.getAbsolutePath();\n    }\n\n    @Nullable\n    private static File getFromMediaUriPfd(Context context, ContentResolver resolver, Uri uri) {\n        if (uri == null) return null;\n\n        FileInputStream input = null;\n        FileOutputStream output = null;\n        try {\n            ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, \"r\");\n            FileDescriptor fd = pfd.getFileDescriptor();\n            input = new FileInputStream(fd);\n\n            String tempFilename = getTempFilename(context);\n            output = new FileOutputStream(tempFilename);\n\n            int read;\n            byte[] bytes = new byte[4096];\n            while ((read = input.read(bytes)) != -1) {\n                output.write(bytes, 0, read);\n            }\n            return new File(tempFilename);\n        } catch (IOException ignored) {\n            // Nothing we can do\n        } finally {\n            closeSilently(input);\n            closeSilently(output);\n        }\n        return null;\n    }\n\n    public static void startBackgroundJob(MonitoredActivity activity,\n                                          String title, String message, Runnable job, Handler handler) {\n        // Make the progress dialog uncancelable, so that we can guarantee\n        // the thread will be done before the activity getting destroyed\n        ProgressDialog dialog = ProgressDialog.show(\n                activity, title, message, true, false);\n        new Thread(new BackgroundJob(activity, job, dialog, handler)).start();\n    }\n\n    private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter implements Runnable {\n\n        private final MonitoredActivity activity;\n        private final ProgressDialog dialog;\n        private final Runnable job;\n        private final Handler handler;\n        private final Runnable cleanupRunner = new Runnable() {\n            public void run() {\n                activity.removeLifeCycleListener(BackgroundJob.this);\n                if (dialog.getWindow() != null) dialog.dismiss();\n            }\n        };\n\n        public BackgroundJob(MonitoredActivity activity, Runnable job,\n                             ProgressDialog dialog, Handler handler) {\n            this.activity = activity;\n            this.dialog = dialog;\n            this.job = job;\n            this.activity.addLifeCycleListener(this);\n            this.handler = handler;\n        }\n\n        public void run() {\n            try {\n                job.run();\n            } finally {\n                handler.post(cleanupRunner);\n            }\n        }\n\n        @Override\n        public void onActivityDestroyed(MonitoredActivity activity) {\n            // We get here only when the onDestroyed being called before\n            // the cleanupRunner. So, run it now and remove it from the queue\n            cleanupRunner.run();\n            handler.removeCallbacks(cleanupRunner);\n        }\n\n        @Override\n        public void onActivityStopped(MonitoredActivity activity) {\n            dialog.hide();\n        }\n\n        @Override\n        public void onActivityStarted(MonitoredActivity activity) {\n            dialog.show();\n        }\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/HighlightView.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.crop;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Region;\nimport android.os.Build;\nimport android.util.TypedValue;\nimport android.view.View;\n\nimport com.liuguangqiang.ipicker.R;\n\n/*\n * Modified from code in AOSP.\n *\n * This class is used to display a highlighted cropping rectangle\n * overlayed on the image. There are two coordinate spaces in use. One is\n * image, another is screen. computeLayout() uses matrix to map from image\n * space to screen space.\n */\npublic class HighlightView {\n\n    public static final int GROW_NONE = (1 << 0);\n    public static final int GROW_LEFT_EDGE = (1 << 1);\n    public static final int GROW_RIGHT_EDGE = (1 << 2);\n    public static final int GROW_TOP_EDGE = (1 << 3);\n    public static final int GROW_BOTTOM_EDGE = (1 << 4);\n    public static final int MOVE = (1 << 5);\n\n    private static final int DEFAULT_HIGHLIGHT_COLOR = 0xFF33B5E5;\n    private static final float HANDLE_RADIUS_DP = 12f;\n    private static final float OUTLINE_DP = 2f;\n\n    enum ModifyMode {None, Move, Grow}\n\n    enum HandleMode {Changing, Always, Never}\n\n    RectF cropRect; // Image space\n    Rect drawRect; // Screen space\n    Matrix matrix;\n    private RectF imageRect; // Image space\n\n    private final Paint outsidePaint = new Paint();\n    private final Paint outlinePaint = new Paint();\n    private final Paint handlePaint = new Paint();\n\n    private View viewContext; // View displaying image\n    private boolean showThirds;\n    private boolean showCircle;\n    private int highlightColor;\n\n    private ModifyMode modifyMode = ModifyMode.None;\n    private HandleMode handleMode = HandleMode.Changing;\n    private boolean maintainAspectRatio;\n    private float initialAspectRatio;\n    private float handleRadius;\n    private float outlineWidth;\n    private boolean isFocused;\n\n    public HighlightView(View context) {\n        viewContext = context;\n        initStyles(context.getContext());\n    }\n\n    private void initStyles(Context context) {\n        TypedValue outValue = new TypedValue();\n        context.getTheme().resolveAttribute(R.attr.IPicker_CropImageStyle, outValue, true);\n        TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.IPicker_CropImageView);\n        try {\n            showThirds = attributes.getBoolean(R.styleable.IPicker_CropImageView_ipicker_showThirds, false);\n            showCircle = attributes.getBoolean(R.styleable.IPicker_CropImageView_ipicker_showCircle, false);\n            highlightColor = attributes.getColor(R.styleable.IPicker_CropImageView_ipicker_highlightColor,\n                    DEFAULT_HIGHLIGHT_COLOR);\n            handleMode = HandleMode.values()[attributes.getInt(R.styleable.IPicker_CropImageView_ipicker_showHandles, 0)];\n        } finally {\n            attributes.recycle();\n        }\n    }\n\n    public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean maintainAspectRatio) {\n        matrix = new Matrix(m);\n\n        this.cropRect = cropRect;\n        this.imageRect = new RectF(imageRect);\n        this.maintainAspectRatio = maintainAspectRatio;\n\n        initialAspectRatio = this.cropRect.width() / this.cropRect.height();\n        drawRect = computeLayout();\n\n        outsidePaint.setARGB(125, 50, 50, 50);\n        outlinePaint.setStyle(Paint.Style.STROKE);\n        outlinePaint.setAntiAlias(true);\n        outlineWidth = dpToPx(OUTLINE_DP);\n\n        handlePaint.setColor(highlightColor);\n        handlePaint.setStyle(Paint.Style.FILL);\n        handlePaint.setAntiAlias(true);\n        handleRadius = dpToPx(HANDLE_RADIUS_DP);\n\n        modifyMode = ModifyMode.None;\n    }\n\n    private float dpToPx(float dp) {\n        return dp * viewContext.getResources().getDisplayMetrics().density;\n    }\n\n    protected void draw(Canvas canvas) {\n        canvas.save();\n        Path path = new Path();\n        outlinePaint.setStrokeWidth(outlineWidth);\n        if (!hasFocus()) {\n            outlinePaint.setColor(Color.BLACK);\n            canvas.drawRect(drawRect, outlinePaint);\n        } else {\n            Rect viewDrawingRect = new Rect();\n            viewContext.getDrawingRect(viewDrawingRect);\n\n            path.addRect(new RectF(drawRect), Path.Direction.CW);\n            outlinePaint.setColor(highlightColor);\n\n            if (isClipPathSupported(canvas)) {\n                canvas.clipPath(path, Region.Op.DIFFERENCE);\n                canvas.drawRect(viewDrawingRect, outsidePaint);\n            } else {\n                drawOutsideFallback(canvas);\n            }\n\n            canvas.restore();\n            canvas.drawPath(path, outlinePaint);\n\n            if (showThirds) {\n                drawThirds(canvas);\n            }\n\n            if (showCircle) {\n                drawCircle(canvas);\n            }\n\n            if (handleMode == HandleMode.Always ||\n                    (handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {\n                drawHandles(canvas);\n            }\n        }\n    }\n\n    /*\n     * Fall back to naive method for darkening outside crop area\n     */\n    private void drawOutsideFallback(Canvas canvas) {\n        canvas.drawRect(0, 0, canvas.getWidth(), drawRect.top, outsidePaint);\n        canvas.drawRect(0, drawRect.bottom, canvas.getWidth(), canvas.getHeight(), outsidePaint);\n        canvas.drawRect(0, drawRect.top, drawRect.left, drawRect.bottom, outsidePaint);\n        canvas.drawRect(drawRect.right, drawRect.top, canvas.getWidth(), drawRect.bottom, outsidePaint);\n    }\n\n    /*\n     * Clip path is broken, unreliable or not supported on:\n     * - JellyBean MR1\n     * - ICS & ICS MR1 with hardware acceleration turned on\n     */\n    @SuppressLint(\"NewApi\")\n    private boolean isClipPathSupported(Canvas canvas) {\n        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {\n            return false;\n        } else if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)\n                || Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {\n            return true;\n        } else {\n            return !canvas.isHardwareAccelerated();\n        }\n    }\n\n    private void drawHandles(Canvas canvas) {\n        int xMiddle = drawRect.left + ((drawRect.right - drawRect.left) / 2);\n        int yMiddle = drawRect.top + ((drawRect.bottom - drawRect.top) / 2);\n\n        canvas.drawCircle(drawRect.left, yMiddle, handleRadius, handlePaint);\n        canvas.drawCircle(xMiddle, drawRect.top, handleRadius, handlePaint);\n        canvas.drawCircle(drawRect.right, yMiddle, handleRadius, handlePaint);\n        canvas.drawCircle(xMiddle, drawRect.bottom, handleRadius, handlePaint);\n    }\n\n    private void drawThirds(Canvas canvas) {\n        outlinePaint.setStrokeWidth(1);\n        float xThird = (drawRect.right - drawRect.left) / 3;\n        float yThird = (drawRect.bottom - drawRect.top) / 3;\n\n        canvas.drawLine(drawRect.left + xThird, drawRect.top,\n                drawRect.left + xThird, drawRect.bottom, outlinePaint);\n        canvas.drawLine(drawRect.left + xThird * 2, drawRect.top,\n                drawRect.left + xThird * 2, drawRect.bottom, outlinePaint);\n        canvas.drawLine(drawRect.left, drawRect.top + yThird,\n                drawRect.right, drawRect.top + yThird, outlinePaint);\n        canvas.drawLine(drawRect.left, drawRect.top + yThird * 2,\n                drawRect.right, drawRect.top + yThird * 2, outlinePaint);\n    }\n\n    private void drawCircle(Canvas canvas) {\n        outlinePaint.setStrokeWidth(1);\n        canvas.drawOval(new RectF(drawRect), outlinePaint);\n    }\n\n    public void setMode(ModifyMode mode) {\n        if (mode != modifyMode) {\n            modifyMode = mode;\n            viewContext.invalidate();\n        }\n    }\n\n    // Determines which edges are hit by touching at (x, y)\n    public int getHit(float x, float y) {\n        Rect r = computeLayout();\n        final float hysteresis = 20F;\n        int retval = GROW_NONE;\n\n        // verticalCheck makes sure the position is between the top and\n        // the bottom edge (with some tolerance). Similar for horizCheck.\n        boolean verticalCheck = (y >= r.top - hysteresis)\n                && (y < r.bottom + hysteresis);\n        boolean horizCheck = (x >= r.left - hysteresis)\n                && (x < r.right + hysteresis);\n\n        // Check whether the position is near some edge(s)\n        if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {\n            retval |= GROW_LEFT_EDGE;\n        }\n        if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {\n            retval |= GROW_RIGHT_EDGE;\n        }\n        if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {\n            retval |= GROW_TOP_EDGE;\n        }\n        if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {\n            retval |= GROW_BOTTOM_EDGE;\n        }\n\n        // Not near any edge but inside the rectangle: move\n        if (retval == GROW_NONE && r.contains((int) x, (int) y)) {\n            retval = MOVE;\n        }\n        return retval;\n    }\n\n    // Handles motion (dx, dy) in screen space.\n    // The \"edge\" parameter specifies which edges the user is dragging.\n    void handleMotion(int edge, float dx, float dy) {\n        Rect r = computeLayout();\n        if (edge == MOVE) {\n            // Convert to image space before sending to moveBy()\n            moveBy(dx * (cropRect.width() / r.width()),\n                    dy * (cropRect.height() / r.height()));\n        } else {\n            if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {\n                dx = 0;\n            }\n\n            if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {\n                dy = 0;\n            }\n\n            // Convert to image space before sending to growBy()\n            float xDelta = dx * (cropRect.width() / r.width());\n            float yDelta = dy * (cropRect.height() / r.height());\n            growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,\n                    (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);\n        }\n    }\n\n    // Grows the cropping rectangle by (dx, dy) in image space\n    void moveBy(float dx, float dy) {\n        Rect invalRect = new Rect(drawRect);\n\n        cropRect.offset(dx, dy);\n\n        // Put the cropping rectangle inside image rectangle\n        cropRect.offset(\n                Math.max(0, imageRect.left - cropRect.left),\n                Math.max(0, imageRect.top - cropRect.top));\n\n        cropRect.offset(\n                Math.min(0, imageRect.right - cropRect.right),\n                Math.min(0, imageRect.bottom - cropRect.bottom));\n\n        drawRect = computeLayout();\n        invalRect.union(drawRect);\n        invalRect.inset(-(int) handleRadius, -(int) handleRadius);\n        viewContext.invalidate(invalRect);\n    }\n\n    // Grows the cropping rectangle by (dx, dy) in image space.\n    void growBy(float dx, float dy) {\n        if (maintainAspectRatio) {\n            if (dx != 0) {\n                dy = dx / initialAspectRatio;\n            } else if (dy != 0) {\n                dx = dy * initialAspectRatio;\n            }\n        }\n\n        // Don't let the cropping rectangle grow too fast.\n        // Grow at most half of the difference between the image rectangle and\n        // the cropping rectangle.\n        RectF r = new RectF(cropRect);\n        if (dx > 0F && r.width() + 2 * dx > imageRect.width()) {\n            dx = (imageRect.width() - r.width()) / 2F;\n            if (maintainAspectRatio) {\n                dy = dx / initialAspectRatio;\n            }\n        }\n        if (dy > 0F && r.height() + 2 * dy > imageRect.height()) {\n            dy = (imageRect.height() - r.height()) / 2F;\n            if (maintainAspectRatio) {\n                dx = dy * initialAspectRatio;\n            }\n        }\n\n        r.inset(-dx, -dy);\n\n        // Don't let the cropping rectangle shrink too fast\n        final float widthCap = 25F;\n        if (r.width() < widthCap) {\n            r.inset(-(widthCap - r.width()) / 2F, 0F);\n        }\n        float heightCap = maintainAspectRatio\n                ? (widthCap / initialAspectRatio)\n                : widthCap;\n        if (r.height() < heightCap) {\n            r.inset(0F, -(heightCap - r.height()) / 2F);\n        }\n\n        // Put the cropping rectangle inside the image rectangle\n        if (r.left < imageRect.left) {\n            r.offset(imageRect.left - r.left, 0F);\n        } else if (r.right > imageRect.right) {\n            r.offset(-(r.right - imageRect.right), 0F);\n        }\n        if (r.top < imageRect.top) {\n            r.offset(0F, imageRect.top - r.top);\n        } else if (r.bottom > imageRect.bottom) {\n            r.offset(0F, -(r.bottom - imageRect.bottom));\n        }\n\n        cropRect.set(r);\n        drawRect = computeLayout();\n        viewContext.invalidate();\n    }\n\n    // Returns the cropping rectangle in image space with specified scale\n    public Rect getScaledCropRect(float scale) {\n        return new Rect((int) (cropRect.left * scale), (int) (cropRect.top * scale),\n                (int) (cropRect.right * scale), (int) (cropRect.bottom * scale));\n    }\n\n    // Maps the cropping rectangle from image space to screen space\n    private Rect computeLayout() {\n        RectF r = new RectF(cropRect.left, cropRect.top,\n                cropRect.right, cropRect.bottom);\n        matrix.mapRect(r);\n        return new Rect(Math.round(r.left), Math.round(r.top),\n                Math.round(r.right), Math.round(r.bottom));\n    }\n\n    public void invalidate() {\n        drawRect = computeLayout();\n    }\n\n    public boolean hasFocus() {\n        return isFocused;\n    }\n\n    public void setFocus(boolean isFocused) {\n        this.isFocused = isFocused;\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/ImageViewTouchBase.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.crop;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.Matrix;\nimport android.graphics.RectF;\nimport android.graphics.drawable.Drawable;\nimport android.os.Handler;\nimport android.util.AttributeSet;\nimport android.view.KeyEvent;\n\nimport androidx.appcompat.widget.AppCompatImageView;\n\n/*\n * Modified from original in AOSP.\n */\npublic abstract class ImageViewTouchBase extends AppCompatImageView {\n\n    private static final float SCALE_RATE = 1.25F;\n\n    // This is the base transformation which is used to show the image\n    // initially.  The current computation for this shows the image in\n    // it's entirety, letterboxing as needed.  One could choose to\n    // show the image as cropped instead.\n    //\n    // This matrix is recomputed when we go from the thumbnail image to\n    // the full size image.\n    protected Matrix baseMatrix = new Matrix();\n\n    // This is the supplementary transformation which reflects what\n    // the user has done in terms of zooming and panning.\n    //\n    // This matrix remains the same when we go from the thumbnail image\n    // to the full size image.\n    protected Matrix suppMatrix = new Matrix();\n\n    // This is the final matrix which is computed as the concatentation\n    // of the base matrix and the supplementary matrix.\n    private final Matrix displayMatrix = new Matrix();\n\n    // Temporary buffer used for getting the values out of a matrix.\n    private final float[] matrixValues = new float[9];\n\n    // The current bitmap being displayed.\n    protected final RotateBitmap bitmapDisplayed = new RotateBitmap(null, 0);\n\n    int thisWidth = -1;\n    int thisHeight = -1;\n\n    float maxZoom;\n\n    private Runnable onLayoutRunnable;\n\n    protected Handler handler = new Handler();\n\n    // ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished\n    // its use of that Bitmap\n    public interface Recycler {\n        public void recycle(Bitmap b);\n    }\n\n    private Recycler recycler;\n\n    public ImageViewTouchBase(Context context) {\n        super(context);\n        init();\n    }\n\n    public ImageViewTouchBase(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init();\n    }\n\n    public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        init();\n    }\n\n    public void setRecycler(Recycler recycler) {\n        this.recycler = recycler;\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n        thisWidth = right - left;\n        thisHeight = bottom - top;\n        Runnable r = onLayoutRunnable;\n        if (r != null) {\n            onLayoutRunnable = null;\n            r.run();\n        }\n        if (bitmapDisplayed.getBitmap() != null) {\n            getProperBaseMatrix(bitmapDisplayed, baseMatrix, true);\n            setImageMatrix(getImageViewMatrix());\n        }\n    }\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {\n            event.startTracking();\n            return true;\n        }\n        return super.onKeyDown(keyCode, event);\n    }\n\n    @Override\n    public boolean onKeyUp(int keyCode, KeyEvent event) {\n        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {\n            if (getScale() > 1.0f) {\n                // If we're zoomed in, pressing Back jumps out to show the\n                // entire image, otherwise Back returns the user to the gallery\n                zoomTo(1.0f);\n                return true;\n            }\n        }\n        return super.onKeyUp(keyCode, event);\n    }\n\n    @Override\n    public void setImageBitmap(Bitmap bitmap) {\n        setImageBitmap(bitmap, 0);\n    }\n\n    private void setImageBitmap(Bitmap bitmap, int rotation) {\n        super.setImageBitmap(bitmap);\n        Drawable d = getDrawable();\n        if (d != null) {\n            d.setDither(true);\n        }\n\n        Bitmap old = bitmapDisplayed.getBitmap();\n        bitmapDisplayed.setBitmap(bitmap);\n        bitmapDisplayed.setRotation(rotation);\n\n        if (old != null && old != bitmap && recycler != null) {\n            recycler.recycle(old);\n        }\n    }\n\n    public void clear() {\n        setImageBitmapResetBase(null, true);\n    }\n\n\n    // This function changes bitmap, reset base matrix according to the size\n    // of the bitmap, and optionally reset the supplementary matrix\n    public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {\n        setImageRotateBitmapResetBase(new RotateBitmap(bitmap, 0), resetSupp);\n    }\n\n    public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, final boolean resetSupp) {\n        final int viewWidth = getWidth();\n\n        if (viewWidth <= 0) {\n            onLayoutRunnable = new Runnable() {\n                public void run() {\n                    setImageRotateBitmapResetBase(bitmap, resetSupp);\n                }\n            };\n            return;\n        }\n\n        if (bitmap.getBitmap() != null) {\n            getProperBaseMatrix(bitmap, baseMatrix, true);\n            setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());\n        } else {\n            baseMatrix.reset();\n            setImageBitmap(null);\n        }\n\n        if (resetSupp) {\n            suppMatrix.reset();\n        }\n        setImageMatrix(getImageViewMatrix());\n        maxZoom = calculateMaxZoom();\n    }\n\n    // Center as much as possible in one or both axis.  Centering is defined as follows:\n    // * If the image is scaled down below the view's dimensions then center it.\n    // * If the image is scaled larger than the view and is translated out of view then translate it back into view.\n    public void center() {\n        final Bitmap bitmap = bitmapDisplayed.getBitmap();\n        if (bitmap == null) {\n            return;\n        }\n        Matrix m = getImageViewMatrix();\n\n        RectF rect = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());\n        m.mapRect(rect);\n\n        float height = rect.height();\n        float width = rect.width();\n\n        float deltaX = 0, deltaY = 0;\n\n        deltaY = centerVertical(rect, height, deltaY);\n        deltaX = centerHorizontal(rect, width, deltaX);\n\n        postTranslate(deltaX, deltaY);\n        setImageMatrix(getImageViewMatrix());\n    }\n\n    private float centerVertical(RectF rect, float height, float deltaY) {\n        int viewHeight = getHeight();\n        if (height < viewHeight) {\n            deltaY = (viewHeight - height) / 2 - rect.top;\n        } else if (rect.top > 0) {\n            deltaY = -rect.top;\n        } else if (rect.bottom < viewHeight) {\n            deltaY = getHeight() - rect.bottom;\n        }\n        return deltaY;\n    }\n\n    private float centerHorizontal(RectF rect, float width, float deltaX) {\n        int viewWidth = getWidth();\n        if (width < viewWidth) {\n            deltaX = (viewWidth - width) / 2 - rect.left;\n        } else if (rect.left > 0) {\n            deltaX = -rect.left;\n        } else if (rect.right < viewWidth) {\n            deltaX = viewWidth - rect.right;\n        }\n        return deltaX;\n    }\n\n    private void init() {\n        setScaleType(ScaleType.MATRIX);\n    }\n\n    protected float getValue(Matrix matrix, int whichValue) {\n        matrix.getValues(matrixValues);\n        return matrixValues[whichValue];\n    }\n\n    // Get the scale factor out of the matrix.\n    public float getScale(Matrix matrix) {\n        return getValue(matrix, Matrix.MSCALE_X);\n    }\n\n    public float getScale() {\n        return getScale(suppMatrix);\n    }\n\n    // Setup the base matrix so that the image is centered and scaled properly.\n    private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix, boolean includeRotation) {\n        float viewWidth = getWidth();\n        float viewHeight = getHeight();\n\n        float w = bitmap.getWidth();\n        float h = bitmap.getHeight();\n        matrix.reset();\n\n        // We limit up-scaling to 3x otherwise the result may look bad if it's a small icon\n        float widthScale = Math.min(viewWidth / w, 3.0f);\n        float heightScale = Math.min(viewHeight / h, 3.0f);\n        float scale = Math.min(widthScale, heightScale);\n\n        if (includeRotation) {\n            matrix.postConcat(bitmap.getRotateMatrix());\n        }\n        matrix.postScale(scale, scale);\n        matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);\n    }\n\n    // Combine the base matrix and the supp matrix to make the final matrix\n    protected Matrix getImageViewMatrix() {\n        // The final matrix is computed as the concatentation of the base matrix\n        // and the supplementary matrix\n        displayMatrix.set(baseMatrix);\n        displayMatrix.postConcat(suppMatrix);\n        return displayMatrix;\n    }\n\n    public Matrix getUnrotatedMatrix() {\n        Matrix unrotated = new Matrix();\n        getProperBaseMatrix(bitmapDisplayed, unrotated, false);\n        unrotated.postConcat(suppMatrix);\n        return unrotated;\n    }\n\n    protected float calculateMaxZoom() {\n        if (bitmapDisplayed.getBitmap() == null) {\n            return 1F;\n        }\n\n        float fw = (float) bitmapDisplayed.getWidth() / (float) thisWidth;\n        float fh = (float) bitmapDisplayed.getHeight() / (float) thisHeight;\n        return Math.max(fw, fh) * 4; // 400%\n    }\n\n    protected void zoomTo(float scale, float centerX, float centerY) {\n        if (scale > maxZoom) {\n            scale = maxZoom;\n        }\n\n        float oldScale = getScale();\n        float deltaScale = scale / oldScale;\n\n        suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);\n        setImageMatrix(getImageViewMatrix());\n        center();\n    }\n\n    protected void zoomTo(final float scale, final float centerX,\n                          final float centerY, final float durationMs) {\n        final float incrementPerMs = (scale - getScale()) / durationMs;\n        final float oldScale = getScale();\n        final long startTime = System.currentTimeMillis();\n\n        handler.post(new Runnable() {\n            public void run() {\n                long now = System.currentTimeMillis();\n                float currentMs = Math.min(durationMs, now - startTime);\n                float target = oldScale + (incrementPerMs * currentMs);\n                zoomTo(target, centerX, centerY);\n\n                if (currentMs < durationMs) {\n                    handler.post(this);\n                }\n            }\n        });\n    }\n\n    protected void zoomTo(float scale) {\n        float cx = getWidth() / 2F;\n        float cy = getHeight() / 2F;\n        zoomTo(scale, cx, cy);\n    }\n\n    protected void zoomIn() {\n        zoomIn(SCALE_RATE);\n    }\n\n    protected void zoomOut() {\n        zoomOut(SCALE_RATE);\n    }\n\n    protected void zoomIn(float rate) {\n        if (getScale() >= maxZoom) {\n            return; // Don't let the user zoom into the molecular level\n        }\n        if (bitmapDisplayed.getBitmap() == null) {\n            return;\n        }\n\n        float cx = getWidth() / 2F;\n        float cy = getHeight() / 2F;\n\n        suppMatrix.postScale(rate, rate, cx, cy);\n        setImageMatrix(getImageViewMatrix());\n    }\n\n    protected void zoomOut(float rate) {\n        if (bitmapDisplayed.getBitmap() == null) {\n            return;\n        }\n\n        float cx = getWidth() / 2F;\n        float cy = getHeight() / 2F;\n\n        // Zoom out to at most 1x\n        Matrix tmp = new Matrix(suppMatrix);\n        tmp.postScale(1F / rate, 1F / rate, cx, cy);\n\n        if (getScale(tmp) < 1F) {\n            suppMatrix.setScale(1F, 1F, cx, cy);\n        } else {\n            suppMatrix.postScale(1F / rate, 1F / rate, cx, cy);\n        }\n        setImageMatrix(getImageViewMatrix());\n        center();\n    }\n\n    protected void postTranslate(float dx, float dy) {\n        suppMatrix.postTranslate(dx, dy);\n    }\n\n    protected void panBy(float dx, float dy) {\n        postTranslate(dx, dy);\n        setImageMatrix(getImageViewMatrix());\n    }\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/Log.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.crop;\n\npublic class Log {\n\n    private static final String TAG = \"android-crop\";\n\n    public static void e(String msg) {\n        android.util.Log.e(TAG, msg);\n    }\n\n    public static void e(String msg, Throwable e) {\n        android.util.Log.e(TAG, msg, e);\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/MonitoredActivity.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.crop;\n\nimport android.os.Bundle;\n\nimport java.util.ArrayList;\n\nimport androidx.appcompat.app.AppCompatActivity;\n\n/*\n * Modified from original in AOSP.\n */\npublic abstract class MonitoredActivity extends AppCompatActivity {\n\n    private final ArrayList<LifeCycleListener> listeners = new ArrayList<LifeCycleListener>();\n\n    public static interface LifeCycleListener {\n        public void onActivityCreated(MonitoredActivity activity);\n\n        public void onActivityDestroyed(MonitoredActivity activity);\n\n        public void onActivityStarted(MonitoredActivity activity);\n\n        public void onActivityStopped(MonitoredActivity activity);\n    }\n\n    public static class LifeCycleAdapter implements LifeCycleListener {\n        public void onActivityCreated(MonitoredActivity activity) {\n        }\n\n        public void onActivityDestroyed(MonitoredActivity activity) {\n        }\n\n        public void onActivityStarted(MonitoredActivity activity) {\n        }\n\n        public void onActivityStopped(MonitoredActivity activity) {\n        }\n    }\n\n    public void addLifeCycleListener(LifeCycleListener listener) {\n        if (listeners.contains(listener)) return;\n        listeners.add(listener);\n    }\n\n    public void removeLifeCycleListener(LifeCycleListener listener) {\n        listeners.remove(listener);\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        for (LifeCycleListener listener : listeners) {\n            listener.onActivityCreated(this);\n        }\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        for (LifeCycleListener listener : listeners) {\n            listener.onActivityDestroyed(this);\n        }\n    }\n\n    @Override\n    protected void onStart() {\n        super.onStart();\n        for (LifeCycleListener listener : listeners) {\n            listener.onActivityStarted(this);\n        }\n    }\n\n    @Override\n    protected void onStop() {\n        super.onStop();\n        for (LifeCycleListener listener : listeners) {\n            listener.onActivityStopped(this);\n        }\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/crop/RotateBitmap.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.crop;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Matrix;\n\n/*\n * Modified from original in AOSP.\n */\npublic class RotateBitmap {\n\n    private Bitmap bitmap;\n    private int rotation;\n\n    public RotateBitmap(Bitmap bitmap, int rotation) {\n        this.bitmap = bitmap;\n        this.rotation = rotation % 360;\n    }\n\n    public void setRotation(int rotation) {\n        this.rotation = rotation;\n    }\n\n    public int getRotation() {\n        return rotation;\n    }\n\n    public Bitmap getBitmap() {\n        return bitmap;\n    }\n\n    public void setBitmap(Bitmap bitmap) {\n        this.bitmap = bitmap;\n    }\n\n    public Matrix getRotateMatrix() {\n        // By default this is an identity matrix\n        Matrix matrix = new Matrix();\n        if (bitmap != null && rotation != 0) {\n            // We want to do the rotation at origin, but since the bounding\n            // rectangle will be changed after rotation, so the delta values\n            // are based on old & new width/height respectively.\n            int cx = bitmap.getWidth() / 2;\n            int cy = bitmap.getHeight() / 2;\n            matrix.preTranslate(-cx, -cy);\n            matrix.postRotate(rotation);\n            matrix.postTranslate(getWidth() / 2, getHeight() / 2);\n        }\n        return matrix;\n    }\n\n    public boolean isOrientationChanged() {\n        return (rotation / 90) % 2 != 0;\n    }\n\n    public int getHeight() {\n        if (bitmap == null) return 0;\n        if (isOrientationChanged()) {\n            return bitmap.getWidth();\n        } else {\n            return bitmap.getHeight();\n        }\n    }\n\n    public int getWidth() {\n        if (bitmap == null) return 0;\n        if (isOrientationChanged()) {\n            return bitmap.getHeight();\n        } else {\n            return bitmap.getWidth();\n        }\n    }\n\n    public void recycle() {\n        if (bitmap != null) {\n            bitmap.recycle();\n            bitmap = null;\n        }\n    }\n}\n\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/entities/Photo.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.entities;\n\n/**\n * Created by Eric on 16/9/12.\n */\n\npublic class Photo {\n\n    public String path;\n\n    public boolean showCamera = false;\n\n    public Photo(String path) {\n        this.path = path;\n        this.showCamera = false;\n    }\n\n    public Photo(boolean showCamera) {\n        this.showCamera = showCamera;\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/internal/ImageMedia.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.internal;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.provider.MediaStore;\n\nimport com.liuguangqiang.ipicker.entities.Photo;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * A helper for querying all images from sd card.\n * <p>\n * Created by Eric on 16/9/12.\n */\npublic class ImageMedia {\n\n    /**\n     * Return a collection of image path.\n     *\n     * @param context\n     * @return\n     */\n    public static List<Photo> queryAll(Context context) {\n        List<Photo> photos = new ArrayList<>();\n\n        // which image properties are we querying\n        String[] projection = new String[]{\n                MediaStore.Images.Media._ID,\n                MediaStore.Images.Media.BUCKET_DISPLAY_NAME,\n                MediaStore.Images.Media.DATE_TAKEN,\n                MediaStore.Images.Media.DATA,\n        };\n\n        Uri images = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;\n        Cursor cur = MediaStore.Images.Media.query(\n                context.getContentResolver(),\n                images,                                             // Uri\n                projection,                                         // Which columns to return\n                null,                                               // Which rows to return (all rows)\n                null,                                               // Selection arguments (none)\n                MediaStore.Images.Media.DATE_TAKEN + \" DESC\"        // Ordering\n        );\n\n        if (cur.moveToFirst()) {\n            int dataColumn = cur.getColumnIndex(\n                    MediaStore.Images.Media.DATA);\n            do {\n                // Get the field values\n                photos.add(new Photo(cur.getString(dataColumn)));\n            } while (cur.moveToNext());\n        }\n\n        return photos;\n    }\n\n    /**\n     * Return the absolutely path of a uri.\n     *\n     * @param context\n     * @param uri\n     * @return\n     */\n    public static String getFilePath(Context context, Uri uri) {\n        String[] filePathColumn = {MediaStore.Images.Media.DATA};\n        Cursor cursor = context.getContentResolver().query(uri, filePathColumn, null, null, null);\n        cursor.moveToFirst();\n        int columnIndex = cursor.getColumnIndex(filePathColumn[0]);\n        String filePath = cursor.getString(columnIndex);\n        cursor.close();\n        return filePath;\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/internal/Logger.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.internal;\n\nimport android.util.Log;\n\n/**\n * Created by Eric on 16/9/12.\n */\n\npublic class Logger {\n\n    private static final String TAG = \"IPicker\";\n\n    public static void i(String msg) {\n        Log.i(TAG, msg);\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/java/com/liuguangqiang/ipicker/widgets/SquareRelativeLayout.java",
    "content": "/*\n * Copyright 2016 Eric Liu\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.liuguangqiang.ipicker.widgets;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.os.Build;\nimport android.util.AttributeSet;\nimport android.widget.RelativeLayout;\n\n/**\n * Created by Eric on 16/9/12.\n */\n\npublic class SquareRelativeLayout extends RelativeLayout {\n\n    public SquareRelativeLayout(Context context) {\n        super(context);\n    }\n\n    public SquareRelativeLayout(Context context, AttributeSet attrs) {\n        super(context, attrs);\n    }\n\n    public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, widthMeasureSpec);\n    }\n\n}\n"
  },
  {
    "path": "library/src/main/res/drawable/crop_texture.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:src=\"@drawable/crop_tile\"\n    android:tileMode=\"repeat\" />"
  },
  {
    "path": "library/src/main/res/drawable/round_white.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n  ~  Copyright 2015 GoIn Inc. All rights reserved.\n  -->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <corners android:radius=\"300dp\" />\n\n    <solid android:color=\"@android:color/white\" />\n\n    <size\n        android:width=\"5dp\"\n        android:height=\"5dp\" />\n\n</shape>"
  },
  {
    "path": "library/src/main/res/layout/activity_ipicker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/layout_container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\"\n    android:orientation=\"vertical\">\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_photos\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</RelativeLayout>"
  },
  {
    "path": "library/src/main/res/layout/activity_ipicker_crop.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <com.liuguangqiang.ipicker.crop.CropImageView\n        android:id=\"@+id/ipicker_crop_image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@drawable/crop_texture\" />\n\n</LinearLayout>"
  },
  {
    "path": "library/src/main/res/layout/item_photo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.liuguangqiang.ipicker.widgets.SquareRelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"1dp\">\n\n    <ImageView\n        android:id=\"@+id/iv_photo\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerCrop\" />\n\n    <ImageView\n        android:id=\"@+id/iv_camera\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/placeholder\"\n        android:scaleType=\"center\"\n        android:src=\"@mipmap/ic_camera\"\n        android:visibility=\"gone\" />\n\n    <ImageView\n        android:id=\"@+id/iv_checkbox\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentRight=\"true\"\n        android:layout_margin=\"6dp\"\n        android:background=\"@mipmap/ic_checkbox\" />\n\n</com.liuguangqiang.ipicker.widgets.SquareRelativeLayout>"
  },
  {
    "path": "library/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n\n    <attr name=\"IPicker_CropImageStyle\" format=\"reference\" />\n\n    <declare-styleable name=\"IPicker_CropImageView\">\n        <attr name=\"ipicker_highlightColor\" format=\"reference|color\" />\n        <attr name=\"ipicker_showThirds\" format=\"boolean\" />\n        <attr name=\"ipicker_showCircle\" format=\"boolean\" />\n        <attr name=\"ipicker_showHandles\">\n            <enum name=\"changing\" value=\"0\" />\n            <enum name=\"always\" value=\"1\" />\n            <enum name=\"never\" value=\"2\" />\n        </attr>\n    </declare-styleable>\n\n</resources>"
  },
  {
    "path": "library/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <color name=\"color_primary\">#f45d64</color>\n    <color name=\"color_primary_dark\">#c34a51</color>\n\n    <color name=\"placeholder\">#666666</color>\n    <color name=\"background\">#3d4049</color>\n    <color name=\"white\">#ffffff</color>\n\n    <color name=\"crop__button_bar\">#f3f3f3</color>\n    <color name=\"crop__button_text\">#666666</color>\n    <color name=\"crop__selector_pressed\">#1a000000</color>\n    <color name=\"crop__selector_focused\">#77000000</color>\n\n</resources>"
  },
  {
    "path": "library/src/main/res/values/dimens.xml",
    "content": "<?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",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"title_act_picker\">All Pictures</string>\n    <string name=\"action_done\">DONE</string>\n\n    <string name=\"btn_setting\">Go Setting</string>\n    <string name=\"no_permission_camera\">Sorry, No permission to take pictures.</string>\n    <string name=\"no_permission_gallery\">Sorry, No permission to access photos on your device.</string>\n    <string name=\"format_max_size\">Only %1$d photos can be selected.</string>\n\n    <!--crop-->\n    <string name=\"crop__title\">Clipping Picture</string>\n    <string name=\"crop__saving\">Saving picture…</string>\n    <string name=\"crop__wait\">Please wait…</string>\n    <string name=\"crop__pick_error\">No image sources available</string>\n    <string name=\"crop__done\">DONE</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">CANCEL</string>\n\n</resources>\n"
  },
  {
    "path": "library/src/main/res/values-zh/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"title_act_picker\">选择照片</string>\n    <string name=\"action_done\">完成</string>\n\n    <string name=\"btn_setting\">设置</string>\n    <string name=\"no_permission_camera\">对不起, 没有拍照的权限。</string>\n    <string name=\"no_permission_gallery\">对不起, 没有访问照片的权限。</string>\n    <string name=\"format_max_size\">最多只能选%1$d张图片哦。</string>\n\n    <string name=\"crop__title\">裁剪图片</string>\n    <string name=\"crop__done\">完成</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">取消</string>\n\n</resources>\n"
  },
  {
    "path": "library/src/test/java/com/liuguangqiang/ipicker/ExampleUnitTest.java",
    "content": "package com.liuguangqiang.ipicker;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() throws Exception {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':library'\n"
  }
]