[
  {
    "path": ".gitignore",
    "content": "\n/local.properties\n/.idea\nbuild/\n.gradle\n.DS_Store\n*.iml"
  },
  {
    "path": ".publishing/sonatype.gradle",
    "content": "configurations {\n    archives {\n        extendsFrom configurations.default\n    }\n}\n\nsigning {\n    required { has(\"release\") && gradle.taskGraph.hasTask(\"uploadArchives\") }\n    sign configurations.archives\n}\n\nuploadArchives {\n    configuration = configurations.archives\n\n    repositories {\n        mavenDeployer {\n            beforeDeployment {\n                MavenDeployment deployment -> signing.signPom(deployment)\n            }\n\n            repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2') {\n                authentication(userName: sonatypeUsername, password: sonatypePassword)\n            }\n\n            pom.project {\n                name 'Android Crop'\n                packaging 'aar'\n                description 'An Android library that provides an image cropping Activity'\n                url 'https://github.com/jdamcd/android-crop'\n\n                scm {\n                    url 'scm:git@github.com:jdamcd/android-crop.git'\n                    connection 'scm:git@github.com:jdamcd/android-crop.git'\n                    developerConnection 'scm:git@github.com:jdamcd/android-crop.git'\n                }\n\n                licenses {\n                    license {\n                        name 'The Apache Software License, Version 2.0'\n                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n                    }\n                }\n\n                organization {\n                    name 'SoundCloud'\n                    url 'http://developers.soundcloud.com'\n                }\n\n                developers {\n                    developer {\n                        id 'jdamcd'\n                        name 'Jamie McDonald'\n                        email 'mcdonald@soundcloud.com'\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\nsudo: false\n\nandroid:\n  components:\n    - build-tools-23.0.1\n    - android-23\n    - extra-android-support\n    - extra-android-m2repository\n\nscript:\n - ./gradlew clean build\n  "
  },
  {
    "path": "CHANGELOG.md",
    "content": "## Next\n* Fix max size crash when input cannot be decoded\n* Translations: German, Chinese (simplified & traditional)\n\n## 1.0.1\n\n* Support image picker helper from Fragments\n* Restore support for SDK level 10\n* Fix translucent status bar set via app theme\n* Fix wrong result code when crop results in IOException\n* Fix image \"twitching\" on zoom out to max bounds\n* Translations: Italian, Turkish, Catalan, Swedish\n\n## 1.0.0\n\n* Improved builder interface: `Crop.of(in, out).start(activity)`\n* Material styling\n* Drop support for SDK level 9\n* Start crop from support Fragment\n* Fix max size\n* Fix issue cropping images from Google Drive\n* Optional circle crop guide\n* Optional custom request code\n* Translations: French, Korean, Chinese, Spanish, Japanese, Arabic, Portuguese, Indonesian, Russian\n\n## 0.9.10\n\n* Fix bug on some devices where image was displayed with 0 size\n\n## 0.9.9\n\n* Downscale source images that are too big to load\n* Optional always show crop handles\n* Fix shading outside crop area on some API levels\n"
  },
  {
    "path": "README.md",
    "content": "> I guess people are just cropping out all the sadness\n\nAn Android library project that provides a simple image cropping `Activity`, based on code from AOSP.\n\n**Deprecated!** This project is not maintained. If it doesn't meet your needs as is, consider creating a fork or picking from these [alternatives](https://android-arsenal.com/tag/45).\n\n[![maven central](https://img.shields.io/badge/maven%20central-1.0.1-brightgreen.svg)](http://search.maven.org/#artifactdetails%7Ccom.soundcloud.android%7Candroid-crop%7C1.0.1%7Caar.asc)\n[![changelog](https://img.shields.io/badge/changelog-1.0.1-lightgrey.svg)](CHANGELOG.md)\n\n## Features\n\n* Gradle build & AAR\n* Modern UI\n* Backwards compatible to SDK 10\n* Simple builder for configuration\n* Example project\n\n## Usage\n\nFirst, declare `CropImageActivity` in your manifest file:\n\n```xml\n<activity android:name=\"com.soundcloud.android.crop.CropImageActivity\" />\n```\n\n#### Crop\n\n```java\nCrop.of(inputUri, outputUri).asSquare().start(activity)\n```\n\nListen for the result of the crop (see example project if you want to do some error handling):\n\n```java\n@Override\nprotected void onActivityResult(int requestCode, int resultCode, Intent result) {\n    if (requestCode == Crop.REQUEST_CROP && resultCode == RESULT_OK) {\n        doSomethingWithCroppedImage(outputUri);\n    }\n}\n```\n    \nSome attributes are provided to customise the crop screen. See the example project [theme](https://github.com/jdamcd/android-crop/blob/master/example/src/main/res/values/theme.xml).\n\n#### Pick\n\nThe library provides a utility method to start an image picker:\n\n```java\nCrop.pickImage(activity)\n```\n\n#### Dependency\n\nThe AAR is published on Maven Central:\n\n```groovy\ncompile 'com.soundcloud.android:android-crop:1.0.1@aar'\n```\n\n## How does it look?\n\n![android-crop screenshot](screenshot.png)\n\n## License\n\nThis project is based on the [AOSP](https://source.android.com) camera image cropper via [android-cropimage](https://github.com/lvillani/android-cropimage).\n\n    Copyright 2016 SoundCloud\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    repositories {\n        mavenCentral()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:2.1.0'\n    }\n}\n\nallprojects {\n    group = 'com.soundcloud.android'\n    version = project.VERSION\n\n    repositories {\n        mavenCentral()\n    }\n}\n"
  },
  {
    "path": "example/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\narchivesBaseName = 'android-crop-example'\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion '23.0.1'\n\n    defaultConfig {\n        minSdkVersion 10\n        targetSdkVersion 22\n        versionCode Integer.parseInt(project.VERSION_CODE)\n        versionName project.VERSION\n    }\n}\n\ndependencies {\n    compile project(':lib')\n}\n"
  },
  {
    "path": "example/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.soundcloud.android.crop.example\" >\n\n    <application\n        android:label=\"@string/app_name\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:allowBackup=\"true\"\n        android:theme=\"@style/CustomTheme\">\n        <activity\n            android:name=\"com.soundcloud.android.crop.example.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity android:name=\"com.soundcloud.android.crop.CropImageActivity\" />\n    </application>\n    \n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n\n</manifest>\n"
  },
  {
    "path": "example/src/main/java/com/soundcloud/android/crop/example/MainActivity.java",
    "content": "package com.soundcloud.android.crop.example;\n\nimport com.soundcloud.android.crop.Crop;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.widget.ImageView;\nimport android.widget.Toast;\n\nimport java.io.File;\n\npublic class MainActivity extends Activity {\n\n    private ImageView resultView;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_main);\n        resultView = (ImageView) findViewById(R.id.result_image);\n    }\n\n    @Override\n    public boolean onCreateOptionsMenu(Menu menu) {\n        getMenuInflater().inflate(R.menu.activity_main, menu);\n        return super.onCreateOptionsMenu(menu);\n    }\n\n    @Override\n    public boolean onOptionsItemSelected(MenuItem item) {\n        if (item.getItemId() == R.id.action_select) {\n            resultView.setImageDrawable(null);\n            Crop.pickImage(this);\n            return true;\n        }\n        return super.onOptionsItemSelected(item);\n    }\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent result) {\n        if (requestCode == Crop.REQUEST_PICK && resultCode == RESULT_OK) {\n            beginCrop(result.getData());\n        } else if (requestCode == Crop.REQUEST_CROP) {\n            handleCrop(resultCode, result);\n        }\n    }\n\n    private void beginCrop(Uri source) {\n        Uri destination = Uri.fromFile(new File(getCacheDir(), \"cropped\"));\n        Crop.of(source, destination).asSquare().start(this);\n    }\n\n    private void handleCrop(int resultCode, Intent result) {\n        if (resultCode == RESULT_OK) {\n            resultView.setImageURI(Crop.getOutput(result));\n        } else if (resultCode == Crop.RESULT_ERROR) {\n            Toast.makeText(this, Crop.getError(result).getMessage(), Toast.LENGTH_SHORT).show();\n        }\n    }\n\n}\n"
  },
  {
    "path": "example/src/main/res/drawable/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/tile\"\n    android:tileMode=\"repeat\" />"
  },
  {
    "path": "example/src/main/res/layout/activity_main.xml",
    "content": "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.soundcloud.android.crop.example.MainActivity\">\n\n    <ImageView\n        android:id=\"@+id/result_image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerInside\"\n        android:background=\"@drawable/texture\" />\n\n</FrameLayout>\n"
  },
  {
    "path": "example/src/main/res/menu/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:id=\"@+id/action_select\"\n        android:title=\"@string/action_select\"\n        android:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "example/src/main/res/values/colors.xml",
    "content": "<resources>\n\n    <color name=\"highlight\">#f3f3f3</color>\n\n</resources>"
  },
  {
    "path": "example/src/main/res/values/strings.xml",
    "content": "<resources>\n\n    <string name=\"app_name\">Crop Demo</string>\n    <string name=\"action_select\">Pick</string>\n\n</resources>\n"
  },
  {
    "path": "example/src/main/res/values/theme.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <style tools:ignore=\"NewApi\" name=\"CustomTheme\" parent=\"android:Theme.DeviceDefault\">\n        <item name=\"cropImageStyle\">@style/Widget.CropImageView</item>\n    </style>\n\n    <style name=\"Widget.CropImageView\" parent=\"android:Widget\">\n        <item name=\"showThirds\">true</item>\n        <item name=\"showCircle\">false</item>\n        <item name=\"showHandles\">always</item>\n        <item name=\"highlightColor\">@color/highlight</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri May 27 11:24:52 EDT 2016\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.10-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "VERSION=1.0.1\nVERSION_CODE=1\n\nsigning.keyId=63A46540\nsigning.secretKeyRingFile=\nsigning.password=\n\nsonatypeUsername=jdamcd\nsonatypePassword=\n"
  },
  {
    "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# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\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\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\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\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\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "lib/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'maven'\napply plugin: 'signing'\n//apply from: '../.publishing/sonatype.gradle'\n\narchivesBaseName = 'android-crop'\n\nandroid {\n    compileSdkVersion 23\n    buildToolsVersion '23.0.1'\n\n    defaultConfig {\n        minSdkVersion 10\n        targetSdkVersion 22\n\n        testApplicationId 'com.soundcloud.android.crop.test'\n        testInstrumentationRunner 'android.test.InstrumentationTestRunner'\n    }\n}\n\ndependencies {\n    compile 'com.android.support:support-annotations:23.0.1'\n    compile 'com.android.support:support-v4:23.0.1'\n    androidTestCompile 'com.squareup:fest-android:1.0.7'\n    androidTestCompile 'com.android.support:support-v4:23.0.1'\n    androidTestCompile 'org.mockito:mockito-core:1.9.5'\n    androidTestCompile 'com.google.dexmaker:dexmaker:1.0'\n    androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'\n}\n\n"
  },
  {
    "path": "lib/src/androidTest/java/com/soundcloud/android/crop/BaseTestCase.java",
    "content": "package com.soundcloud.android.crop;\n\nimport android.test.InstrumentationTestCase;\n\npublic class BaseTestCase extends InstrumentationTestCase {\n\n    @Override\n    public void setUp() throws Exception {\n        super.setUp();\n        // Work around dexmaker issue when running tests on Android 4.3\n        System.setProperty(\"dexmaker.dexcache\",\n                getInstrumentation().getTargetContext().getCacheDir().getPath());\n    }\n\n}\n"
  },
  {
    "path": "lib/src/androidTest/java/com/soundcloud/android/crop/CropBuilderTest.java",
    "content": "package com.soundcloud.android.crop;\n\nimport static org.fest.assertions.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport org.fest.assertions.api.ANDROID;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.provider.MediaStore;\n\npublic class CropBuilderTest extends BaseTestCase {\n\n    private Activity activity;\n    private Crop builder;\n\n    @Override\n    public void setUp() throws Exception {\n        super.setUp();\n        activity = mock(Activity.class);\n        when(activity.getPackageName()).thenReturn(\"com.example\");\n\n        builder = Crop.of(Uri.parse(\"image:input\"), Uri.parse(\"image:output\"));\n    }\n\n    public void testInputUriSetAsData() {\n        ANDROID.assertThat(builder.getIntent(activity)).hasData(\"image:input\");\n    }\n\n    public void testOutputUriSetAsExtra() {\n        Intent intent = builder.getIntent(activity);\n        Uri output = intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);\n\n        assertThat(output.toString()).isEqualTo(\"image:output\");\n    }\n\n    public void testAspectRatioSetAsExtras() {\n        builder.withAspect(16, 10);\n\n        Intent intent = builder.getIntent(activity);\n\n        assertThat(intent.getIntExtra(\"aspect_x\", 0)).isEqualTo(16);\n        assertThat(intent.getIntExtra(\"aspect_y\", 0)).isEqualTo(10);\n    }\n\n    public void testFixedAspectRatioSetAsExtras() {\n        builder.asSquare();\n\n        Intent intent = builder.getIntent(activity);\n\n        assertThat(intent.getIntExtra(\"aspect_x\", 0)).isEqualTo(1);\n        assertThat(intent.getIntExtra(\"aspect_y\", 0)).isEqualTo(1);\n    }\n\n    public void testMaxSizeSetAsExtras() {\n        builder.withMaxSize(400, 300);\n\n        Intent intent = builder.getIntent(activity);\n\n        assertThat(intent.getIntExtra(\"max_x\", 0)).isEqualTo(400);\n        assertThat(intent.getIntExtra(\"max_y\", 0)).isEqualTo(300);\n    }\n\n    public void testBuildsIntentWithMultipleOptions() {\n        builder.asSquare().withMaxSize(200, 200);\n\n        Intent intent = builder.getIntent(activity);\n\n        assertThat(intent.getIntExtra(\"aspect_x\", 0)).isEqualTo(1);\n        assertThat(intent.getIntExtra(\"aspect_y\", 0)).isEqualTo(1);\n        assertThat(intent.getIntExtra(\"max_x\", 0)).isEqualTo(200);\n        assertThat(intent.getIntExtra(\"max_y\", 0)).isEqualTo(200);\n    }\n\n    public void testAsPngSetAsExtras() {\n        builder.asPng(true);\n\n        Intent intent = builder.getIntent(activity);\n\n        assertThat(intent.getBooleanExtra(\"as_png\", false)).isEqualTo(true);\n    }\n}\n"
  },
  {
    "path": "lib/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.soundcloud.android.crop\" />\n"
  },
  {
    "path": "lib/src/main/java/com/soundcloud/android/crop/Crop.java",
    "content": "package com.soundcloud.android.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\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    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     * @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 from a support library Fragment\n     *\n     * @param context  Context\n     * @param fragment Fragment to receive result\n     */\n    public void start(Context context, android.support.v4.app.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     * 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    public void start(Context context, android.support.v4.app.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 a support library Fragment\n     *\n     * @param context  Context\n     * @param fragment Fragment to receive result\n     */\n    public static void pickImage(Context context, android.support.v4.app.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    /**\n     * Pick image from a support library 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    public static void pickImage(Context context, android.support.v4.app.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": "lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java",
    "content": "/*\n * Copyright (C) 2007 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.soundcloud.android.crop;\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.View;\nimport android.view.Window;\nimport android.view.WindowManager;\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 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\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.crop__activity_crop);\n\n        imageView = (CropImageView) findViewById(R.id.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        findViewById(R.id.btn_cancel).setOnClickListener(new View.OnClickListener() {\n            public void onClick(View v) {\n                setResult(RESULT_CANCELED);\n                finish();\n            }\n        });\n\n        findViewById(R.id.btn_done).setOnClickListener(new View.OnClickListener() {\n            public void onClick(View v) {\n                onSaveClicked();\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": "lib/src/main/java/com/soundcloud/android/crop/CropImageView.java",
    "content": "package com.soundcloud.android.crop;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.support.annotation.NonNull;\nimport android.util.AttributeSet;\nimport android.view.MotionEvent;\n\nimport java.util.ArrayList;\n\npublic class CropImageView extends ImageViewTouchBase {\n\n    ArrayList<HighlightView> highlightViews = new ArrayList<HighlightView>();\n    HighlightView motionHighlightView;\n    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": "lib/src/main/java/com/soundcloud/android/crop/CropUtil.java",
    "content": "/*\n * Copyright (C) 2009 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.soundcloud.android.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.support.annotation.Nullable;\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\n/*\n * Modified from original in AOSP.\n */\nclass 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": "lib/src/main/java/com/soundcloud/android/crop/HighlightView.java",
    "content": "/*\n * Copyright (C) 2007 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.soundcloud.android.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\n/*\n * Modified from version 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 */\nclass 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    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.cropImageStyle, outValue, true);\n        TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);\n        try {\n            showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);\n            showCircle = attributes.getBoolean(R.styleable.CropImageView_showCircle, false);\n            highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor,\n                    DEFAULT_HIGHLIGHT_COLOR);\n            handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_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": "lib/src/main/java/com/soundcloud/android/crop/ImageViewTouchBase.java",
    "content": "/*\n * Copyright (C) 2009 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.soundcloud.android.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;\nimport android.widget.ImageView;\n\n/*\n * Modified from original in AOSP.\n */\nabstract class ImageViewTouchBase extends ImageView {\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    protected 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(ImageView.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    protected float getScale(Matrix matrix) {\n        return getValue(matrix, Matrix.MSCALE_X);\n    }\n\n    protected 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": "lib/src/main/java/com/soundcloud/android/crop/Log.java",
    "content": "package com.soundcloud.android.crop;\n\nclass 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": "lib/src/main/java/com/soundcloud/android/crop/MonitoredActivity.java",
    "content": "/*\n * Copyright (C) 2009 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.soundcloud.android.crop;\n\nimport android.app.Activity;\nimport android.os.Bundle;\n\nimport java.util.ArrayList;\n\n/*\n * Modified from original in AOSP.\n */\nabstract class MonitoredActivity extends Activity {\n\n    private final ArrayList<LifeCycleListener> listeners = new ArrayList<LifeCycleListener>();\n\n    public static interface LifeCycleListener {\n        public void onActivityCreated(MonitoredActivity activity);\n        public void onActivityDestroyed(MonitoredActivity activity);\n        public void onActivityStarted(MonitoredActivity activity);\n        public void onActivityStopped(MonitoredActivity activity);\n    }\n\n    public static class LifeCycleAdapter implements LifeCycleListener {\n        public void onActivityCreated(MonitoredActivity activity) {}\n        public void onActivityDestroyed(MonitoredActivity activity) {}\n        public void onActivityStarted(MonitoredActivity activity) {}\n        public void onActivityStopped(MonitoredActivity activity) {}\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": "lib/src/main/java/com/soundcloud/android/crop/RotateBitmap.java",
    "content": "/*\n * Copyright (C) 2009 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.soundcloud.android.crop;\n\nimport android.graphics.Bitmap;\nimport android.graphics.Matrix;\n\n/*\n * Modified from original in AOSP.\n */\nclass 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": "lib/src/main/res/drawable/crop__selectable_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:exitFadeDuration=\"@android:integer/config_mediumAnimTime\">\n\n    <item android:state_pressed=\"true\">\n        <shape>\n            <solid android:color=\"@color/crop__selector_pressed\" />\n        </shape>\n    </item>\n\n    <item android:state_focused=\"true\">\n        <shape>\n            <solid android:color=\"@color/crop__selector_focused\" />\n        </shape>\n    </item>\n\n    <item android:drawable=\"@android:color/transparent\" />\n\n</selector>"
  },
  {
    "path": "lib/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": "lib/src/main/res/drawable-v21/crop__selectable_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:color=\"@color/crop__selector_pressed\">\n  <item android:drawable=\"@color/crop__button_bar\" />\n</ripple>\n"
  },
  {
    "path": "lib/src/main/res/layout/crop__activity_crop.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<RelativeLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <include\n        android:id=\"@+id/done_cancel_bar\"\n        layout=\"@layout/crop__layout_done_cancel\" />\n\n    <com.soundcloud.android.crop.CropImageView\n        android:id=\"@+id/crop_image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@drawable/crop__texture\"\n        android:layout_below=\"@id/done_cancel_bar\" />\n\n</RelativeLayout>"
  },
  {
    "path": "lib/src/main/res/layout/crop__layout_done_cancel.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    style=\"@style/Crop.DoneCancelBar\">\n\n    <FrameLayout\n        android:id=\"@+id/btn_cancel\"\n        style=\"@style/Crop.ActionButton\">\n        <TextView style=\"@style/Crop.ActionButtonText.Cancel\" />\n    </FrameLayout>\n\n    <FrameLayout\n        android:id=\"@+id/btn_done\"\n        style=\"@style/Crop.ActionButton\">\n        <TextView style=\"@style/Crop.ActionButtonText.Done\" />\n    </FrameLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "lib/src/main/res/values/attrs.xml",
    "content": "<resources>\n\n    <attr name=\"cropImageStyle\" format=\"reference\" />\n\n    <declare-styleable name=\"CropImageView\">\n        <attr name=\"highlightColor\" format=\"reference|color\" />\n        <attr name=\"showThirds\" format=\"boolean\" />\n        <attr name=\"showCircle\" format=\"boolean\" />\n        <attr name=\"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": "lib/src/main/res/values/colors.xml",
    "content": "<resources>\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": "lib/src/main/res/values/dimens.xml",
    "content": "<resources>\n\n    <dimen name=\"crop__bar_height\">56dp</dimen>\n\n</resources>"
  },
  {
    "path": "lib/src/main/res/values/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\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\n    <string name=\"crop__done\">DONE</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">CANCEL</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values/styles.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <style name=\"Crop\"></style>\n\n    <style name=\"Crop.DoneCancelBar\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">@dimen/crop__bar_height</item>\n        <item name=\"android:orientation\">horizontal</item>\n        <item name=\"android:divider\">@drawable/crop__divider</item>\n        <item name=\"android:showDividers\" tools:ignore=\"NewApi\">middle</item>\n        <item name=\"android:dividerPadding\" tools:ignore=\"NewApi\">12dp</item>\n        <item name=\"android:background\">@color/crop__button_bar</item>\n    </style>\n\n    <style name=\"Crop.ActionButton\">\n        <item name=\"android:layout_width\">0dp</item>\n        <item name=\"android:layout_height\">match_parent</item>\n        <item name=\"android:layout_weight\">1</item>\n        <item name=\"android:background\">@drawable/crop__selectable_background</item>\n    </style>\n\n    <style name=\"Crop.ActionButtonText\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_gravity\">center</item>\n        <item name=\"android:gravity\">center_vertical</item>\n        <item name=\"android:paddingRight\">20dp</item> <!-- Offsets left drawable -->\n        <item name=\"android:drawablePadding\">8dp</item>\n        <item name=\"android:textColor\">@color/crop__button_text</item>\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"android:textSize\">13sp</item>\n    </style>\n\n    <style name=\"Crop.ActionButtonText.Done\">\n        <item name=\"android:drawableLeft\">@drawable/crop__ic_done</item>\n        <item name=\"android:text\">@string/crop__done</item>\n    </style>\n\n    <style name=\"Crop.ActionButtonText.Cancel\">\n        <item name=\"android:drawableLeft\">@drawable/crop__ic_cancel</item>\n        <item name=\"android:text\">@string/crop__cancel</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "lib/src/main/res/values-ar/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">جارى حفظ الصورة …</string>\n    <string name=\"crop__wait\">رجاء الأنتظار …</string>\n    <string name=\"crop__pick_error\">الصورة غير متاحة</string>\n\n    <string name=\"crop__done\">تم</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">الغاء</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-ca/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Guardant imatge…</string>\n    <string name=\"crop__wait\">Si us plau esperi…</string>\n    <string name=\"crop__pick_error\">No hi ha imatges disponibles</string>\n\n    <string name=\"crop__done\">ACCEPTAR</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">CANCEL·LAR</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-de/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Bild speichern…</string>\n    <string name=\"crop__wait\">Bitte warten…</string>\n    <string name=\"crop__pick_error\">Keine Bildquellen verfügbar</string>\n\n    <string name=\"crop__done\">übernehmen</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">abbrechen</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-es/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Guardando imagen…</string>\n    <string name=\"crop__wait\">Por favor espere…</string>\n    <string name=\"crop__pick_error\">No hay imágenes disponibles</string>\n\n    <string name=\"crop__done\">ACEPTAR</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">CANCELAR</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-fa/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">در حال ذخیره سازی</string>\n    <string name=\"crop__wait\">لطفاً صبر کنید ...</string>\n    <string name=\"crop__pick_error\">تصویری در دسترس نیست</string>\n\n    <string name=\"crop__done\">تأیید</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">انصراف</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-fr/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Enregistrement de l\\'image…</string>\n    <string name=\"crop__wait\">Veuillez patienter…</string>\n    <string name=\"crop__pick_error\">Aucune image disponible</string>\n\n    <string name=\"crop__done\">ACCEPTER</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">ANNULER</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-in/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Menyimpan gambar…</string>\n    <string name=\"crop__wait\">Silakan tunggu…</string>\n    <string name=\"crop__pick_error\">Tidak ada sumber gambar yang tersedia</string>\n\n    <string name=\"crop__done\">SELESAI</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">BATAL</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-it/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Salvataggio immagine…</string>\n    <string name=\"crop__wait\">Attendere prego…</string>\n    <string name=\"crop__pick_error\">Nessuna immagine disponibile</string>\n\n    <string name=\"crop__done\">ACCETTA</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">ANNULLA</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-ja/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">保存中…</string>\n    <string name=\"crop__wait\">お待ちください…</string>\n    <string name=\"crop__pick_error\">画像が見つかりません</string>\n\n    <string name=\"crop__done\">決定</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">キャンセル</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-ko/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">사진을 저장중입니다…</string>\n    <string name=\"crop__wait\">잠시만 기다려주세요…</string>\n    <string name=\"crop__pick_error\">이미지가 존재하지 않습니다.</string>\n\n    <string name=\"crop__done\">확인</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">취소</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-land/dimens.xml",
    "content": "<resources>\n\n    <dimen name=\"crop__bar_height\">48dp</dimen>\n\n</resources>"
  },
  {
    "path": "lib/src/main/res/values-large/dimens.xml",
    "content": "<resources>\n\n    <dimen name=\"crop__bar_height\">64dp</dimen>\n\n</resources>"
  },
  {
    "path": "lib/src/main/res/values-pt/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Salvando imagem…</string>\n    <string name=\"crop__wait\">Por favor, aguarde…</string>\n    <string name=\"crop__pick_error\">Sem fontes de imagem disponíveis</string>\n\n    <string name=\"crop__done\">FINALIZADO</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">CANCELAR</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-ru/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Изображение сохраняется…</string>\n    <string name=\"crop__wait\">Пожалуйста, подождите…</string>\n    <string name=\"crop__pick_error\">Нет доступных изображений</string>\n\n    <string name=\"crop__done\">ГОТОВО</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">ОТМЕНА</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-sv/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Sparar bild…</string>\n    <string name=\"crop__wait\">Var god vänta…</string>\n    <string name=\"crop__pick_error\">Inga bildkällor tillgängliga</string>\n\n    <string name=\"crop__done\">KLAR</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">AVBRYT</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-tr/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">Fotoğraf kaydediliyor…</string>\n    <string name=\"crop__wait\">Lütfen bekleyin…</string>\n    <string name=\"crop__pick_error\">Fotoğraf bulunamadı</string>\n\n    <string name=\"crop__done\">TAMAM</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">ÇIKIŞ</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-v21/colors.xml",
    "content": "<resources>\n\n  <color name=\"crop__selector_pressed\">#aaaaaa</color>\n\n</resources>"
  },
  {
    "path": "lib/src/main/res/values-zh-rCN/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">正在保存照片…</string>\n    <string name=\"crop__wait\">请等待…</string>\n    <string name=\"crop__pick_error\">无效的图片</string>\n\n    <string name=\"crop__done\">完成</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">取消</string>\n\n</resources>\n"
  },
  {
    "path": "lib/src/main/res/values-zh-rTW/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <string name=\"crop__saving\">正在儲存相片…</string>\n    <string name=\"crop__wait\">請稍候…</string>\n    <string name=\"crop__pick_error\">沒有可用的圖片來源</string>\n\n    <string name=\"crop__done\">完成</string>\n    <string name=\"crop__cancel\" tools:ignore=\"ButtonCase\">取消</string>\n\n</resources>\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':lib', ':example'\n"
  }
]