[
  {
    "path": ".gitignore",
    "content": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\nout/\n\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n\n# Android Studio Navigation editor temp files\n.navigation/\n\n# Android Studio captures folder\ncaptures/\n\n# IntelliJ\n*.iml\n.idea/workspace.xml\n.idea/tasks.xml\n.idea/gradle.xml\n.idea/assetWizardSettings.xml\n.idea/dictionaries\n.idea/libraries\n.idea/caches\n.idea/\n# Keystore files\n# Uncomment the following line if you do not want to check your keystore files in.\n#*.jks\n\n# External native build folder generated in Android Studio 2.2 and later\n.externalNativeBuild\n\n# Google Services (e.g. APIs or Firebase)\ngoogle-services.json\n\n# Freeline\nfreeline.py\nfreeline/\nfreeline_project_description.json\n\n# fastlane\nfastlane/report.xml\nfastlane/Preview.html\nfastlane/screenshots\nfastlane/test_output\nfastlane/readme.md\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\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": "README.md",
    "content": "# LockDemo\n封装了：指纹识别、图形密码手势密码识别、aliOCR识别\n个人使用，可根据需求添加。\n\n  \n \n\n\n\n <div class='row'>\n<img src=\"https://github.com/wzx54321/LockDemo/blob/master/imgs/take1.gif\" title=\"指纹识别\" width=\"25%\" height=\"25%\"/>  \n<img src=\"https://github.com/wzx54321/LockDemo/blob/master/imgs/take2.gif\" title=\"图形密码手势密码识别\" width=\"25%\" height=\"25%\"/>  \n  </div>\n <div class='row'>\n <img src=\"https://github.com/wzx54321/LockDemo/blob/master/imgs/take0.gif\" title=\"aliOCR识别\" width=\"25%\" height=\"25%\"/>\n   \n   <img src=\"https://github.com/wzx54321/LockDemo/blob/master/imgs/take.gif\" title=\"aliOCR识别\" width=\"25%\" height=\"25%\"/>\n</div>\n\n\n### Duang！！！轻量级APP开发框架，刚出炉：https://github.com/wzx54321/XinFrameworkLib\n## 组件化框架demo请关注：https://github.com/wzx54321/XinframeworkSample   \n\n"
  },
  {
    "path": "aliocrlib/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "aliocrlib/README.MD",
    "content": "## 基于阿里OCR封装的身份证识别功能"
  },
  {
    "path": "aliocrlib/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 27\n\n\n\n    defaultConfig {\n        minSdkVersion 16\n        targetSdkVersion 27\n        versionCode 1\n        versionName \"1.0\"\n\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n\n    sourceSets {\n        main.java.srcDirs += 'src/main/base'\n        main.java.srcDirs += 'src/main/api9'\n        main.java.srcDirs += 'src/main/api14'\n        main.java.srcDirs += 'src/main/api21'\n        main.java.srcDirs += 'src/main/api23'\n\n    }\n\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    implementation 'com.android.support:appcompat-v7:27.1.1'\n    implementation 'com.android.support:design:27.1.1'\n    implementation 'com.squareup.retrofit2:retrofit:2.4.0'\n    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'\n    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'\n    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'\n    implementation files('libs/BASE64Encoder.jar')\n    implementation 'com.android.support.constraint:constraint-layout:1.1.2'\n}\n"
  },
  {
    "path": "aliocrlib/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "aliocrlib/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.lib.aliocr\"\n          xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>\n    <uses-permission android:name=\"android.permission.CAMERA\"/>\n\n    <uses-feature android:name=\"android.hardware.camera\"/>\n    <uses-feature\n        android:name=\"android.hardware.camera.autofocus\"\n        android:required=\"false\"/>\n\n    <application>\n        <activity android:name=\".view.OCRMainActivity\">\n        </activity>\n        <activity android:name=\".widget.crop.CropImageActivity\"/>\n\n    </application>\n\n</manifest>"
  },
  {
    "path": "aliocrlib/src/main/api14/cameraview/Camera1.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.annotation.SuppressLint;\nimport android.graphics.SurfaceTexture;\nimport android.hardware.Camera;\nimport android.os.Build;\nimport android.support.v4.util.SparseArrayCompat;\nimport android.view.SurfaceHolder;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.SortedSet;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n\n@SuppressWarnings(\"deprecation\")\nclass Camera1 extends CameraViewImpl {\n\n    private static final int INVALID_CAMERA_ID = -1;\n\n    private static final SparseArrayCompat<String> FLASH_MODES = new SparseArrayCompat<>();\n\n    static {\n        FLASH_MODES.put(Constants.FLASH_OFF, Camera.Parameters.FLASH_MODE_OFF);\n        FLASH_MODES.put(Constants.FLASH_ON, Camera.Parameters.FLASH_MODE_ON);\n        FLASH_MODES.put(Constants.FLASH_TORCH, Camera.Parameters.FLASH_MODE_TORCH);\n        FLASH_MODES.put(Constants.FLASH_AUTO, Camera.Parameters.FLASH_MODE_AUTO);\n        FLASH_MODES.put(Constants.FLASH_RED_EYE, Camera.Parameters.FLASH_MODE_RED_EYE);\n    }\n\n    private int mCameraId;\n\n    private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false);\n\n    Camera mCamera;\n\n    private Camera.Parameters mCameraParameters;\n\n    private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();\n\n    private final SizeMap mPreviewSizes = new SizeMap();\n\n    private final SizeMap mPictureSizes = new SizeMap();\n\n    private AspectRatio mAspectRatio;\n\n    private boolean mShowingPreview;\n\n    private boolean mAutoFocus;\n\n    private int mFacing;\n\n    private int mFlash;\n\n    private int mDisplayOrientation;\n\n    Camera1(Callback callback, PreviewImpl preview) {\n        super(callback, preview);\n        preview.setCallback(new PreviewImpl.Callback() {\n            @Override\n            public void onSurfaceChanged() {\n                if (mCamera != null) {\n                    setUpPreview();\n                    adjustCameraParameters();\n                }\n            }\n        });\n    }\n\n    @Override\n    boolean start() {\n        chooseCamera();\n        openCamera();\n        if (mPreview.isReady()) {\n            setUpPreview();\n        }\n        mShowingPreview = true;\n        mCamera.startPreview();\n        return true;\n    }\n\n    @Override\n    void stop() {\n        if (mCamera != null) {\n            mCamera.stopPreview();\n        }\n        mShowingPreview = false;\n        releaseCamera();\n    }\n\n    // Suppresses Camera#setPreviewTexture\n    @SuppressLint(\"NewApi\")\n    void setUpPreview() {\n        try {\n            if (mPreview.getOutputClass() == SurfaceHolder.class) {\n                final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;\n                if (needsToStopPreview) {\n                    mCamera.stopPreview();\n                }\n                mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());\n                if (needsToStopPreview) {\n                    mCamera.startPreview();\n                }\n            } else {\n                mCamera.setPreviewTexture((SurfaceTexture) mPreview.getSurfaceTexture());\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    boolean isCameraOpened() {\n        return mCamera != null;\n    }\n\n    @Override\n    void setFacing(int facing) {\n        if (mFacing == facing) {\n            return;\n        }\n        mFacing = facing;\n        if (isCameraOpened()) {\n            stop();\n            start();\n        }\n    }\n\n    @Override\n    int getFacing() {\n        return mFacing;\n    }\n\n    @Override\n    Set<AspectRatio> getSupportedAspectRatios() {\n        SizeMap idealAspectRatios = mPreviewSizes;\n        for (AspectRatio aspectRatio : idealAspectRatios.ratios()) {\n            if (mPictureSizes.sizes(aspectRatio) == null) {\n                idealAspectRatios.remove(aspectRatio);\n            }\n        }\n        return idealAspectRatios.ratios();\n    }\n\n    @Override\n    boolean setAspectRatio(AspectRatio ratio) {\n        if (mAspectRatio == null || !isCameraOpened()) {\n            // Handle this later when camera is opened\n            mAspectRatio = ratio;\n            return true;\n        } else if (!mAspectRatio.equals(ratio)) {\n            final Set<Size> sizes = mPreviewSizes.sizes(ratio);\n            if (sizes == null) {\n                throw new UnsupportedOperationException(ratio + \" is not supported\");\n            } else {\n                mAspectRatio = ratio;\n                adjustCameraParameters();\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    AspectRatio getAspectRatio() {\n        return mAspectRatio;\n    }\n\n    @Override\n    void setAutoFocus(boolean autoFocus) {\n        if (mAutoFocus == autoFocus) {\n            return;\n        }\n        if (setAutoFocusInternal(autoFocus)) {\n            mCamera.setParameters(mCameraParameters);\n        }\n    }\n\n    @Override\n    boolean getAutoFocus() {\n        if (!isCameraOpened()) {\n            return mAutoFocus;\n        }\n        String focusMode = mCameraParameters.getFocusMode();\n        return focusMode != null && focusMode.contains(\"continuous\");\n    }\n\n    @Override\n    void setFlash(int flash) {\n        if (flash == mFlash) {\n            return;\n        }\n        if (setFlashInternal(flash)) {\n            mCamera.setParameters(mCameraParameters);\n        }\n    }\n\n    @Override\n    int getFlash() {\n        return mFlash;\n    }\n\n    @Override\n    void takePicture() {\n        if (!isCameraOpened()) {\n            throw new IllegalStateException(\n                    \"Camera is not ready. Call start() before takePicture().\");\n        }\n        if (getAutoFocus()) {\n            mCamera.cancelAutoFocus();\n            mCamera.autoFocus(new Camera.AutoFocusCallback() {\n                @Override\n                public void onAutoFocus(boolean success, Camera camera) {\n                    takePictureInternal();\n                }\n            });\n        } else {\n            takePictureInternal();\n        }\n    }\n\n    void takePictureInternal() {\n        if (!isPictureCaptureInProgress.getAndSet(true)) {\n            mCamera.takePicture(null, null, null, new Camera.PictureCallback() {\n                @Override\n                public void onPictureTaken(byte[] data, Camera camera) {\n                    isPictureCaptureInProgress.set(false);\n                    mCallback.onPictureTaken(data);\n                    camera.cancelAutoFocus();\n                    camera.startPreview();\n                }\n            });\n        }\n    }\n\n    @Override\n    void setDisplayOrientation(int displayOrientation) {\n        if (mDisplayOrientation == displayOrientation) {\n            return;\n        }\n        mDisplayOrientation = displayOrientation;\n        if (isCameraOpened()) {\n            mCameraParameters.setRotation(calcCameraRotation(displayOrientation));\n            mCamera.setParameters(mCameraParameters);\n            final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;\n            if (needsToStopPreview) {\n                mCamera.stopPreview();\n            }\n            mCamera.setDisplayOrientation(calcDisplayOrientation(displayOrientation));\n            if (needsToStopPreview) {\n                mCamera.startPreview();\n            }\n        }\n    }\n\n    /**\n     * This rewrites {@link #mCameraId} and {@link #mCameraInfo}.\n     */\n    private void chooseCamera() {\n        for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {\n            Camera.getCameraInfo(i, mCameraInfo);\n            if (mCameraInfo.facing == mFacing) {\n                mCameraId = i;\n                return;\n            }\n        }\n        mCameraId = INVALID_CAMERA_ID;\n    }\n\n    private void openCamera() {\n        if (mCamera != null) {\n            releaseCamera();\n        }\n        mCamera = Camera.open(mCameraId);\n        mCameraParameters = mCamera.getParameters();\n        // Supported preview sizes\n        mPreviewSizes.clear();\n        for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {\n            mPreviewSizes.add(new Size(size.width, size.height));\n        }\n        // Supported picture sizes;\n        mPictureSizes.clear();\n        for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {\n            mPictureSizes.add(new Size(size.width, size.height));\n        }\n        // AspectRatio\n        if (mAspectRatio == null) {\n            mAspectRatio = Constants.DEFAULT_ASPECT_RATIO;\n        }\n        adjustCameraParameters();\n        mCamera.setDisplayOrientation(calcDisplayOrientation(mDisplayOrientation));\n        mCallback.onCameraOpened();\n    }\n\n    private AspectRatio chooseAspectRatio() {\n        AspectRatio r = null;\n        for (AspectRatio ratio : mPreviewSizes.ratios()) {\n            r = ratio;\n            if (ratio.equals(Constants.DEFAULT_ASPECT_RATIO)) {\n                return ratio;\n            }\n        }\n        return r;\n    }\n\n    void adjustCameraParameters() {\n        SortedSet<Size> sizes = mPreviewSizes.sizes(mAspectRatio);\n        if (sizes == null) { // Not supported\n            mAspectRatio = chooseAspectRatio();\n            sizes = mPreviewSizes.sizes(mAspectRatio);\n        }\n        Size size = chooseOptimalSize(sizes);\n\n        // Always re-apply camera parameters\n        // Largest picture size in this ratio\n        final Size pictureSize = mPictureSizes.sizes(mAspectRatio).last();\n        if (mShowingPreview) {\n            mCamera.stopPreview();\n        }\n        mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight());\n        mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());\n        mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation));\n        setAutoFocusInternal(mAutoFocus);\n        setFlashInternal(mFlash);\n        mCamera.setParameters(mCameraParameters);\n        if (mShowingPreview) {\n            mCamera.startPreview();\n        }\n    }\n\n    @SuppressWarnings(\"SuspiciousNameCombination\")\n    private Size chooseOptimalSize(SortedSet<Size> sizes) {\n        if (!mPreview.isReady()) { // Not yet laid out\n            return sizes.first(); // Return the smallest size\n        }\n        int desiredWidth;\n        int desiredHeight;\n        final int surfaceWidth = mPreview.getWidth();\n        final int surfaceHeight = mPreview.getHeight();\n        if (isLandscape(mDisplayOrientation)) {\n            desiredWidth = surfaceHeight;\n            desiredHeight = surfaceWidth;\n        } else {\n            desiredWidth = surfaceWidth;\n            desiredHeight = surfaceHeight;\n        }\n        Size result = null;\n        for (Size size : sizes) { // Iterate from small to large\n            if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {\n                return size;\n\n            }\n            result = size;\n        }\n        return result;\n    }\n\n    private void releaseCamera() {\n        if (mCamera != null) {\n            mCamera.release();\n            mCamera = null;\n            mCallback.onCameraClosed();\n        }\n    }\n\n    /**\n     * Calculate display orientation\n     * https://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)\n     *\n     * This calculation is used for orienting the preview\n     *\n     * Note: This is not the same calculation as the camera rotation\n     *\n     * @param screenOrientationDegrees Screen orientation in degrees\n     * @return Number of degrees required to rotate preview\n     */\n    private int calcDisplayOrientation(int screenOrientationDegrees) {\n        if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {\n            return (360 - (mCameraInfo.orientation + screenOrientationDegrees) % 360) % 360;\n        } else {  // back-facing\n            return (mCameraInfo.orientation - screenOrientationDegrees + 360) % 360;\n        }\n    }\n\n    /**\n     * Calculate camera rotation\n     *\n     * This calculation is applied to the output JPEG either via Exif Orientation tag\n     * or by actually transforming the bitmap. (Determined by vendor camera API implementation)\n     *\n     * Note: This is not the same calculation as the display orientation\n     *\n     * @param screenOrientationDegrees Screen orientation in degrees\n     * @return Number of degrees to rotate image in order for it to view correctly.\n     */\n    private int calcCameraRotation(int screenOrientationDegrees) {\n        if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {\n            return (mCameraInfo.orientation + screenOrientationDegrees) % 360;\n        } else {  // back-facing\n            final int landscapeFlip = isLandscape(screenOrientationDegrees) ? 180 : 0;\n            return (mCameraInfo.orientation + screenOrientationDegrees + landscapeFlip) % 360;\n        }\n    }\n\n    /**\n     * Test if the supplied orientation is in landscape.\n     *\n     * @param orientationDegrees Orientation in degrees (0,90,180,270)\n     * @return True if in landscape, false if portrait\n     */\n    private boolean isLandscape(int orientationDegrees) {\n        return (orientationDegrees == Constants.LANDSCAPE_90 ||\n                orientationDegrees == Constants.LANDSCAPE_270);\n    }\n\n    /**\n     * @return {@code true} if {@link #mCameraParameters} was modified.\n     */\n    private boolean setAutoFocusInternal(boolean autoFocus) {\n        mAutoFocus = autoFocus;\n        if (isCameraOpened()) {\n            final List<String> modes = mCameraParameters.getSupportedFocusModes();\n            if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {\n                mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);\n            } else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {\n                mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);\n            } else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {\n                mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);\n            } else {\n                mCameraParameters.setFocusMode(modes.get(0));\n            }\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * @return {@code true} if {@link #mCameraParameters} was modified.\n     */\n    private boolean setFlashInternal(int flash) {\n        if (isCameraOpened()) {\n            List<String> modes = mCameraParameters.getSupportedFlashModes();\n            String mode = FLASH_MODES.get(flash);\n            if (modes != null && modes.contains(mode)) {\n                mCameraParameters.setFlashMode(mode);\n                mFlash = flash;\n                return true;\n            }\n            String currentMode = FLASH_MODES.get(mFlash);\n            if (modes == null || !modes.contains(currentMode)) {\n                mCameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);\n                mFlash = Constants.FLASH_OFF;\n                return true;\n            }\n            return false;\n        } else {\n            mFlash = flash;\n            return false;\n        }\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/api14/cameraview/TextureViewPreview.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.graphics.Matrix;\nimport android.graphics.SurfaceTexture;\nimport android.view.Surface;\nimport android.view.TextureView;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.lib.aliocr.R;\n\n\n@TargetApi(14)\nclass TextureViewPreview extends PreviewImpl {\n\n    private final TextureView mTextureView;\n\n    private int mDisplayOrientation;\n\n    TextureViewPreview(Context context, ViewGroup parent) {\n        final View view = View.inflate(context, R.layout.texture_view, parent);\n        mTextureView = view.findViewById(R.id.texture_view);\n        mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {\n\n            @Override\n            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {\n                setSize(width, height);\n                configureTransform();\n                dispatchSurfaceChanged();\n            }\n\n            @Override\n            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {\n                setSize(width, height);\n                configureTransform();\n                dispatchSurfaceChanged();\n            }\n\n            @Override\n            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {\n                setSize(0, 0);\n                return true;\n            }\n\n            @Override\n            public void onSurfaceTextureUpdated(SurfaceTexture surface) {\n            }\n        });\n    }\n\n    // This method is called only from Camera2.\n    @TargetApi(15)\n    @Override\n    void setBufferSize(int width, int height) {\n        mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);\n    }\n\n    @Override\n    Surface getSurface() {\n        return new Surface(mTextureView.getSurfaceTexture());\n    }\n\n    @Override\n    SurfaceTexture getSurfaceTexture() {\n        return mTextureView.getSurfaceTexture();\n    }\n\n    @Override\n    View getView() {\n        return mTextureView;\n    }\n\n    @Override\n    Class getOutputClass() {\n        return SurfaceTexture.class;\n    }\n\n    @Override\n    void setDisplayOrientation(int displayOrientation) {\n        mDisplayOrientation = displayOrientation;\n        configureTransform();\n    }\n\n    @Override\n    boolean isReady() {\n        return mTextureView.getSurfaceTexture() != null;\n    }\n\n    /**\n     * Configures the transform matrix for TextureView based on {@link #mDisplayOrientation} and\n     * the surface size.\n     */\n    void configureTransform() {\n        Matrix matrix = new Matrix();\n        if (mDisplayOrientation % 180 == 90) {\n            final int width = getWidth();\n            final int height = getHeight();\n            // Rotate the camera preview when the screen is landscape.\n            matrix.setPolyToPoly(\n                    new float[]{\n                            0.f, 0.f, // top left\n                            width, 0.f, // top right\n                            0.f, height, // bottom left\n                            width, height, // bottom right\n                    }, 0,\n                    mDisplayOrientation == 90 ?\n                            // Clockwise\n                            new float[]{\n                                    0.f, height, // top left\n                                    0.f, 0.f, // top right\n                                    width, height, // bottom left\n                                    width, 0.f, // bottom right\n                            } : // mDisplayOrientation == 270\n                            // Counter-clockwise\n                            new float[]{\n                                    width, 0.f, // top left\n                                    width, height, // top right\n                                    0.f, 0.f, // bottom left\n                                    0.f, height, // bottom right\n                            }, 0,\n                    4);\n        } else if (mDisplayOrientation == 180) {\n            matrix.postRotate(180, getWidth() / 2, getHeight() / 2);\n        }\n        mTextureView.setTransform(matrix);\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/api21/cameraview/Camera2.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.graphics.ImageFormat;\nimport android.hardware.camera2.CameraAccessException;\nimport android.hardware.camera2.CameraCaptureSession;\nimport android.hardware.camera2.CameraCharacteristics;\nimport android.hardware.camera2.CameraDevice;\nimport android.hardware.camera2.CameraManager;\nimport android.hardware.camera2.CaptureRequest;\nimport android.hardware.camera2.CaptureResult;\nimport android.hardware.camera2.TotalCaptureResult;\nimport android.hardware.camera2.params.StreamConfigurationMap;\nimport android.media.Image;\nimport android.media.ImageReader;\nimport android.support.annotation.NonNull;\nimport android.util.Log;\nimport android.util.SparseIntArray;\nimport android.view.Surface;\n\nimport java.nio.ByteBuffer;\nimport java.util.Arrays;\nimport java.util.Set;\nimport java.util.SortedSet;\n\n@SuppressWarnings(\"MissingPermission\")\n@TargetApi(21)\nclass Camera2 extends CameraViewImpl {\n\n    private static final String TAG = \"Camera2\";\n\n    private static final SparseIntArray INTERNAL_FACINGS = new SparseIntArray();\n\n    static {\n        INTERNAL_FACINGS.put(Constants.FACING_BACK, CameraCharacteristics.LENS_FACING_BACK);\n        INTERNAL_FACINGS.put(Constants.FACING_FRONT, CameraCharacteristics.LENS_FACING_FRONT);\n    }\n\n    /**\n     * Max preview width that is guaranteed by Camera2 API\n     */\n    private static final int MAX_PREVIEW_WIDTH = 1920;\n\n    /**\n     * Max preview height that is guaranteed by Camera2 API\n     */\n    private static final int MAX_PREVIEW_HEIGHT = 1080;\n\n    private final CameraManager mCameraManager;\n\n    private final CameraDevice.StateCallback mCameraDeviceCallback\n            = new CameraDevice.StateCallback() {\n\n        @Override\n        public void onOpened(@NonNull CameraDevice camera) {\n            mCamera = camera;\n            mCallback.onCameraOpened();\n            startCaptureSession();\n        }\n\n        @Override\n        public void onClosed(@NonNull CameraDevice camera) {\n            mCallback.onCameraClosed();\n        }\n\n        @Override\n        public void onDisconnected(@NonNull CameraDevice camera) {\n            mCamera = null;\n        }\n\n        @Override\n        public void onError(@NonNull CameraDevice camera, int error) {\n            Log.e(TAG, \"onError: \" + camera.getId() + \" (\" + error + \")\");\n            mCamera = null;\n        }\n\n    };\n\n    private final CameraCaptureSession.StateCallback mSessionCallback\n            = new CameraCaptureSession.StateCallback() {\n\n        @Override\n        public void onConfigured(@NonNull CameraCaptureSession session) {\n            if (mCamera == null) {\n                return;\n            }\n            mCaptureSession = session;\n            updateAutoFocus();\n            updateFlash();\n            try {\n                mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),\n                        mCaptureCallback, null);\n            } catch (CameraAccessException e) {\n                Log.e(TAG, \"Failed to start camera preview because it couldn't access camera\", e);\n            } catch (IllegalStateException e) {\n                Log.e(TAG, \"Failed to start camera preview.\", e);\n            }\n        }\n\n        @Override\n        public void onConfigureFailed(@NonNull CameraCaptureSession session) {\n            Log.e(TAG, \"Failed to configure capture session.\");\n        }\n\n        @Override\n        public void onClosed(@NonNull CameraCaptureSession session) {\n            if (mCaptureSession != null && mCaptureSession.equals(session)) {\n                mCaptureSession = null;\n            }\n        }\n\n    };\n\n    PictureCaptureCallback mCaptureCallback = new PictureCaptureCallback() {\n\n        @Override\n        public void onPrecaptureRequired() {\n            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,\n                    CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);\n            setState(STATE_PRECAPTURE);\n            try {\n                mCaptureSession.capture(mPreviewRequestBuilder.build(), this, null);\n                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,\n                        CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);\n            } catch (CameraAccessException e) {\n                Log.e(TAG, \"Failed to run precapture sequence.\", e);\n            }\n        }\n\n        @Override\n        public void onReady() {\n            captureStillPicture();\n        }\n\n    };\n\n    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener\n            = new ImageReader.OnImageAvailableListener() {\n\n        @Override\n        public void onImageAvailable(ImageReader reader) {\n            try (Image image = reader.acquireNextImage()) {\n                Image.Plane[] planes = image.getPlanes();\n                if (planes.length > 0) {\n                    ByteBuffer buffer = planes[0].getBuffer();\n                    byte[] data = new byte[buffer.remaining()];\n                    buffer.get(data);\n                    mCallback.onPictureTaken(data);\n                }\n            }\n        }\n\n    };\n\n\n    private String mCameraId;\n\n    private CameraCharacteristics mCameraCharacteristics;\n\n    CameraDevice mCamera;\n\n    CameraCaptureSession mCaptureSession;\n\n    CaptureRequest.Builder mPreviewRequestBuilder;\n\n    private ImageReader mImageReader;\n\n    private final SizeMap mPreviewSizes = new SizeMap();\n\n    private final SizeMap mPictureSizes = new SizeMap();\n\n    private int mFacing;\n\n    private AspectRatio mAspectRatio = Constants.DEFAULT_ASPECT_RATIO;\n\n    private boolean mAutoFocus;\n\n    private int mFlash;\n\n    private int mDisplayOrientation;\n\n    Camera2(Callback callback, PreviewImpl preview, Context context) {\n        super(callback, preview);\n        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);\n        mPreview.setCallback(new PreviewImpl.Callback() {\n            @Override\n            public void onSurfaceChanged() {\n                startCaptureSession();\n            }\n        });\n    }\n\n    @Override\n    boolean start() {\n        if (!chooseCameraIdByFacing()) {\n            return false;\n        }\n        collectCameraInfo();\n        prepareImageReader();\n        startOpeningCamera();\n        return true;\n    }\n\n    @Override\n    void stop() {\n        if (mCaptureSession != null) {\n            mCaptureSession.close();\n            mCaptureSession = null;\n        }\n        if (mCamera != null) {\n            mCamera.close();\n            mCamera = null;\n        }\n        if (mImageReader != null) {\n            mImageReader.close();\n            mImageReader = null;\n        }\n    }\n\n    @Override\n    boolean isCameraOpened() {\n        return mCamera != null;\n    }\n\n    @Override\n    void setFacing(int facing) {\n        if (mFacing == facing) {\n            return;\n        }\n        mFacing = facing;\n        if (isCameraOpened()) {\n            stop();\n            start();\n        }\n    }\n\n    @Override\n    int getFacing() {\n        return mFacing;\n    }\n\n    @Override\n    Set<AspectRatio> getSupportedAspectRatios() {\n        return mPreviewSizes.ratios();\n    }\n\n    @Override\n    boolean setAspectRatio(AspectRatio ratio) {\n        if (ratio == null || ratio.equals(mAspectRatio) ||\n                !mPreviewSizes.ratios().contains(ratio)) {\n            // TODO: Better error handling\n            return false;\n        }\n        mAspectRatio = ratio;\n        prepareImageReader();\n        if (mCaptureSession != null) {\n            mCaptureSession.close();\n            mCaptureSession = null;\n            startCaptureSession();\n        }\n        return true;\n    }\n\n    @Override\n    AspectRatio getAspectRatio() {\n        return mAspectRatio;\n    }\n\n    @Override\n    void setAutoFocus(boolean autoFocus) {\n        if (mAutoFocus == autoFocus) {\n            return;\n        }\n        mAutoFocus = autoFocus;\n        if (mPreviewRequestBuilder != null) {\n            updateAutoFocus();\n            if (mCaptureSession != null) {\n                try {\n                    mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),\n                            mCaptureCallback, null);\n                } catch (CameraAccessException e) {\n                    mAutoFocus = !mAutoFocus; // Revert\n                }\n            }\n        }\n    }\n\n    @Override\n    boolean getAutoFocus() {\n        return mAutoFocus;\n    }\n\n    @Override\n    void setFlash(int flash) {\n        if (mFlash == flash) {\n            return;\n        }\n        int saved = mFlash;\n        mFlash = flash;\n        if (mPreviewRequestBuilder != null) {\n            updateFlash();\n            if (mCaptureSession != null) {\n                try {\n                    mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),\n                            mCaptureCallback, null);\n                } catch (CameraAccessException e) {\n                    mFlash = saved; // Revert\n                }\n            }\n        }\n    }\n\n    @Override\n    int getFlash() {\n        return mFlash;\n    }\n\n    @Override\n    void takePicture() {\n        if (mAutoFocus) {\n            lockFocus();\n        } else {\n            captureStillPicture();\n        }\n    }\n\n    @Override\n    void setDisplayOrientation(int displayOrientation) {\n        mDisplayOrientation = displayOrientation;\n        mPreview.setDisplayOrientation(mDisplayOrientation);\n    }\n\n    /**\n     * <p>Chooses a camera ID by the specified camera facing ({@link #mFacing}).</p>\n     * <p>This rewrites {@link #mCameraId}, {@link #mCameraCharacteristics}, and optionally\n     * {@link #mFacing}.</p>\n     */\n    private boolean chooseCameraIdByFacing() {\n        try {\n            int internalFacing = INTERNAL_FACINGS.get(mFacing);\n            final String[] ids = mCameraManager.getCameraIdList();\n            if (ids.length == 0) { // No camera\n                throw new RuntimeException(\"No camera available.\");\n            }\n            for (String id : ids) {\n                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);\n                Integer level = characteristics.get(\n                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);\n                if (level == null ||\n                        level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {\n                    continue;\n                }\n                Integer internal = characteristics.get(CameraCharacteristics.LENS_FACING);\n                if (internal == null) {\n                    throw new NullPointerException(\"Unexpected state: LENS_FACING null\");\n                }\n                if (internal == internalFacing) {\n                    mCameraId = id;\n                    mCameraCharacteristics = characteristics;\n                    return true;\n                }\n            }\n            // Not found\n            mCameraId = ids[0];\n            mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);\n            Integer level = mCameraCharacteristics.get(\n                    CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);\n            if (level == null ||\n                    level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {\n                return false;\n            }\n            Integer internal = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING);\n            if (internal == null) {\n                throw new NullPointerException(\"Unexpected state: LENS_FACING null\");\n            }\n            for (int i = 0, count = INTERNAL_FACINGS.size(); i < count; i++) {\n                if (INTERNAL_FACINGS.valueAt(i) == internal) {\n                    mFacing = INTERNAL_FACINGS.keyAt(i);\n                    return true;\n                }\n            }\n            // The operation can reach here when the only camera device is an external one.\n            // We treat it as facing back.\n            mFacing = Constants.FACING_BACK;\n            return true;\n        } catch (CameraAccessException e) {\n            throw new RuntimeException(\"Failed to get a list of camera devices\", e);\n        }\n    }\n\n    /**\n     * <p>Collects some information from {@link #mCameraCharacteristics}.</p>\n     * <p>This rewrites {@link #mPreviewSizes}, {@link #mPictureSizes}, and optionally,\n     * {@link #mAspectRatio}.</p>\n     */\n    private void collectCameraInfo() {\n        StreamConfigurationMap map = mCameraCharacteristics.get(\n                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);\n        if (map == null) {\n            throw new IllegalStateException(\"Failed to get configuration map: \" + mCameraId);\n        }\n        mPreviewSizes.clear();\n        for (android.util.Size size : map.getOutputSizes(mPreview.getOutputClass())) {\n            int width = size.getWidth();\n            int height = size.getHeight();\n            if (width <= MAX_PREVIEW_WIDTH && height <= MAX_PREVIEW_HEIGHT) {\n                mPreviewSizes.add(new Size(width, height));\n            }\n        }\n        mPictureSizes.clear();\n        collectPictureSizes(mPictureSizes, map);\n        for (AspectRatio ratio : mPreviewSizes.ratios()) {\n            if (!mPictureSizes.ratios().contains(ratio)) {\n                mPreviewSizes.remove(ratio);\n            }\n        }\n\n        if (!mPreviewSizes.ratios().contains(mAspectRatio)) {\n            mAspectRatio = mPreviewSizes.ratios().iterator().next();\n        }\n    }\n\n    protected void collectPictureSizes(SizeMap sizes, StreamConfigurationMap map) {\n        for (android.util.Size size : map.getOutputSizes(ImageFormat.JPEG)) {\n            mPictureSizes.add(new Size(size.getWidth(), size.getHeight()));\n        }\n    }\n\n    private void prepareImageReader() {\n        if (mImageReader != null) {\n            mImageReader.close();\n        }\n        Size largest = mPictureSizes.sizes(mAspectRatio).last();\n        mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),\n                ImageFormat.JPEG, /* maxImages */ 2);\n        mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null);\n    }\n\n    /**\n     * <p>Starts opening a camera device.</p>\n     * <p>The result will be processed in {@link #mCameraDeviceCallback}.</p>\n     */\n    private void startOpeningCamera() {\n        try {\n            mCameraManager.openCamera(mCameraId, mCameraDeviceCallback, null);\n        } catch (CameraAccessException e) {\n            throw new RuntimeException(\"Failed to open camera: \" + mCameraId, e);\n        }\n    }\n\n    /**\n     * <p>Starts a capture session for camera preview.</p>\n     * <p>This rewrites {@link #mPreviewRequestBuilder}.</p>\n     * <p>The result will be continuously processed in {@link #mSessionCallback}.</p>\n     */\n    void startCaptureSession() {\n        if (!isCameraOpened() || !mPreview.isReady() || mImageReader == null) {\n            return;\n        }\n        Size previewSize = chooseOptimalSize();\n        mPreview.setBufferSize(previewSize.getWidth(), previewSize.getHeight());\n        Surface surface = mPreview.getSurface();\n        try {\n            mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);\n            mPreviewRequestBuilder.addTarget(surface);\n            mCamera.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),\n                    mSessionCallback, null);\n        } catch (CameraAccessException e) {\n            throw new RuntimeException(\"Failed to start camera session\");\n        }\n    }\n\n    /**\n     * Chooses the optimal preview size based on {@link #mPreviewSizes} and the surface size.\n     *\n     * @return The picked size for camera preview.\n     */\n    private Size chooseOptimalSize() {\n        int surfaceLonger, surfaceShorter;\n        final int surfaceWidth = mPreview.getWidth();\n        final int surfaceHeight = mPreview.getHeight();\n        if (surfaceWidth < surfaceHeight) {\n            surfaceLonger = surfaceHeight;\n            surfaceShorter = surfaceWidth;\n        } else {\n            surfaceLonger = surfaceWidth;\n            surfaceShorter = surfaceHeight;\n        }\n        SortedSet<Size> candidates = mPreviewSizes.sizes(mAspectRatio);\n\n        // Pick the smallest of those big enough\n        for (Size size : candidates) {\n            if (size.getWidth() >= surfaceLonger && size.getHeight() >= surfaceShorter) {\n                return size;\n            }\n        }\n        // If no size is big enough, pick the largest one.\n        return candidates.last();\n    }\n\n    /**\n     * Updates the internal state of auto-focus to {@link #mAutoFocus}.\n     */\n    void updateAutoFocus() {\n        if (mAutoFocus) {\n            int[] modes = mCameraCharacteristics.get(\n                    CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);\n            // Auto focus is not supported\n            if (modes == null || modes.length == 0 ||\n                    (modes.length == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF)) {\n                mAutoFocus = false;\n                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,\n                        CaptureRequest.CONTROL_AF_MODE_OFF);\n            } else {\n                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,\n                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);\n            }\n        } else {\n            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,\n                    CaptureRequest.CONTROL_AF_MODE_OFF);\n        }\n    }\n\n    /**\n     * Updates the internal state of flash to {@link #mFlash}.\n     */\n    void updateFlash() {\n        switch (mFlash) {\n            case Constants.FLASH_OFF:\n                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                        CaptureRequest.CONTROL_AE_MODE_ON);\n                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE,\n                        CaptureRequest.FLASH_MODE_OFF);\n                break;\n            case Constants.FLASH_ON:\n                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                        CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);\n                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE,\n                        CaptureRequest.FLASH_MODE_OFF);\n                break;\n            case Constants.FLASH_TORCH:\n                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                        CaptureRequest.CONTROL_AE_MODE_ON);\n                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE,\n                        CaptureRequest.FLASH_MODE_TORCH);\n                break;\n            case Constants.FLASH_AUTO:\n                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                        CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);\n                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE,\n                        CaptureRequest.FLASH_MODE_OFF);\n                break;\n            case Constants.FLASH_RED_EYE:\n                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                        CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE);\n                mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE,\n                        CaptureRequest.FLASH_MODE_OFF);\n                break;\n        }\n    }\n\n    /**\n     * Locks the focus as the first step for a still image capture.\n     */\n    private void lockFocus() {\n        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,\n                CaptureRequest.CONTROL_AF_TRIGGER_START);\n        try {\n            mCaptureCallback.setState(PictureCaptureCallback.STATE_LOCKING);\n            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null);\n        } catch (CameraAccessException e) {\n            Log.e(TAG, \"Failed to lock focus.\", e);\n        }\n    }\n\n    /**\n     * Captures a still picture.\n     */\n    void captureStillPicture() {\n        try {\n            CaptureRequest.Builder captureRequestBuilder = mCamera.createCaptureRequest(\n                    CameraDevice.TEMPLATE_STILL_CAPTURE);\n            captureRequestBuilder.addTarget(mImageReader.getSurface());\n            captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,\n                    mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_MODE));\n            switch (mFlash) {\n                case Constants.FLASH_OFF:\n                    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                            CaptureRequest.CONTROL_AE_MODE_ON);\n                    captureRequestBuilder.set(CaptureRequest.FLASH_MODE,\n                            CaptureRequest.FLASH_MODE_OFF);\n                    break;\n                case Constants.FLASH_ON:\n                    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                            CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);\n                    break;\n                case Constants.FLASH_TORCH:\n                    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                            CaptureRequest.CONTROL_AE_MODE_ON);\n                    captureRequestBuilder.set(CaptureRequest.FLASH_MODE,\n                            CaptureRequest.FLASH_MODE_TORCH);\n                    break;\n                case Constants.FLASH_AUTO:\n                    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                            CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);\n                    break;\n                case Constants.FLASH_RED_EYE:\n                    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,\n                            CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);\n                    break;\n            }\n            // Calculate JPEG orientation.\n            @SuppressWarnings(\"ConstantConditions\")\n            int sensorOrientation = mCameraCharacteristics.get(\n                    CameraCharacteristics.SENSOR_ORIENTATION);\n            captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION,\n                    (sensorOrientation +\n                            mDisplayOrientation * (mFacing == Constants.FACING_FRONT ? 1 : -1) +\n                            360) % 360);\n            // Stop preview and capture a still picture.\n            mCaptureSession.stopRepeating();\n            mCaptureSession.capture(captureRequestBuilder.build(),\n                    new CameraCaptureSession.CaptureCallback() {\n                        @Override\n                        public void onCaptureCompleted(@NonNull CameraCaptureSession session,\n                                @NonNull CaptureRequest request,\n                                @NonNull TotalCaptureResult result) {\n                            unlockFocus();\n                        }\n                    }, null);\n        } catch (CameraAccessException e) {\n            Log.e(TAG, \"Cannot capture a still picture.\", e);\n        }\n    }\n\n    /**\n     * Unlocks the auto-focus and restart camera preview. This is supposed to be called after\n     * capturing a still picture.\n     */\n    void unlockFocus() {\n        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,\n                CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);\n        try {\n            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, null);\n            updateAutoFocus();\n            updateFlash();\n            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,\n                    CaptureRequest.CONTROL_AF_TRIGGER_IDLE);\n            mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback,\n                    null);\n            mCaptureCallback.setState(PictureCaptureCallback.STATE_PREVIEW);\n        } catch (CameraAccessException e) {\n            Log.e(TAG, \"Failed to restart camera preview.\", e);\n        }\n    }\n\n    /**\n     * A {@link CameraCaptureSession.CaptureCallback} for capturing a still picture.\n     */\n    private static abstract class PictureCaptureCallback\n            extends CameraCaptureSession.CaptureCallback {\n\n        static final int STATE_PREVIEW = 0;\n        static final int STATE_LOCKING = 1;\n        static final int STATE_LOCKED = 2;\n        static final int STATE_PRECAPTURE = 3;\n        static final int STATE_WAITING = 4;\n        static final int STATE_CAPTURING = 5;\n\n        private int mState;\n\n        PictureCaptureCallback() {\n        }\n\n        void setState(int state) {\n            mState = state;\n        }\n\n        @Override\n        public void onCaptureProgressed(@NonNull CameraCaptureSession session,\n                @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {\n            process(partialResult);\n        }\n\n        @Override\n        public void onCaptureCompleted(@NonNull CameraCaptureSession session,\n                @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {\n            process(result);\n        }\n\n        private void process(@NonNull CaptureResult result) {\n            switch (mState) {\n                case STATE_LOCKING: {\n                    Integer af = result.get(CaptureResult.CONTROL_AF_STATE);\n                    if (af == null) {\n                        break;\n                    }\n                    if (af == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||\n                            af == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {\n                        Integer ae = result.get(CaptureResult.CONTROL_AE_STATE);\n                        if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) {\n                            setState(STATE_CAPTURING);\n                            onReady();\n                        } else {\n                            setState(STATE_LOCKED);\n                            onPrecaptureRequired();\n                        }\n                    }\n                    break;\n                }\n                case STATE_PRECAPTURE: {\n                    Integer ae = result.get(CaptureResult.CONTROL_AE_STATE);\n                    if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||\n                            ae == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED ||\n                            ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) {\n                        setState(STATE_WAITING);\n                    }\n                    break;\n                }\n                case STATE_WAITING: {\n                    Integer ae = result.get(CaptureResult.CONTROL_AE_STATE);\n                    if (ae == null || ae != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {\n                        setState(STATE_CAPTURING);\n                        onReady();\n                    }\n                    break;\n                }\n            }\n        }\n\n        /**\n         * Called when it is ready to take a still picture.\n         */\n        public abstract void onReady();\n\n        /**\n         * Called when it is necessary to run the precapture sequence.\n         */\n        public abstract void onPrecaptureRequired();\n\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/api23/cameraview/Camera2Api23.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.graphics.ImageFormat;\nimport android.hardware.camera2.params.StreamConfigurationMap;\n\n\n@TargetApi(23)\nclass Camera2Api23 extends Camera2 {\n\n    Camera2Api23(Callback callback, PreviewImpl preview, Context context) {\n        super(callback, preview, context);\n    }\n\n    @Override\n    protected void collectPictureSizes(SizeMap sizes, StreamConfigurationMap map) {\n        // Try to get hi-res output sizes\n        android.util.Size[] outputSizes = map.getHighResolutionOutputSizes(ImageFormat.JPEG);\n        if (outputSizes != null) {\n            for (android.util.Size size : map.getHighResolutionOutputSizes(ImageFormat.JPEG)) {\n                sizes.add(new Size(size.getWidth(), size.getHeight()));\n            }\n        }\n        if (sizes.isEmpty()) {\n            super.collectPictureSizes(sizes, map);\n        }\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/api9/cameraview/SurfaceViewPreview.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.content.Context;\nimport android.support.v4.view.ViewCompat;\nimport android.view.Surface;\nimport android.view.SurfaceHolder;\nimport android.view.SurfaceView;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.lib.aliocr.R;\n\nclass SurfaceViewPreview extends PreviewImpl {\n\n    final SurfaceView mSurfaceView;\n\n    SurfaceViewPreview(Context context, ViewGroup parent) {\n        final View view = View.inflate(context, R.layout.surface_view, parent);\n        mSurfaceView = view.findViewById(R.id.surface_view);\n        final SurfaceHolder holder = mSurfaceView.getHolder();\n        //noinspection deprecation\n        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);\n        holder.addCallback(new SurfaceHolder.Callback() {\n            @Override\n            public void surfaceCreated(SurfaceHolder h) {\n            }\n\n            @Override\n            public void surfaceChanged(SurfaceHolder h, int format, int width, int height) {\n                setSize(width, height);\n                if (!ViewCompat.isInLayout(mSurfaceView)) {\n                    dispatchSurfaceChanged();\n                }\n            }\n\n            @Override\n            public void surfaceDestroyed(SurfaceHolder h) {\n                setSize(0, 0);\n            }\n        });\n    }\n\n    @Override\n    Surface getSurface() {\n        return getSurfaceHolder().getSurface();\n    }\n\n    @Override\n    SurfaceHolder getSurfaceHolder() {\n        return mSurfaceView.getHolder();\n    }\n\n    @Override\n    View getView() {\n        return mSurfaceView;\n    }\n\n    @Override\n    Class getOutputClass() {\n        return SurfaceHolder.class;\n    }\n\n    @Override\n    void setDisplayOrientation(int displayOrientation) {\n    }\n\n    @Override\n    boolean isReady() {\n        return getWidth() != 0 && getHeight() != 0;\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/base/cameraview/AspectRatio.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.support.annotation.NonNull;\nimport android.support.v4.util.SparseArrayCompat;\n\n/**\n * Immutable class for describing proportional relationship between width and height.\n */\npublic class AspectRatio implements Comparable<AspectRatio>, Parcelable {\n\n    private final static SparseArrayCompat<SparseArrayCompat<AspectRatio>> sCache\n            = new SparseArrayCompat<>(16);\n\n    private final int mX;\n    private final int mY;\n\n    /**\n     * Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values.\n     * The values {@code x} and {@code} will be reduced by their greatest common divider.\n     *\n     * @param x The width\n     * @param y The height\n     * @return An instance of {@link AspectRatio}\n     */\n    public static AspectRatio of(int x, int y) {\n        int gcd = gcd(x, y);\n        x /= gcd;\n        y /= gcd;\n        SparseArrayCompat<AspectRatio> arrayX = sCache.get(x);\n        if (arrayX == null) {\n            AspectRatio ratio = new AspectRatio(x, y);\n            arrayX = new SparseArrayCompat<>();\n            arrayX.put(y, ratio);\n            sCache.put(x, arrayX);\n            return ratio;\n        } else {\n            AspectRatio ratio = arrayX.get(y);\n            if (ratio == null) {\n                ratio = new AspectRatio(x, y);\n                arrayX.put(y, ratio);\n            }\n            return ratio;\n        }\n    }\n\n    /**\n     * Parse an {@link AspectRatio} from a {@link String} formatted like \"4:3\".\n     *\n     * @param s The string representation of the aspect ratio\n     * @return The aspect ratio\n     * @throws IllegalArgumentException when the format is incorrect.\n     */\n    public static AspectRatio parse(String s) {\n        int position = s.indexOf(':');\n        if (position == -1) {\n            throw new IllegalArgumentException(\"Malformed aspect ratio: \" + s);\n        }\n        try {\n            int x = Integer.parseInt(s.substring(0, position));\n            int y = Integer.parseInt(s.substring(position + 1));\n            return AspectRatio.of(x, y);\n        } catch (NumberFormatException e) {\n            throw new IllegalArgumentException(\"Malformed aspect ratio: \" + s, e);\n        }\n    }\n\n    private AspectRatio(int x, int y) {\n        mX = x;\n        mY = y;\n    }\n\n    public int getX() {\n        return mX;\n    }\n\n    public int getY() {\n        return mY;\n    }\n\n    public boolean matches(Size size) {\n        int gcd = gcd(size.getWidth(), size.getHeight());\n        int x = size.getWidth() / gcd;\n        int y = size.getHeight() / gcd;\n        return mX == x && mY == y;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == null) {\n            return false;\n        }\n        if (this == o) {\n            return true;\n        }\n        if (o instanceof AspectRatio) {\n            AspectRatio ratio = (AspectRatio) o;\n            return mX == ratio.mX && mY == ratio.mY;\n        }\n        return false;\n    }\n\n    @Override\n    public String toString() {\n        return mX + \":\" + mY;\n    }\n\n    public float toFloat() {\n        return (float) mX / mY;\n    }\n\n    @Override\n    public int hashCode() {\n        // assuming most sizes are <2^16, doing a rotate will give us perfect hashing\n        return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2)));\n    }\n\n    @Override\n    public int compareTo(@NonNull AspectRatio another) {\n        if (equals(another)) {\n            return 0;\n        } else if (toFloat() - another.toFloat() > 0) {\n            return 1;\n        }\n        return -1;\n    }\n\n    /**\n     * @return The inverse of this {@link AspectRatio}.\n     */\n    public AspectRatio inverse() {\n        //noinspection SuspiciousNameCombination\n        return AspectRatio.of(mY, mX);\n    }\n\n    private static int gcd(int a, int b) {\n        while (b != 0) {\n            int c = b;\n            b = a % b;\n            a = c;\n        }\n        return a;\n    }\n\n    @Override\n    public int describeContents() {\n        return 0;\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n        dest.writeInt(mX);\n        dest.writeInt(mY);\n    }\n\n    public static final Parcelable.Creator<AspectRatio> CREATOR\n            = new Parcelable.Creator<AspectRatio>() {\n\n        @Override\n        public AspectRatio createFromParcel(Parcel source) {\n            int x = source.readInt();\n            int y = source.readInt();\n            return AspectRatio.of(x, y);\n        }\n\n        @Override\n        public AspectRatio[] newArray(int size) {\n            return new AspectRatio[size];\n        }\n    };\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/base/cameraview/CameraViewImpl.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.view.View;\n\nimport java.util.Set;\n\nabstract class CameraViewImpl {\n\n    protected final Callback mCallback;\n\n    protected final PreviewImpl mPreview;\n\n    CameraViewImpl(Callback callback, PreviewImpl preview) {\n        mCallback = callback;\n        mPreview = preview;\n    }\n\n    View getView() {\n        return mPreview.getView();\n    }\n\n    /**\n     * @return {@code true} if the implementation was able to start the camera session.\n     */\n    abstract boolean start();\n\n    abstract void stop();\n\n    abstract boolean isCameraOpened();\n\n    abstract void setFacing(int facing);\n\n    abstract int getFacing();\n\n    abstract Set<AspectRatio> getSupportedAspectRatios();\n\n    /**\n     * @return {@code true} if the aspect ratio was changed.\n     */\n    abstract boolean setAspectRatio(AspectRatio ratio);\n\n    abstract AspectRatio getAspectRatio();\n\n    abstract void setAutoFocus(boolean autoFocus);\n\n    abstract boolean getAutoFocus();\n\n    abstract void setFlash(int flash);\n\n    abstract int getFlash();\n\n    abstract void takePicture();\n\n    abstract void setDisplayOrientation(int displayOrientation);\n\n    interface Callback {\n\n        void onCameraOpened();\n\n        void onCameraClosed();\n\n        void onPictureTaken(byte[] data);\n\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/base/cameraview/Constants.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\n\npublic interface Constants {\n\n    AspectRatio DEFAULT_ASPECT_RATIO = AspectRatio.of(4, 3);\n\n    int FACING_BACK = 0;\n    int FACING_FRONT = 1;\n\n    int FLASH_OFF = 0;\n    int FLASH_ON = 1;\n    int FLASH_TORCH = 2;\n    int FLASH_AUTO = 3;\n    int FLASH_RED_EYE = 4;\n\n    int LANDSCAPE_90 = 90;\n    int LANDSCAPE_270 = 270;\n}\n"
  },
  {
    "path": "aliocrlib/src/main/base/cameraview/PreviewImpl.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.view.Surface;\nimport android.view.SurfaceHolder;\nimport android.view.View;\n\n\n/**\n * Encapsulates all the operations related to camera preview in a backward-compatible manner.\n */\nabstract class PreviewImpl {\n\n    interface Callback {\n        void onSurfaceChanged();\n    }\n\n    private Callback mCallback;\n\n    private int mWidth;\n\n    private int mHeight;\n\n    void setCallback(Callback callback) {\n        mCallback = callback;\n    }\n\n    abstract Surface getSurface();\n\n    abstract View getView();\n\n    abstract Class getOutputClass();\n\n    abstract void setDisplayOrientation(int displayOrientation);\n\n    abstract boolean isReady();\n\n    protected void dispatchSurfaceChanged() {\n        mCallback.onSurfaceChanged();\n    }\n\n    SurfaceHolder getSurfaceHolder() {\n        return null;\n    }\n\n    Object getSurfaceTexture() {\n        return null;\n    }\n\n    void setBufferSize(int width, int height) {\n    }\n\n    void setSize(int width, int height) {\n        mWidth = width;\n        mHeight = height;\n    }\n\n    int getWidth() {\n        return mWidth;\n    }\n\n    int getHeight() {\n        return mHeight;\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/base/cameraview/Size.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.support.annotation.NonNull;\n\n/**\n * Immutable class for describing width and height dimensions in pixels.\n */\npublic class Size implements Comparable<Size> {\n\n    private final int mWidth;\n    private final int mHeight;\n\n    /**\n     * Create a new immutable Size instance.\n     *\n     * @param width  The width of the size, in pixels\n     * @param height The height of the size, in pixels\n     */\n    public Size(int width, int height) {\n        mWidth = width;\n        mHeight = height;\n    }\n\n    public int getWidth() {\n        return mWidth;\n    }\n\n    public int getHeight() {\n        return mHeight;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (o == null) {\n            return false;\n        }\n        if (this == o) {\n            return true;\n        }\n        if (o instanceof Size) {\n            Size size = (Size) o;\n            return mWidth == size.mWidth && mHeight == size.mHeight;\n        }\n        return false;\n    }\n\n    @Override\n    public String toString() {\n        return mWidth + \"x\" + mHeight;\n    }\n\n    @Override\n    public int hashCode() {\n        // assuming most sizes are <2^16, doing a rotate will give us perfect hashing\n        return mHeight ^ ((mWidth << (Integer.SIZE / 2)) | (mWidth >>> (Integer.SIZE / 2)));\n    }\n\n    @Override\n    public int compareTo(@NonNull Size another) {\n        return mWidth * mHeight - another.mWidth * another.mHeight;\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/base/cameraview/SizeMap.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.support.v4.util.ArrayMap;\n\nimport java.util.Set;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\n\n/**\n * A collection class that automatically groups {@link Size}s by their {@link AspectRatio}s.\n */\nclass SizeMap {\n\n    private final ArrayMap<AspectRatio, SortedSet<Size>> mRatios = new ArrayMap<>();\n\n    /**\n     * Add a new {@link Size} to this collection.\n     *\n     * @param size The size to add.\n     * @return {@code true} if it is added, {@code false} if it already exists and is not added.\n     */\n    public boolean add(Size size) {\n        for (AspectRatio ratio : mRatios.keySet()) {\n            if (ratio.matches(size)) {\n                final SortedSet<Size> sizes = mRatios.get(ratio);\n                if (sizes.contains(size)) {\n                    return false;\n                } else {\n                    sizes.add(size);\n                    return true;\n                }\n            }\n        }\n        // None of the existing ratio matches the provided size; add a new key\n        SortedSet<Size> sizes = new TreeSet<>();\n        sizes.add(size);\n        mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes);\n        return true;\n    }\n\n    /**\n     * Removes the specified aspect ratio and all sizes associated with it.\n     *\n     * @param ratio The aspect ratio to be removed.\n     */\n    public void remove(AspectRatio ratio) {\n        mRatios.remove(ratio);\n    }\n\n    Set<AspectRatio> ratios() {\n        return mRatios.keySet();\n    }\n\n    SortedSet<Size> sizes(AspectRatio ratio) {\n        return mRatios.get(ratio);\n    }\n\n    void clear() {\n        mRatios.clear();\n    }\n\n    boolean isEmpty() {\n        return mRatios.isEmpty();\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/cameraview/CameraView.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.os.Build;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.support.annotation.IntDef;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.support.v4.os.ParcelableCompat;\nimport android.support.v4.os.ParcelableCompatCreatorCallbacks;\nimport android.support.v4.view.ViewCompat;\nimport android.util.AttributeSet;\nimport android.widget.FrameLayout;\n\nimport com.lib.aliocr.R;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.util.ArrayList;\nimport java.util.Set;\n\npublic class CameraView extends FrameLayout {\n\n    /** The camera device faces the opposite direction as the device's screen. */\n    public static final int FACING_BACK = Constants.FACING_BACK;\n\n    /** The camera device faces the same direction as the device's screen. */\n    public static final int FACING_FRONT = Constants.FACING_FRONT;\n\n    /** Direction the camera faces relative to device screen. */\n    @IntDef({FACING_BACK, FACING_FRONT})\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface Facing {\n    }\n\n    /** Flash will not be fired. */\n    public static final int FLASH_OFF = Constants.FLASH_OFF;\n\n    /** Flash will always be fired during snapshot. */\n    public static final int FLASH_ON = Constants.FLASH_ON;\n\n    /** Constant emission of light during preview, auto-focus and snapshot. */\n    public static final int FLASH_TORCH = Constants.FLASH_TORCH;\n\n    /** Flash will be fired automatically when required. */\n    public static final int FLASH_AUTO = Constants.FLASH_AUTO;\n\n    /** Flash will be fired in red-eye reduction mode. */\n    public static final int FLASH_RED_EYE = Constants.FLASH_RED_EYE;\n\n    /** The mode for for the camera device's flash control */\n    @IntDef({FLASH_OFF, FLASH_ON, FLASH_TORCH, FLASH_AUTO, FLASH_RED_EYE})\n    public @interface Flash {\n    }\n\n    CameraViewImpl mImpl;\n\n    private final CallbackBridge mCallbacks;\n\n    private boolean mAdjustViewBounds;\n\n    private final DisplayOrientationDetector mDisplayOrientationDetector;\n\n    public CameraView(Context context) {\n        this(context, null);\n    }\n\n    public CameraView(Context context, AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    @SuppressWarnings(\"WrongConstant\")\n    public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        if (isInEditMode()){\n            mCallbacks = null;\n            mDisplayOrientationDetector = null;\n            return;\n        }\n        // Internal setup\n        final PreviewImpl preview = createPreviewImpl(context);\n        mCallbacks = new CallbackBridge();\n        if (Build.VERSION.SDK_INT < 21) {\n            mImpl = new Camera1(mCallbacks, preview);\n        } else if (Build.VERSION.SDK_INT < 23) {\n            mImpl = new Camera2(mCallbacks, preview, context);\n        } else {\n            mImpl = new Camera2Api23(mCallbacks, preview, context);\n        }\n        // Attributes\n        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView, defStyleAttr,\n                R.style.Widget_CameraView);\n        mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false);\n        setFacing(a.getInt(R.styleable.CameraView_facing, FACING_BACK));\n        String aspectRatio = a.getString(R.styleable.CameraView_aspectRatio);\n        if (aspectRatio != null) {\n            setAspectRatio(AspectRatio.parse(aspectRatio));\n        } else {\n            setAspectRatio(Constants.DEFAULT_ASPECT_RATIO);\n        }\n        setAutoFocus(a.getBoolean(R.styleable.CameraView_autoFocus, true));\n        setFlash(a.getInt(R.styleable.CameraView_flash, Constants.FLASH_AUTO));\n        a.recycle();\n        // Display orientation detector\n        mDisplayOrientationDetector = new DisplayOrientationDetector(context) {\n            @Override\n            public void onDisplayOrientationChanged(int displayOrientation) {\n                mImpl.setDisplayOrientation(displayOrientation);\n            }\n        };\n    }\n\n    @NonNull\n    private PreviewImpl createPreviewImpl(Context context) {\n        PreviewImpl preview;\n        if (Build.VERSION.SDK_INT < 14) {\n            preview = new SurfaceViewPreview(context, this);\n        } else {\n            preview = new TextureViewPreview(context, this);\n        }\n        return preview;\n    }\n\n    @Override\n    protected void onAttachedToWindow() {\n        super.onAttachedToWindow();\n        if (!isInEditMode()) {\n            mDisplayOrientationDetector.enable(ViewCompat.getDisplay(this));\n        }\n    }\n\n    @Override\n    protected void onDetachedFromWindow() {\n        if (!isInEditMode()) {\n            mDisplayOrientationDetector.disable();\n        }\n        super.onDetachedFromWindow();\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        if (isInEditMode()){\n            super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n            return;\n        }\n        // Handle android:adjustViewBounds\n        if (mAdjustViewBounds) {\n            if (!isCameraOpened()) {\n                mCallbacks.reserveRequestLayoutOnOpen();\n                super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n                return;\n            }\n            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);\n            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);\n            if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {\n                final AspectRatio ratio = getAspectRatio();\n                assert ratio != null;\n                int height = (int) (MeasureSpec.getSize(widthMeasureSpec) * ratio.toFloat());\n                if (heightMode == MeasureSpec.AT_MOST) {\n                    height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));\n                }\n                super.onMeasure(widthMeasureSpec,\n                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));\n            } else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {\n                final AspectRatio ratio = getAspectRatio();\n                assert ratio != null;\n                int width = (int) (MeasureSpec.getSize(heightMeasureSpec) * ratio.toFloat());\n                if (widthMode == MeasureSpec.AT_MOST) {\n                    width = Math.min(width, MeasureSpec.getSize(widthMeasureSpec));\n                }\n                super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),\n                        heightMeasureSpec);\n            } else {\n                super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n            }\n        } else {\n            super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        }\n        // Measure the TextureView\n        int width = getMeasuredWidth();\n        int height = getMeasuredHeight();\n        AspectRatio ratio = getAspectRatio();\n        if (mDisplayOrientationDetector.getLastKnownDisplayOrientation() % 180 == 0) {\n            ratio = ratio.inverse();\n        }\n        assert ratio != null;\n        if (height < width * ratio.getY() / ratio.getX()) {\n            mImpl.getView().measure(\n                    MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),\n                    MeasureSpec.makeMeasureSpec(width * ratio.getY() / ratio.getX(),\n                            MeasureSpec.EXACTLY));\n        } else {\n            mImpl.getView().measure(\n                    MeasureSpec.makeMeasureSpec(height * ratio.getX() / ratio.getY(),\n                            MeasureSpec.EXACTLY),\n                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));\n        }\n    }\n\n    @Override\n    protected Parcelable onSaveInstanceState() {\n        SavedState state = new SavedState(super.onSaveInstanceState());\n        state.facing = getFacing();\n        state.ratio = getAspectRatio();\n        state.autoFocus = getAutoFocus();\n        state.flash = getFlash();\n        return state;\n    }\n\n    @Override\n    protected void onRestoreInstanceState(Parcelable state) {\n        if (!(state instanceof SavedState)) {\n            super.onRestoreInstanceState(state);\n            return;\n        }\n        SavedState ss = (SavedState) state;\n        super.onRestoreInstanceState(ss.getSuperState());\n        setFacing(ss.facing);\n        setAspectRatio(ss.ratio);\n        setAutoFocus(ss.autoFocus);\n        setFlash(ss.flash);\n    }\n\n    /**\n     * Open a camera device and start showing camera preview. This is typically called from\n     * {@link Activity#onResume()}.\n     */\n    public void start() {\n        if (!mImpl.start()) {\n            //store the state ,and restore this state after fall back o Camera1\n            Parcelable state=onSaveInstanceState();\n            // Camera2 uses legacy hardware layer; fall back to Camera1\n            mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext()));\n            onRestoreInstanceState(state);\n            mImpl.start();\n        }\n    }\n\n    /**\n     * Stop camera preview and close the device. This is typically called from\n     * {@link Activity#onPause()}.\n     */\n    public void stop() {\n        mImpl.stop();\n    }\n\n    /**\n     * @return {@code true} if the camera is opened.\n     */\n    public boolean isCameraOpened() {\n        return mImpl.isCameraOpened();\n    }\n\n    /**\n     * Add a new callback.\n     *\n     * @param callback The {@link Callback} to add.\n     * @see #removeCallback(Callback)\n     */\n    public void addCallback(@NonNull Callback callback) {\n        mCallbacks.add(callback);\n    }\n\n    /**\n     * Remove a callback.\n     *\n     * @param callback The {@link Callback} to remove.\n     * @see #addCallback(Callback)\n     */\n    public void removeCallback(@NonNull Callback callback) {\n        mCallbacks.remove(callback);\n    }\n\n    /**\n     * @param adjustViewBounds {@code true} if you want the CameraView to adjust its bounds to\n     *                         preserve the aspect ratio of camera.\n     * @see #getAdjustViewBounds()\n     */\n    public void setAdjustViewBounds(boolean adjustViewBounds) {\n        if (mAdjustViewBounds != adjustViewBounds) {\n            mAdjustViewBounds = adjustViewBounds;\n            requestLayout();\n        }\n    }\n\n    /**\n     * @return True when this CameraView is adjusting its bounds to preserve the aspect ratio of\n     * camera.\n     * @see #setAdjustViewBounds(boolean)\n     */\n    public boolean getAdjustViewBounds() {\n        return mAdjustViewBounds;\n    }\n\n    /**\n     * Chooses camera by the direction it faces.\n     *\n     * @param facing The camera facing. Must be either {@link #FACING_BACK} or\n     *               {@link #FACING_FRONT}.\n     */\n    public void setFacing(@Facing int facing) {\n        mImpl.setFacing(facing);\n    }\n\n    /**\n     * Gets the direction that the current camera faces.\n     *\n     * @return The camera facing.\n     */\n    @Facing\n    public int getFacing() {\n        //noinspection WrongConstant\n        return mImpl.getFacing();\n    }\n\n    /**\n     * Gets all the aspect ratios supported by the current camera.\n     */\n    public Set<AspectRatio> getSupportedAspectRatios() {\n        return mImpl.getSupportedAspectRatios();\n    }\n\n    /**\n     * Sets the aspect ratio of camera.\n     *\n     * @param ratio The {@link AspectRatio} to be set.\n     */\n    public void setAspectRatio(@NonNull AspectRatio ratio) {\n        if (mImpl.setAspectRatio(ratio)) {\n            requestLayout();\n        }\n    }\n\n    /**\n     * Gets the current aspect ratio of camera.\n     *\n     * @return The current {@link AspectRatio}. Can be {@code null} if no camera is opened yet.\n     */\n    @Nullable\n    public AspectRatio getAspectRatio() {\n        return mImpl.getAspectRatio();\n    }\n\n    /**\n     * Enables or disables the continuous auto-focus mode. When the current camera doesn't support\n     * auto-focus, calling this method will be ignored.\n     *\n     * @param autoFocus {@code true} to enable continuous auto-focus mode. {@code false} to\n     *                  disable it.\n     */\n    public void setAutoFocus(boolean autoFocus) {\n        mImpl.setAutoFocus(autoFocus);\n    }\n\n    /**\n     * Returns whether the continuous auto-focus mode is enabled.\n     *\n     * @return {@code true} if the continuous auto-focus mode is enabled. {@code false} if it is\n     * disabled, or if it is not supported by the current camera.\n     */\n    public boolean getAutoFocus() {\n        return mImpl.getAutoFocus();\n    }\n\n    /**\n     * Sets the flash mode.\n     *\n     * @param flash The desired flash mode.\n     */\n    public void setFlash(@Flash int flash) {\n        mImpl.setFlash(flash);\n    }\n\n    /**\n     * Gets the current flash mode.\n     *\n     * @return The current flash mode.\n     */\n    @Flash\n    public int getFlash() {\n        //noinspection WrongConstant\n        return mImpl.getFlash();\n    }\n\n    /**\n     * Take a picture. The result will be returned to\n     * {@link Callback#onPictureTaken(CameraView, byte[])}.\n     */\n    public void takePicture() {\n        mImpl.takePicture();\n    }\n\n    private class CallbackBridge implements CameraViewImpl.Callback {\n\n        private final ArrayList<Callback> mCallbacks = new ArrayList<>();\n\n        private boolean mRequestLayoutOnOpen;\n\n        CallbackBridge() {\n        }\n\n        public void add(Callback callback) {\n            mCallbacks.add(callback);\n        }\n\n        public void remove(Callback callback) {\n            mCallbacks.remove(callback);\n        }\n\n        @Override\n        public void onCameraOpened() {\n            if (mRequestLayoutOnOpen) {\n                mRequestLayoutOnOpen = false;\n                requestLayout();\n            }\n            for (Callback callback : mCallbacks) {\n                callback.onCameraOpened(CameraView.this);\n            }\n        }\n\n        @Override\n        public void onCameraClosed() {\n            for (Callback callback : mCallbacks) {\n                callback.onCameraClosed(CameraView.this);\n            }\n        }\n\n        @Override\n        public void onPictureTaken(byte[] data) {\n            for (Callback callback : mCallbacks) {\n                callback.onPictureTaken(CameraView.this, data);\n            }\n        }\n\n        public void reserveRequestLayoutOnOpen() {\n            mRequestLayoutOnOpen = true;\n        }\n    }\n\n    protected static class SavedState extends BaseSavedState {\n\n        @Facing\n        int facing;\n\n        AspectRatio ratio;\n\n        boolean autoFocus;\n\n        @Flash\n        int flash;\n\n        @SuppressWarnings(\"WrongConstant\")\n        public SavedState(Parcel source, ClassLoader loader) {\n            super(source);\n            facing = source.readInt();\n            ratio = source.readParcelable(loader);\n            autoFocus = source.readByte() != 0;\n            flash = source.readInt();\n        }\n\n        public SavedState(Parcelable superState) {\n            super(superState);\n        }\n\n        @Override\n        public void writeToParcel(Parcel out, int flags) {\n            super.writeToParcel(out, flags);\n            out.writeInt(facing);\n            out.writeParcelable(ratio, 0);\n            out.writeByte((byte) (autoFocus ? 1 : 0));\n            out.writeInt(flash);\n        }\n\n        public static final Parcelable.Creator<SavedState> CREATOR\n                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {\n\n            @Override\n            public SavedState createFromParcel(Parcel in, ClassLoader loader) {\n                return new SavedState(in, loader);\n            }\n\n            @Override\n            public SavedState[] newArray(int size) {\n                return new SavedState[size];\n            }\n\n        });\n\n    }\n\n    /**\n     * Callback for monitoring events about {@link CameraView}.\n     */\n    @SuppressWarnings(\"UnusedParameters\")\n    public abstract static class Callback {\n\n        /**\n         * Called when camera is opened.\n         *\n         * @param cameraView The associated {@link CameraView}.\n         */\n        public void onCameraOpened(CameraView cameraView) {\n        }\n\n        /**\n         * Called when camera is closed.\n         *\n         * @param cameraView The associated {@link CameraView}.\n         */\n        public void onCameraClosed(CameraView cameraView) {\n        }\n\n        /**\n         * Called when a picture is taken.\n         *\n         * @param cameraView The associated {@link CameraView}.\n         * @param data       JPEG data.\n         */\n        public void onPictureTaken(CameraView cameraView, byte[] data) {\n        }\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/cameraview/DisplayOrientationDetector.java",
    "content": "/*\n * Copyright (C) 2016 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 cameraview;\n\nimport android.content.Context;\nimport android.util.SparseIntArray;\nimport android.view.Display;\nimport android.view.OrientationEventListener;\nimport android.view.Surface;\n\n\n/**\n * Monitors the value returned from {@link Display#getRotation()}.\n */\nabstract class DisplayOrientationDetector {\n\n    private final OrientationEventListener mOrientationEventListener;\n\n    /** Mapping from Surface.Rotation_n to degrees. */\n    static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray();\n\n    static {\n        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0);\n        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90);\n        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180);\n        DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270);\n    }\n\n    Display mDisplay;\n\n    private int mLastKnownDisplayOrientation = 0;\n\n    public DisplayOrientationDetector(Context context) {\n        mOrientationEventListener = new OrientationEventListener(context) {\n\n            /** This is either Surface.Rotation_0, _90, _180, _270, or -1 (invalid). */\n            private int mLastKnownRotation = -1;\n\n            @Override\n            public void onOrientationChanged(int orientation) {\n                if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN ||\n                        mDisplay == null) {\n                    return;\n                }\n                final int rotation = mDisplay.getRotation();\n                if (mLastKnownRotation != rotation) {\n                    mLastKnownRotation = rotation;\n                    dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(rotation));\n                }\n            }\n        };\n    }\n\n    public void enable(Display display) {\n        mDisplay = display;\n        mOrientationEventListener.enable();\n        // Immediately dispatch the first callback\n        dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(display.getRotation()));\n    }\n\n    public void disable() {\n        mOrientationEventListener.disable();\n        mDisplay = null;\n    }\n\n    public int getLastKnownDisplayOrientation() {\n        return mLastKnownDisplayOrientation;\n    }\n\n    void dispatchOnDisplayOrientationChanged(int displayOrientation) {\n        mLastKnownDisplayOrientation = displayOrientation;\n        onDisplayOrientationChanged(displayOrientation);\n    }\n\n    /**\n     * Called when display orientation is changed.\n     *\n     * @param displayOrientation One of 0, 90, 180, and 270.\n     */\n    public abstract void onDisplayOrientationChanged(int displayOrientation);\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/api/HttpCustomConfig.java",
    "content": "package com.lib.aliocr.api;\n\nimport com.lib.aliocr.http.config.HttpConfig;\n\nimport okhttp3.OkHttpClient;\n\n/**\n * 作者：xin on 2018/6/7 0007 17:40\n * <p>\n * 邮箱：ittfxin@126.com\n * <P>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class HttpCustomConfig extends HttpConfig {\n    @Override\n    public OkHttpClient.Builder getCustomBuilder() {\n        // 根据个人需要配置\n        return null;\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/api/MyApiManager.java",
    "content": "package com.lib.aliocr.api;\n\n\nimport com.lib.aliocr.bean.RepOutput;\nimport com.lib.aliocr.common.Api;\n\nimport io.reactivex.Observable;\nimport okhttp3.RequestBody;\nimport retrofit2.http.Body;\nimport retrofit2.http.Headers;\nimport retrofit2.http.POST;\n\nimport static com.lib.aliocr.common.Api.path;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic interface MyApiManager {\n\n\n    @POST(path)\n    @Headers({\"Authorization: APPCODE \" + Api.APPCODE})\n    Observable<RepOutput> authCard(@Body RequestBody body);\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/bean/RepOutput.java",
    "content": "package com.lib.aliocr.bean;\n\nimport java.io.Serializable;\n\n/**\n * 作者：xin on 2018/7/9 0009 13:50\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class RepOutput implements Serializable {\n\n\n    private static final long serialVersionUID = 2694028643765036256L;\n\n    private String address;// 地址信息\n    private String config_str;// 配置信息，同输入configure\n    private faceRect face_rect;// 信息\n    private String name;// 姓名\n    private String nationality;//  民族\n    private String num;//  身份证号\n    private String sex;//  性别\n    private String birth;//  出生日期\n    private String start_date;// 有效期起始时间\n    private String end_date;// 有效期结束时间\n    private String issue;// 签发机关\n    private boolean success;// true表示成功，false表示失败\n\n    static class faceRect implements Serializable {\n        private static final long serialVersionUID = -7765350923476725941L;\n        private double angle;\n        private Center center;\n        private Size size;\n\n        public double getAngle() {\n            return angle;\n        }\n\n        public void setAngle(int angle) {\n            this.angle = angle;\n        }\n\n        public Center getCenter() {\n            return center;\n        }\n\n        public void setCenter(Center center) {\n            this.center = center;\n        }\n\n        public Size getSize() {\n            return size;\n        }\n\n        public void setSize(Size size) {\n            this.size = size;\n        }\n\n        static class Center implements Serializable {\n            private static final long serialVersionUID = 3232617470096971014L;\n            private double x;\n            private double y;\n\n            public double getX() {\n                return x;\n            }\n\n            public void setX(double x) {\n                this.x = x;\n            }\n\n            public double getY() {\n                return y;\n            }\n\n            public void setY(double y) {\n                this.y = y;\n            }\n\n            @Override\n            public String toString() {\n                return \"Center{\" +\n                        \"x=\" + x +\n                        \", y=\" + y +\n                        '}';\n            }\n        }\n\n        static class Size implements Serializable {\n            private static final long serialVersionUID = -3276942859242710227L;\n            private double height;\n            private double width;\n\n            public double getHeight() {\n                return height;\n            }\n\n            public void setHeight(double height) {\n                this.height = height;\n            }\n\n            public double getWidth() {\n                return width;\n            }\n\n            public void setWidth(double width) {\n                this.width = width;\n            }\n\n\n            @Override\n            public String toString() {\n                return \"Size{\" +\n                        \"height=\" + height +\n                        \", width=\" + width +\n                        '}';\n            }\n        }\n\n        @Override\n        public String toString() {\n            return \"faceRect{\" +\n                    \"angle=\" + angle +\n                    \", center=\" + center +\n                    \", size=\" + size +\n                    '}';\n        }\n    }\n\n\n    public String getAddress() {\n        return address;\n    }\n\n    public void setAddress(String address) {\n        this.address = address;\n    }\n\n    public String getConfig_str() {\n        return config_str;\n    }\n\n    public void setConfig_str(String config_str) {\n        this.config_str = config_str;\n    }\n\n    public faceRect getFace_rect() {\n        return face_rect;\n    }\n\n    public void setFace_rect(faceRect face_rect) {\n        this.face_rect = face_rect;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getNationality() {\n        return nationality;\n    }\n\n    public void setNationality(String nationality) {\n        this.nationality = nationality;\n    }\n\n    public String getNum() {\n        return num;\n    }\n\n    public void setNum(String num) {\n        this.num = num;\n    }\n\n    public String getSex() {\n        return sex;\n    }\n\n    public void setSex(String sex) {\n        this.sex = sex;\n    }\n\n    public String getBirth() {\n        return birth;\n    }\n\n    public void setBirth(String birth) {\n        this.birth = birth;\n    }\n\n    public String getStart_date() {\n        return start_date;\n    }\n\n    public void setStart_date(String start_date) {\n        this.start_date = start_date;\n    }\n\n    public String getEnd_date() {\n        return end_date;\n    }\n\n    public void setEnd_date(String end_date) {\n        this.end_date = end_date;\n    }\n\n    public String getIssue() {\n        return issue;\n    }\n\n    public void setIssue(String issue) {\n        this.issue = issue;\n    }\n\n    public boolean isSuccess() {\n        return success;\n    }\n\n    public void setSuccess(boolean success) {\n        this.success = success;\n    }\n\n\n    @Override\n    public String toString() {\n        return \"RepOutput{\" +\n                \"address='\" + address + '\\'' +\n                \", config_str='\" + config_str + '\\'' +\n                \", face_rect=\" + face_rect +\n                \", name='\" + name + '\\'' +\n                \", nationality='\" + nationality + '\\'' +\n                \", num='\" + num + '\\'' +\n                \", sex='\" + sex + '\\'' +\n                \", birth='\" + birth + '\\'' +\n                \", start_date='\" + start_date + '\\'' +\n                \", end_date='\" + end_date + '\\'' +\n                \", issue='\" + issue + '\\'' +\n                \", success=\" + success +\n                '}';\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/bean/ReqInput.java",
    "content": "package com.lib.aliocr.bean;\n\nimport java.io.Serializable;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <P>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class ReqInput implements Serializable {\n    private static final long serialVersionUID = 7540344538333996814L;\n\n    private String image;// 图片的 base64\n    private String configure;//  \"{\\\"side\\\":\\\"face\\\"}\"   身份证正反面类型:face/back\n\n\n    public String getImage() {\n        return image;\n    }\n\n    public void setImage(String image) {\n        this.image = image;\n    }\n\n    public String getConfigure() {\n        return configure;\n    }\n\n    public void setConfigure(String configure) {\n        this.configure = configure;\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/callback/OcrCallback.java",
    "content": "package com.lib.aliocr.callback;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\n\npublic interface OcrCallback {\n\n\n    void onPicResult(String picPath);\n\n    void onPicError();\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/common/Api.java",
    "content": "package com.lib.aliocr.common;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic interface Api {\n\n\n    String host = \"http://dm-51.data.aliyun.com\";\n    String path = \"/rest/160601/ocr/ocr_idcard.json\";\n   // 注册的APP code\n    String APPCODE = \"17b5e0d488564592868ed002e4e282c4\";\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/contact/OcrContact.java",
    "content": "package com.lib.aliocr.contact;\n\nimport android.app.Activity;\n\nimport com.lib.aliocr.bean.RepOutput;\n\nimport io.reactivex.Observable;\n\n/**\n * 作者：xin on 2018/7/12 0012 16:55\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic interface OcrContact {\n\n    interface V {\n\n        Activity getActivity();\n    }\n\n    interface P {\n        void onPictureTaken(final byte[] data );\n\n        void request(boolean isFace, String imgPath);\n    }\n\n\n    interface M {\n\n        Observable<RepOutput> AuthCard(boolean isFace, String imgPath);\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/http/config/HttpConfig.java",
    "content": "package com.lib.aliocr.http.config;\n\nimport android.annotation.SuppressLint;\n\nimport com.lib.aliocr.http.https.HttpsUtils;\nimport com.lib.aliocr.http.interceptor.HttpLog;\n\nimport java.util.concurrent.TimeUnit;\n\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.SSLSession;\n\nimport okhttp3.OkHttpClient;\n\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic abstract class HttpConfig {\n\n    private OkHttpClient httpClient;\n    private static final long DEFAULT_MILLISECONDS = 60000; // 默认时间\n\n    /**\n     * 获取默认的{@link OkHttpClient.Builder}\n     * <p>\n     */\n    private OkHttpClient.Builder getDefaultBuilder() {\n\n        OkHttpClient.Builder builder = new OkHttpClient.Builder();\n        // log相关\n        HttpLog httpLog = new HttpLog();\n        httpLog.setPrintLevel(HttpLog.Level.BODY); // log打印级别，决定了log显示的详细程度\n        //  httpLog.setPrintBinaryBody(true);// 打印二进制Log ,默认不打印\n        builder.addInterceptor(httpLog);\n\n        // 超时时间设置，默认60秒\n        builder.readTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS);      // 全局的读取超时时间\n        builder.writeTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS);     // 全局的写入超时时间\n        builder.connectTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS);   // 全局的连接超时时间\n\n        //  builder.cookieJar(new CookieJarImpl(new SPCookieStore(app)));          // 使用sp保持cookie，如果cookie不过期，则一直有效\n        //   builder.cookieJar(new CookieJarImpl(new DBCookieStore(ContextUtils.getActivity())));             // 使用数据库保持cookie，如果cookie不过期，则一直有效\n\n        // https相关设置，以下几种方案根据需要自己设置\n        // 方法一：信任所有证书,不安全有风险\n        HttpsUtils.SSLParams sslParams1 = HttpsUtils.getSslSocketFactory();\n        // 方法二：自定义信任规则，校验服务端证书\n        //  HttpsUtils.SSLParams sslParams2 = HttpsUtils.getSslSocketFactory(new SafeTrustManager());\n        // 方法三：使用预埋证书，校验服务端证书（自签名证书）\n       /* try {\n            HttpsUtils.SSLParams sslParams3 = HttpsUtils.getSslSocketFactory(app.getAssets().open(\"srca.cer\"));\n        } catch (IOException e) {\n            e.printStackTrace();\n        }*/\n        // 方法四：使用bks证书和密码管理客户端证书（双向认证），使用预埋证书，校验服务端证书（自签名证书）\n        // HttpsUtils.SSLParams sslParams4 = HttpsUtils.getSslSocketFactory(getAssets().open(\"xxx.bks\"), \"123456\", getAssets().open(\"yyy.cer\"));\n        builder.sslSocketFactory(sslParams1.sSLSocketFactory, sslParams1.trustManager);\n        // 配置https的域名匹配规则，详细看demo的初始化介绍，不需要就不要加入，使用不当会导致https握手失败\n        builder.hostnameVerifier(new SafeHostnameVerifier());\n        return builder;\n\n    }\n\n\n    /**\n     * 获取默认的{@link OkHttpClient.Builder}\n     * <p>\n     * 可以根据个人需要创建项目需求的Builder\n     */\n\n    @SuppressWarnings(\"SameReturnValue\")\n    protected abstract OkHttpClient.Builder getCustomBuilder();\n\n\n    /**\n     * 创建OkHttpClient\n     */\n    public OkHttpClient build() {\n\n        OkHttpClient.Builder builder = getCustomBuilder();\n\n        if (builder == null) {\n            builder = getDefaultBuilder();\n        }\n\n        httpClient = builder.build();\n        return httpClient;\n    }\n\n\n    /**\n     * 获取OKHttp\n     * <p>\n     * OkHttpClient\n     */\n    public OkHttpClient getHttpClient() {\n\n        if (httpClient == null) {\n            throw new NullPointerException(\"OkHttpClient is null,you must call HttpConfig.build(); or set a OkHttpClient  object  first\");\n        }\n\n        return httpClient;\n    }\n\n    public void setHttpClient(OkHttpClient httpClient) {\n        this.httpClient = httpClient;\n    }\n\n\n\n    /**\n     * 这里只是我谁便写的认证规则，具体每个业务是否需要验证，以及验证规则是什么，请与服务端或者leader确定\n     * 这里只是我谁便写的认证规则，具体每个业务是否需要验证，以及验证规则是什么，请与服务端或者leader确定\n     * 这里只是我谁便写的认证规则，具体每个业务是否需要验证，以及验证规则是什么，请与服务端或者leader确定\n     * 重要的事情说三遍，以下代码不要直接使用\n     */\n    private static class SafeHostnameVerifier implements HostnameVerifier {\n        @SuppressLint(\"BadHostnameVerifier\")\n        @Override\n        public boolean verify(String hostname, SSLSession session) {\n            // 验证主机名是否匹配\n            // return hostname.equals(RestApiPath.REST_URI_HOST);\n            return true;\n        }\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/http/helper/HttpHelper.java",
    "content": "package com.lib.aliocr.http.helper;\n\n\nimport com.lib.aliocr.api.HttpCustomConfig;\n\nimport okhttp3.OkHttpClient;\nimport retrofit2.Retrofit;\nimport retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;\nimport retrofit2.converter.gson.GsonConverterFactory;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class HttpHelper {\n\n\n    private OkHttpClient mOkHttpClient;\n    private RxJava2CallAdapterFactory mAdapterFactory;\n    private GsonConverterFactory mGsonConverterFactory;\n\n    private HttpHelper() {\n\n        mGsonConverterFactory = GsonConverterFactory.create();\n        mAdapterFactory = RxJava2CallAdapterFactory.create();\n        //设置超时时间\n        mOkHttpClient = new HttpCustomConfig().build();\n    }\n\n    public static HttpHelper getInstance() {\n        return LazyHolder.INSTANCE;\n    }\n\n    public  Retrofit getRetrofit(String url) {\n\n        return new Retrofit.Builder()\n                .baseUrl(url)\n                .addCallAdapterFactory(mAdapterFactory)\n                .addConverterFactory(mGsonConverterFactory)\n                .client(mOkHttpClient)\n                .build();\n    }\n\n    private static final class LazyHolder {\n        static final HttpHelper INSTANCE = new HttpHelper();\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/http/https/HttpsUtils.java",
    "content": "\npackage com.lib.aliocr.http.https;\n\n\nimport android.annotation.SuppressLint;\nimport android.util.Log;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.KeyManagementException;\nimport java.security.KeyStore;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\n\nimport javax.net.ssl.HostnameVerifier;\nimport javax.net.ssl.KeyManager;\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSession;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509TrustManager;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n@SuppressWarnings(\"WeakerAccess\")\npublic class HttpsUtils {\n\n    public static class SSLParams {\n        public SSLSocketFactory sSLSocketFactory;\n        public X509TrustManager trustManager;\n    }\n\n    public static SSLParams getSslSocketFactory() {\n        return getSslSocketFactoryBase(null, null, null);\n    }\n\n    /**\n     * https单向认证\n     * 可以额外配置信任服务端的证书策略，否则默认是按CA证书去验证的，若不是CA可信任的证书，则无法通过验证\n     */\n    public static SSLParams getSslSocketFactory(X509TrustManager trustManager) {\n        return getSslSocketFactoryBase(trustManager, null, null);\n    }\n\n    /**\n     * https单向认证\n     * 用含有服务端公钥的证书校验服务端证书\n     */\n    public static SSLParams getSslSocketFactory(InputStream... certificates) {\n        return getSslSocketFactoryBase(null, null, null, certificates);\n    }\n\n    /**\n     * https双向认证\n     * bksFile 和 password -> 客户端使用bks证书校验服务端证书\n     * certificates -> 用含有服务端公钥的证书校验服务端证书\n     */\n    public static SSLParams getSslSocketFactory(InputStream bksFile, String password, InputStream... certificates) {\n        return getSslSocketFactoryBase(null, bksFile, password, certificates);\n    }\n\n    /**\n     * https双向认证\n     * bksFile 和 password -> 客户端使用bks证书校验服务端证书\n     * X509TrustManager -> 如果需要自己校验，那么可以自己实现相关校验，如果不需要自己校验，那么传null即可\n     */\n    public static SSLParams getSslSocketFactory(InputStream bksFile, String password, X509TrustManager trustManager) {\n        return getSslSocketFactoryBase(trustManager, bksFile, password);\n    }\n\n    private static SSLParams getSslSocketFactoryBase(X509TrustManager trustManager, InputStream bksFile, String password, InputStream... certificates) {\n        SSLParams sslParams = new SSLParams();\n        try {\n            KeyManager[] keyManagers = prepareKeyManager(bksFile, password);\n            TrustManager[] trustManagers = prepareTrustManager(certificates);\n            X509TrustManager manager;\n            if (trustManager != null) {\n                //优先使用用户自定义的TrustManager\n                manager = trustManager;\n            } else if (trustManagers != null) {\n                //然后使用默认的TrustManager\n                manager = chooseTrustManager(trustManagers);\n            } else {\n                //否则使用不安全的TrustManager\n                manager = UnSafeTrustManager;\n            }\n            // 创建TLS类型的SSLContext对象， that uses our TrustManager\n            SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n            // 用上面得到的trustManagers初始化SSLContext，这样sslContext就会信任keyStore中的证书\n            // 第一个参数是授权的密钥管理器，用来授权验证，比如授权自签名的证书验证。第二个是被授权的证书管理器，用来验证服务器端的证书\n            sslContext.init(keyManagers, new TrustManager[]{manager}, null);\n            // 通过sslContext获取SSLSocketFactory对象\n            sslParams.sSLSocketFactory = sslContext.getSocketFactory();\n            sslParams.trustManager = manager;\n            return sslParams;\n        } catch (NoSuchAlgorithmException | KeyManagementException e) {\n            throw new AssertionError(e);\n        }\n    }\n\n    private static KeyManager[] prepareKeyManager(InputStream bksFile, String password) {\n        try {\n            if (bksFile == null || password == null) return null;\n            KeyStore clientKeyStore = KeyStore.getInstance(\"BKS\");\n            clientKeyStore.load(bksFile, password.toCharArray());\n            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());\n            kmf.init(clientKeyStore, password.toCharArray());\n            return kmf.getKeyManagers();\n        } catch (Exception e) {\n            Log.e(\"net\",\"Https#prepareKeyManager error\",e);\n        }\n        return null;\n    }\n\n    private static TrustManager[] prepareTrustManager(InputStream... certificates) {\n        if (certificates == null || certificates.length <= 0) return null;\n        try {\n            CertificateFactory certificateFactory = CertificateFactory.getInstance(\"X.509\");\n            // 创建一个默认类型的KeyStore，存储我们信任的证书\n            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());\n            keyStore.load(null);\n            int index = 0;\n            for (InputStream certStream : certificates) {\n                String certificateAlias = Integer.toString(index++);\n                // 证书工厂根据证书文件的流生成证书 cert\n                Certificate cert = certificateFactory.generateCertificate(certStream);\n                // 将 cert 作为可信证书放入到keyStore中\n                keyStore.setCertificateEntry(certificateAlias, cert);\n                try {\n                    if (certStream != null) certStream.close();\n                } catch (IOException e) {\n                    Log.e(\"net\",\"Https#prepareTrustManager error\",e);\n                }\n            }\n            //我们创建一个默认类型的TrustManagerFactory\n            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n            //用我们之前的keyStore实例初始化TrustManagerFactory，这样tmf就会信任keyStore中的证书\n            tmf.init(keyStore);\n            //通过tmf获取TrustManager数组，TrustManager也会信任keyStore中的证书\n            return tmf.getTrustManagers();\n        } catch (Exception e) {\n            Log.e(\"net\",\"Https#prepareTrustManager error\",e);\n        }\n        return null;\n    }\n\n    private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {\n        for (TrustManager trustManager : trustManagers) {\n            if (trustManager instanceof X509TrustManager) {\n                return (X509TrustManager) trustManager;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * 为了解决客户端不信任服务器数字证书的问题，网络上大部分的解决方案都是让客户端不对证书做任何检查，\n     * 这是一种有很大安全漏洞的办法\n     */\n    public static X509TrustManager UnSafeTrustManager = new X509TrustManager() {\n        @SuppressLint(\"TrustAllX509TrustManager\")\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n        }\n\n        @SuppressLint(\"TrustAllX509TrustManager\")\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n        }\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers() {\n            return new X509Certificate[]{};\n        }\n    };\n\n    /**\n     * 此类是用于主机名验证的基接口。 在握手期间，如果 URL 的主机名和服务器的标识主机名不匹配，\n     * 则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。策略可以是基于证书的或依赖于其他验证方案。\n     * 当验证 URL 主机名使用的默认规则失败时使用这些回调。如果主机名是可接受的，则返回 true\n     */\n    public static HostnameVerifier UnSafeHostnameVerifier = new HostnameVerifier() {\n        @SuppressLint(\"BadHostnameVerifier\")\n        @Override\n        public boolean verify(String hostname, SSLSession session) {\n            return true;\n        }\n    };\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/http/interceptor/HttpLog.java",
    "content": "package com.lib.aliocr.http.interceptor;\n\n\nimport android.support.annotation.NonNull;\nimport android.util.Log;\n\nimport com.lib.aliocr.utils.io.IOUtils;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.util.concurrent.TimeUnit;\n\nimport okhttp3.Connection;\nimport okhttp3.Headers;\nimport okhttp3.Interceptor;\nimport okhttp3.MediaType;\nimport okhttp3.Protocol;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\nimport okhttp3.internal.http.HttpHeaders;\nimport okio.Buffer;\n\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class HttpLog implements Interceptor {\n\n    private boolean isPrintBinaryBody;\n\n    public enum Level {\n        NONE,       //不打印log\n        BASIC,      //只打印 请求首行 和 响应首行\n        HEADERS,    //打印请求和响应的所有 Header\n        BODY        //所有数据全部打印\n    }\n\n\n    private volatile Level printLevel = Level.NONE;\n\n    public void setPrintLevel(Level printLevel) {\n        this.printLevel = printLevel;\n    }\n\n    public void setPrintBinaryBody(boolean printBinaryBody) {\n        isPrintBinaryBody = printBinaryBody;\n    }\n\n    @Override\n    public Response intercept(@NonNull Chain chain) throws IOException {\n        Request request = chain.request();\n        if (printLevel == Level.NONE) {\n            return chain.proceed(request);\n        }\n\n        //请求日志拦截\n        logForRequest(request, chain.connection());\n        // 执行请求，计算请求时间\n        long startNs = System.nanoTime();\n        Response response;\n        try {\n            response = chain.proceed(request);\n        } catch (Exception e) {\n            //  Log.e(e, \"<-- HTTP FAILED: \");\n            throw e;\n        }\n        long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);\n\n        //响应日志拦截\n        return logForResponse(response, tookMs);\n    }\n\n\n    private void logForRequest(Request request, Connection connection) {\n\n        boolean logBody = (printLevel == Level.BODY);\n        boolean logHeaders = (printLevel == Level.BODY || printLevel == Level.HEADERS);\n        RequestBody requestBody = request.body();\n        boolean hasRequestBody = requestBody != null;\n        Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;\n\n        StringBuilder requestMessage = new StringBuilder();\n        try {\n            requestMessage.append(\"--> \").append(request.method()).append(' ').append(request.url()).append(' ').append(protocol).append(\"\\n\");\n\n\n            if (logHeaders) {\n                if (hasRequestBody) {\n                    // Request body headers are only present when installed as a network interceptor. Force\n                    // them to be included (when available) so there values are known.\n                    if (requestBody.contentType() != null) {\n                        requestMessage.append(\"\\tContent-Type: \").append(requestBody.contentType()).append(\"\\n\");\n                    }\n                    if (requestBody.contentLength() != -1) {\n                        requestMessage.append(\"\\tContent-Length: \").append(requestBody.contentLength()).append(\"\\n\");\n                    }\n                }\n                Headers headers = request.headers();\n                for (int i = 0, count = headers.size(); i < count; i++) {\n                    String name = headers.name(i);\n                    // Skip headers from the request body as they are explicitly logged above.\n                    if (!\"Content-Type\".equalsIgnoreCase(name) && !\"Content-Length\".equalsIgnoreCase(name)) {\n                        requestMessage.append(\"\\t\").append(name).append(\": \").append(headers.value(i)).append(\"\\n\");\n                    }\n                }\n\n\n                if (logBody && hasRequestBody) {\n                    if (isPlaintext(requestBody.contentType())) {\n                        requestMessage.append(bodyToString(request));\n                    } else {\n                        requestMessage.append(\"\\tbody: maybe [binary body], omitted!\\n\");\n                    }\n                }\n            }\n        } catch (Exception e) {\n            Log.e(\"orc\", \"intercept\");\n        } finally {\n\n            requestMessage.append(\"--> END \").append(request.method());\n\n            Log.i(\"HTTPLOG\", requestMessage.toString());\n        }\n    }\n\n\n    private Response logForResponse(Response response, long tookMs) {\n        StringBuilder responseMsg = new StringBuilder();\n        Response.Builder builder = response.newBuilder();\n        Response clone = builder.build();\n        ResponseBody responseBody = clone.body();\n        boolean logBody = (printLevel == Level.BODY);\n        boolean logHeaders = (printLevel == Level.BODY || printLevel == Level.HEADERS);\n\n        try {\n            responseMsg.append(\"<-- \").append(clone.code()).append(' ').append(clone.message()).append(' ').append(clone.request().url()).append(\" (\").append(tookMs).append(\"ms）\" + \"\\n\");\n            if (logHeaders) {\n                Headers headers = clone.headers();\n\n                for (int i = 0, count = headers.size(); i < count; i++) {\n                    responseMsg.append(\"\\t\").append(headers.name(i)).append(\": \").append(headers.value(i)).append(\"\\n\");\n                }\n\n                if (logBody && HttpHeaders.hasBody(clone)) {\n                    if (responseBody == null) return response;\n\n                    if (isPrintBinaryBody || isPlaintext(responseBody.contentType())) {\n                        byte[] bytes = IOUtils.toByteArray(responseBody.byteStream());\n                        MediaType contentType = responseBody.contentType();\n                        String body = new String(bytes, getCharset(contentType));\n                        responseMsg.append(\"\\tbody:\").append(body);\n                        responseBody = ResponseBody.create(responseBody.contentType(), bytes);\n                        return response.newBuilder().body(responseBody).build();\n                    } else {\n                        responseMsg.append(\"\\tbody: maybe [binary body], omitted! \\n if you want to log it please call set isPrintBinaryBody is true\");\n                    }\n                }\n            }\n        } catch (Exception e) {\n            Log.e(\"\", \"logForResponse error\");\n        } finally {\n            responseMsg.append(\"<-- END HTTP\");\n\n            Log.i(\"HTTPLOG\", responseMsg.toString());\n        }\n        return response;\n    }\n\n    /**\n     * Returns true if the body in question probably contains human readable text. Uses a small sample\n     * of code points to detect unicode control characters commonly used in binary file signatures.\n     */\n    private static boolean isPlaintext(MediaType mediaType) {\n        if (mediaType == null) return false;\n        if (mediaType.type() != null && mediaType.type().equals(\"text\")) {\n            return true;\n        }\n        String subtype = mediaType.subtype();\n        if (subtype != null) {\n            subtype = subtype.toLowerCase();\n            if (subtype.contains(\"x-www-form-urlencoded\") || subtype.contains(\"json\") || subtype.contains(\"xml\") || subtype.contains(\"html\")) //\n                return true;\n        }\n        return false;\n    }\n\n    private String bodyToString(Request request) {\n\n        try {\n            Request copy = request.newBuilder().build();\n            RequestBody body = copy.body();\n            if (body == null) return \"\";\n            Buffer buffer = new Buffer();\n            body.writeTo(buffer);\n            Charset charset = getCharset(body.contentType());\n            return \"\\tbody:\" + buffer.readString(charset) + \"\\n\";\n        } catch (Exception e) {\n            Log.e(\"\", \"bodyToString error\");\n        }\n\n        return \"\";\n    }\n\n    private static Charset getCharset(MediaType contentType) {\n        Charset charset = contentType != null ? contentType.charset(UTF8) : UTF8;\n        if (charset == null) charset = UTF8;\n        return charset;\n    }\n\n    private static final Charset UTF8 = Charset.forName(\"UTF-8\");\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/modle/OCRModel.java",
    "content": "package com.lib.aliocr.modle;\n\nimport com.google.gson.Gson;\nimport com.lib.aliocr.api.MyApiManager;\nimport com.lib.aliocr.bean.RepOutput;\nimport com.lib.aliocr.bean.ReqInput;\nimport com.lib.aliocr.common.Api;\nimport com.lib.aliocr.contact.OcrContact;\nimport com.lib.aliocr.http.helper.HttpHelper;\nimport com.lib.aliocr.utils.OcrUtils;\n\nimport org.json.JSONObject;\n\nimport io.reactivex.Observable;\nimport io.reactivex.ObservableEmitter;\nimport io.reactivex.ObservableOnSubscribe;\nimport io.reactivex.functions.Function;\nimport okhttp3.RequestBody;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class OCRModel implements OcrContact.M{\n\n\n    public Observable<RepOutput> AuthCard(final boolean isFace, final String imgPath) {\n\n        return Observable.create(new ObservableOnSubscribe<ReqInput>() {\n            @Override\n            public void subscribe(ObservableEmitter<ReqInput> e) throws Exception {\n                JSONObject configObj = new JSONObject();\n                if (isFace) {\n                    configObj.put(\"side\", \"face\");\n                } else {\n                    configObj.put(\"side\", \"back\");\n                }\n                ReqInput input = new ReqInput();\n                input.setImage(OcrUtils.getImageStr(imgPath));\n                input.setConfigure(configObj.toString());\n                e.onNext(input);\n\n            }\n        }).flatMap(new Function<ReqInput, Observable<RepOutput>>() {\n            @Override\n            public Observable<RepOutput> apply(ReqInput reqInput) throws Exception {\n                MyApiManager apiManager = HttpHelper.getInstance().getRetrofit(Api.host).create(MyApiManager.class);\n                Gson gson = new Gson();\n                String reqStr = gson.toJson(reqInput);\n                RequestBody body = RequestBody.create(okhttp3.MediaType.parse(\"application/json; charset=utf-8\"), reqStr);\n                return apiManager.authCard(body);\n            }\n        });\n\n\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/presenter/OCRPresenter.java",
    "content": "package com.lib.aliocr.presenter;\n\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.Environment;\nimport android.util.Log;\nimport android.widget.Toast;\n\nimport com.lib.aliocr.bean.RepOutput;\nimport com.lib.aliocr.contact.OcrContact;\nimport com.lib.aliocr.modle.OCRModel;\nimport com.lib.aliocr.utils.OcrUtils;\nimport com.lib.aliocr.view.OCRMainActivity;\n\nimport java.io.File;\n\nimport io.reactivex.Observable;\nimport io.reactivex.ObservableEmitter;\nimport io.reactivex.ObservableOnSubscribe;\nimport io.reactivex.Observer;\nimport io.reactivex.android.schedulers.AndroidSchedulers;\nimport io.reactivex.disposables.Disposable;\nimport io.reactivex.functions.Consumer;\nimport io.reactivex.schedulers.Schedulers;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class OCRPresenter implements OcrContact.P {\n    private OcrContact.M model;\n    private OcrContact.V view;\n    private static final String TAG = OCRMainActivity.class.getSimpleName();\n\n    public OCRPresenter(OcrContact.V view) {\n        this.view = view;\n        model = new OCRModel();\n    }\n\n    public void onPictureTaken(final byte[] data) {\n\n\n\n        Disposable disposable=  Observable .create(new ObservableOnSubscribe<File>() {\n            @Override\n            public void subscribe(ObservableEmitter<File> emitter) {\n\n                String fileName = System.currentTimeMillis() + \"picture.jpg\";\n                File file = new File(view.getActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES),\n                        fileName);\n                Bitmap bitmap = OcrUtils.byte2bitmap(data);\n\n                OcrUtils.saveBitmap(bitmap, file.getAbsolutePath());\n\n                if (/*OcrUtils.compressSmallImage(bitmap, file, 4 * 1023)*/file.exists()) {\n                    emitter.onNext(file);\n                } else {\n                    emitter.onError(new Exception(\"图片转换失败\"));\n                }\n            }\n        }).subscribeOn(Schedulers.io()).subscribeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<File>() {\n            @Override\n            public void accept(File file) throws Exception {\n                if (file != null && file.exists()) {\n                    OcrUtils.beginCrop(Uri.fromFile(file), view.getActivity(), null);\n\n                }\n            }\n        });\n    }\n\n    public void request(boolean isFace, final String imgPath) {\n        model.AuthCard(isFace, imgPath).subscribeOn(Schedulers.io()).\n                observeOn(AndroidSchedulers.mainThread()).\n                subscribe(new Observer<RepOutput>() {\n                    @Override\n                    public void onSubscribe(Disposable d) {\n\n                    }\n\n                    @Override\n                    public void onNext(RepOutput repOutput) {\n                        if (repOutput != null)\n                            Log.i(\"HTTPLOG\", repOutput.toString());\n\n                        Toast.makeText(view.getActivity(), \"\" + repOutput.toString(), Toast.LENGTH_LONG).show();\n                    }\n\n                    @Override\n                    public void onError(Throwable e) {\n                        Log.i(\"HTTPLOG\", e.toString());\n                        Toast.makeText(view.getActivity(), \"\" + e.toString(), Toast.LENGTH_LONG).show();\n\n                    }\n\n                    @Override\n                    public void onComplete() {\n\n                    }\n                });\n\n\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/Base64.java",
    "content": "package com.lib.aliocr.utils;\n\nimport sun.misc.BASE64Encoder;\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class Base64 {\n\n\n    public static String getAuthor(String secretKey,String author) {\n        byte[] data = null;\n        String s = secretKey + author;\n        try {\n            data = s.getBytes();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        // 加密\n        BASE64Encoder encoder = new BASE64Encoder();\n        return encoder.encode(data != null ? data : new byte[0]);\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/Ocr.java",
    "content": "package com.lib.aliocr.utils;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.Environment;\nimport android.provider.MediaStore;\nimport android.support.v4.app.ActivityCompat;\nimport android.support.v4.app.Fragment;\nimport android.view.Gravity;\nimport android.view.View;\nimport android.widget.AdapterView;\n\nimport com.lib.aliocr.callback.OcrCallback;\nimport com.lib.aliocr.view.OCRMainActivity;\nimport com.lib.aliocr.widget.crop.Crop;\nimport com.lib.aliocr.widget.popup.XinPopWindow;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class Ocr {\n\n    private static final int PICK_IMAGE_CHOOSER_REQUEST_CODE = 222;\n    private static final int PICK_IMAGE_PERMISSIONS_REQUEST_CODE = 222222;\n    private static final int IMAGE_CROP_CODE = 3333;\n\n\n    /**\n     * 发起验证\n     *\n     * @param activity\n     * @param rootView\n     */\n    public static void doOcr(final Activity activity, final Fragment fragment, final View rootView, final boolean isFace) {\n        List<XinPopWindow.MenuItem> menuItems = new ArrayList<>();\n        menuItems.add(new XinPopWindow.MenuItem(\"拍照识别\", 0));\n        menuItems.add(new XinPopWindow.MenuItem(\"相册识别\", 1));\n        final XinPopWindow myPopWindow = new XinPopWindow(activity);\n        myPopWindow.setData(menuItems);\n        myPopWindow.setOnItemClickListener(new XinPopWindow.OnItemClickListener() {\n            @Override\n            public void onItemClick(AdapterView<?> parent, View view, int position, long id, int type) {\n                if (type == 0) {\n                    OCRMainActivity.Launcher(activity, isFace);\n                } else {\n\n                    if (fragment != null) {\n                        Crop.pickImage(activity, fragment);\n                    } else {\n\n                        Crop.pickImage(activity);\n                    }\n                }\n                myPopWindow.dismiss();\n            }\n        });\n        myPopWindow.showAtLocation(rootView, Gravity.BOTTOM, 0, 0);\n    }\n\n\n    /**\n     * activity回调\n     *\n     * @param activity\n     * @param requestCode\n     * @param resultCode\n     * @param result\n     * @param callback\n     */\n    public static void onCropImgResult(Activity activity, Fragment fragment, int requestCode, int resultCode, Intent result, OcrCallback callback) {\n        if (requestCode == Crop.REQUEST_PICK && resultCode == Activity.RESULT_OK) {\n            OcrUtils.beginCrop(result.getData(), activity, fragment);\n        } else if (requestCode == Crop.REQUEST_CROP) {\n            if (resultCode == Activity.RESULT_OK) {\n                Uri imgUri = Crop.getOutput(result);\n\n                if (callback != null) {\n                    callback.onPicResult(OcrUtils.getPath(activity, imgUri));\n                }\n            } else if (resultCode == Crop.RESULT_ERROR) {\n                if (callback != null) {\n\n                    callback.onPicError();\n                }\n            }\n        }\n    }\n\n\n    public static Uri onPickImgResult(Activity activity, int requestCode, int resultCode, Intent data) {\n\n        Uri cropImageUri = null;\n        if (resultCode == Activity.RESULT_OK) {\n            if (requestCode == PICK_IMAGE_CHOOSER_REQUEST_CODE) {\n                cropImageUri = data.getData();\n\n                if (OcrUtils.isReadExternalStoragePermissionsRequired(activity, cropImageUri)) {// 需要权限处理\n                    ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},\n                            PICK_IMAGE_PERMISSIONS_REQUEST_CODE);\n                } else {\n                    handleImgShot(activity, cropImageUri);\n                }\n\n            }\n\n        }\n        return cropImageUri;\n\n    }\n\n\n    public static void onBackResult(Activity activity, int requestCode, int resultCode, Intent data, boolean isFace) {\n        //noinspection StatementWithEmptyBody\n        if (resultCode == Activity.RESULT_OK && requestCode == IMAGE_CROP_CODE) {\n            // TODO\n\n            //    new OCRModel().AuthCard(isFace,)\n        }\n    }\n\n\n    public static void onRequestPermissionsResult(Activity activity, int requestCode, String permissions[],\n                                                  int[] grantResults, Uri cropImageUri) {\n\n        if (requestCode == PICK_IMAGE_PERMISSIONS_REQUEST_CODE) {\n            if (cropImageUri != null\n                    && grantResults.length > 0\n                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n\n\n                handleImgShot(activity, cropImageUri);\n            }\n            // TODO  用户拒绝授权\n\n        }\n    }\n\n\n    /**\n     * Intent方式截图处理\n     */\n    public static void handleImgShot(Activity activity, Uri uri) {\n        File file = new File(activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES), System.currentTimeMillis() + \".jpg\");\n        String cropImagePath = file.getAbsolutePath();\n        Intent intent = new Intent(\"com.android.camera.action.CROP\");\n        intent.setDataAndType(uri, \"image/*\");\n        intent.putExtra(\"crop\", \"true\");\n        intent.putExtra(\"aspectX\", 1);\n        intent.putExtra(\"aspectY\", 1);\n        intent.putExtra(\"outputX\", 500);\n        intent.putExtra(\"outputY\", 500);\n        intent.putExtra(\"scale\", true);\n        intent.putExtra(\"scaleUpIfNeeded\", true);\n        intent.putExtra(\"return-data\", false);\n        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));\n        intent.putExtra(\"outputFormat\", Bitmap.CompressFormat.JPEG.toString());\n        intent.putExtra(\"noFaceDetection\", true);\n        activity.startActivityForResult(intent, IMAGE_CROP_CODE);\n\n    }\n\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/OcrUtils.java",
    "content": "package com.lib.aliocr.utils;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.ContentResolver;\nimport android.content.ContentUris;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.database.Cursor;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.os.Parcelable;\nimport android.provider.DocumentsContract;\nimport android.provider.MediaStore;\nimport android.support.annotation.NonNull;\nimport android.support.v4.app.Fragment;\n\nimport com.lib.aliocr.R;\nimport com.lib.aliocr.widget.crop.Crop;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport sun.misc.BASE64Encoder;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n@SuppressWarnings(\"ResultOfMethodCallIgnored\")\npublic class OcrUtils {\n    public static String getImageStr(String imgFile) {\n        InputStream inputStream;\n        byte[] data = null;\n        try {\n            inputStream = new FileInputStream(imgFile);\n            data = new byte[inputStream.available()];\n            inputStream.read(data);\n            inputStream.close();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        // 加密\n        BASE64Encoder encoder = new BASE64Encoder();\n        return encoder.encode(data);\n    }\n\n    public static Bitmap byte2bitmap(byte[] data) {\n\n        if (null == data) {\n\n            return null;\n\n        }\n\n        return BitmapFactory.decodeByteArray(data,\n                0,\n                data.length);\n\n    }\n\n    public static boolean saveBitmap(Bitmap bitmap,\n                                     String path) {\n        return saveBitmap(bitmap,\n                path,\n                100);\n    }\n\n    /**\n     * 将bitmap位图保存到path路径下，图片格式为Bitmap.CompressFormat.JPEG，质量为100\n     *\n     * @param bitmap\n     * @param path\n     * @param quality 压缩的比率（1-100）\n     */\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public static boolean saveBitmap(Bitmap bitmap,\n                                     String path,\n                                     int quality) {\n\n        try {\n\n            File file = new File(path);\n\n            File parent = file.getParentFile();\n\n            if (!parent.exists()) {\n\n                parent.mkdirs();\n\n            }\n\n            // if(file.exists()){\n            // FileUtil.deleteFile(file);\n            // }\n\n            FileOutputStream fos = new FileOutputStream(file);\n\n            boolean b = bitmap.compress(Bitmap.CompressFormat.JPEG,\n                    quality,\n                    fos);\n            fos.flush();\n\n            fos.close();\n\n            return b;\n\n        } catch (IOException e) {\n\n            e.printStackTrace();\n\n        } finally {\n            if (bitmap != null && !bitmap.isRecycled()) {\n                bitmap.isRecycled();\n            }\n        }\n\n        return false;\n\n    }\n\n\n    /**\n     * 图片压缩并保存到文件(是否压缩都保存)\n     */\n    public static boolean compressSmallImage(Bitmap image,\n                                             File file,\n                                             int kb) {\n        return compressSmallImage(image,\n                file,\n                kb,\n                true);\n    }\n\n    /**\n     * 图片压缩并保存到文件\n     *\n     * @param image      源文件\n     * @param file       要保存到的file\n     * @param kb         限制的size\n     * @param iSsaveFile (可为false,如File已存在但无压缩的情况下可设置为false)\n     * @return 是否存储了文件\n     */\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public static boolean compressSmallImage(Bitmap image,\n                                             File file,\n                                             int kb,\n                                             boolean iSsaveFile) {\n        if (image == null)\n            return false;\n        Boolean isCompressed = false;\n        boolean isSaveFileSuccessed = false;// 是否存储成功了文件夹\n        File parent = file.getParentFile();\n\n        if (!parent.exists()) {\n\n            parent.mkdirs();\n\n        }\n\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        OutputStream out = null;\n        image.compress(Bitmap.CompressFormat.JPEG,\n                100,\n                baos);\n\n        int options = 100;\n        Bitmap temp;\n        while ((baos.size() <= 0 ? baos.toByteArray().length : baos.size()) / 1024 > kb) {\n            isCompressed = true;\n            baos.reset();\n            options -= 10;// 每次都减少10\n            image.compress(Bitmap.CompressFormat.JPEG,\n                    options,\n                    baos);\n        }\n        byte[] byteArray = baos.toByteArray();\n        try {\n            // 不管有无压缩，都写入文件\n            if (iSsaveFile) {\n                out = new FileOutputStream(file);\n                out.write(byteArray,\n                        0,\n                        byteArray.length);\n                isSaveFileSuccessed = true;\n            } else {\n                // iSsaveFile==false 时，只有压缩后写入文件\n                if (isCompressed) {\n                    out = new FileOutputStream(file);\n                    out.write(byteArray,\n                            0,\n                            byteArray.length);\n                    isSaveFileSuccessed = true;\n                }\n            }\n        } catch (Exception e) {\n        } finally {\n            try {\n                if (out != null) {\n                    out.close();\n                }\n                baos.close();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        if (isSaveFileSuccessed) {\n\n        }\n        return isSaveFileSuccessed;\n    }\n\n\n    public static String getPath(Context context,\n                                 Uri uri) {\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n            if (DocumentsContract.isDocumentUri(context, uri)) {\n                String docId;\n                String[] split;\n                String type;\n                if (isExternalStorageDocument(uri)) {\n                    docId = DocumentsContract.getDocumentId(uri);\n                    split = docId.split(\":\");\n                    type = split[0];\n                    if (\"primary\".equalsIgnoreCase(type)) {\n                        return Environment.getExternalStorageDirectory() + \"/\" + split[1];\n                    }\n                } else {\n                    if (isDownloadsDocument(uri)) {\n                        docId = DocumentsContract.getDocumentId(uri);\n                        Uri split1 = ContentUris.withAppendedId(Uri.parse(\"content://downloads/public_downloads\"),\n                                Long.valueOf(docId));\n                        return getDataColumn(context,\n                                split1,\n                                null,\n                                null);\n                    }\n\n                    if (isMediaDocument(uri)) {\n                        docId = DocumentsContract.getDocumentId(uri);\n                        split = docId.split(\":\");\n                        type = split[0];\n                        Uri contentUri = null;\n                        if (\"image\".equals(type)) {\n                            contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;\n                        } else if (\"video\".equals(type)) {\n                            contentUri = android.provider.MediaStore.Video.Media.EXTERNAL_CONTENT_URI;\n                        } else if (\"audio\".equals(type)) {\n                            contentUri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;\n                        }\n\n                        String selection = \"_id=?\";\n                        String[] selectionArgs = new String[]{split[1]};\n                        return getDataColumn(context,\n                                contentUri,\n                                \"_id=?\",\n                                selectionArgs);\n                    }\n                }\n            }\n        }\n\n        if (\"content\".equalsIgnoreCase(uri.getScheme())) {\n            if (isGooglePhotosUri(uri)) {\n                return uri.getLastPathSegment();\n            }\n\n            return getDataColumn(context,\n                    uri,\n                    null,\n                    null);\n        }\n\n        if (\"file\".equalsIgnoreCase(uri.getScheme())) {\n            return uri.getPath();\n        }\n\n\n        return null;\n    }\n\n    public static String getDataColumn(Context context,\n                                       Uri uri,\n                                       String selection,\n                                       String[] selectionArgs) {\n        Cursor cursor = null;\n        String column = \"_data\";\n        String[] projection = new String[]{\"_data\"};\n\n        try {\n            cursor = context.getContentResolver()\n                    .query(uri,\n                            projection,\n                            selection,\n                            selectionArgs,\n                            null);\n            if (cursor != null && cursor.moveToFirst()) {\n                int index = cursor.getColumnIndexOrThrow(\"_data\");\n                String var9 = cursor.getString(index);\n                return var9;\n            }\n        } finally {\n            if (cursor != null) {\n                cursor.close();\n            }\n\n        }\n\n        return null;\n    }\n\n\n    public static boolean isExternalStorageDocument(Uri uri) {\n        return \"com.android.externalstorage.documents\".equals(uri.getAuthority());\n    }\n\n    public static boolean isDownloadsDocument(Uri uri) {\n        return \"com.android.providers.downloads.documents\".equals(uri.getAuthority());\n    }\n\n    public static boolean isMediaDocument(Uri uri) {\n        return \"com.android.providers.media.documents\".equals(uri.getAuthority());\n    }\n\n    public static boolean isGooglePhotosUri(Uri uri) {\n        return \"com.google.android.apps.photos.content\".equals(uri.getAuthority());\n    }\n\n\n    private static Intent getGalleryIntent(Context context, boolean includeDocuments) {\n        PackageManager packageManager = context.getPackageManager();\n        List<Intent> galleryIntents = getGalleryIntents(packageManager, Intent.ACTION_GET_CONTENT, includeDocuments);\n\n        if (galleryIntents.size() == 0) {\n            // if no intents found for get-content try pick intent action (Huawei P9).\n            galleryIntents = getGalleryIntents(packageManager, Intent.ACTION_PICK, includeDocuments);\n        }\n        Intent target;\n        if (galleryIntents.isEmpty()) {\n            target = new Intent();\n        } else {\n            target = galleryIntents.get(galleryIntents.size() - 1);\n            galleryIntents.remove(galleryIntents.size() - 1);\n        }\n\n        // Create a chooser from the main  intent\n        Intent chooserIntent = Intent.createChooser(target, context.getString(R.string.pick_image_intent_chooser_title));\n\n        // Add all other intents\n        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, galleryIntents.toArray(new Parcelable[galleryIntents.size()]));\n\n        return chooserIntent;\n    }\n\n\n    /**\n     * Get all Gallery intents for getting image from one of the apps of the device that handle images.\n     */\n    private static List<Intent> getGalleryIntents(@NonNull\n                                                          PackageManager packageManager, String action, boolean includeDocuments) {\n        List<Intent> intents = new ArrayList<>();\n        Intent galleryIntent = Intent.ACTION_GET_CONTENT.equals(action) ? new Intent(action)\n                : new Intent(action, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);\n        galleryIntent.setType(\"image/*\");\n        List<ResolveInfo> listGallery = packageManager.queryIntentActivities(galleryIntent, 0);\n        for (ResolveInfo res : listGallery) {\n            Intent intent = new Intent(galleryIntent);\n            intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));\n            intent.setPackage(res.activityInfo.packageName);\n            intents.add(intent);\n        }\n\n        // remove documents intent\n        if (!includeDocuments) {\n            for (Intent intent : intents) {\n                if (intent.getComponent().getClassName().equals(\"com.android.documentsui.DocumentsActivity\")) {\n                    intents.remove(intent);\n                    break;\n                }\n            }\n        }\n        return intents;\n    }\n\n\n    public static boolean isReadExternalStoragePermissionsRequired(@NonNull Context context, @NonNull\n            Uri uri) {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&\n                context.checkSelfPermission(\n                        Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED &&\n                isUriRequiresPermissions(context, uri);\n    }\n\n\n    /**\n     * Test if we can open the given Android URI to test if permission required error is thrown.<br>\n     * Only relevant for API version 23 and above.\n     *\n     * @param context used to access Android APIs, like content resolve, it is your activity/fragment/widget.\n     * @param uri     the result URI of image pick.\n     */\n    public static boolean isUriRequiresPermissions(@NonNull Context context, @NonNull Uri uri) {\n        try {\n            ContentResolver resolver = context.getContentResolver();\n            InputStream stream = resolver.openInputStream(uri);\n            stream.close();\n            return false;\n        } catch (Exception e) {\n            return true;\n        }\n    }\n\n\n    public static void beginCrop(Uri source, Activity activity, Fragment fragment) {\n        Uri destination = Uri.fromFile(new File(activity.getCacheDir(), \"cropped\"));\n\n        Crop crop = Crop.of(source, destination).withAspect(1, 1.3f);\n        if (fragment == null)\n            crop.start(activity);\n        else\n            crop.start(activity, fragment);\n\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/io/Charsets.java",
    "content": "\npackage com.lib.aliocr.utils.io;\n\nimport java.nio.charset.Charset;\n\n/**\n * Charsets\n */\n@SuppressWarnings(\"WeakerAccess\")\npublic class Charsets {\n    public static Charset toCharset(Charset charset) {\n        return charset == null ? Charset.defaultCharset() : charset;\n    }\n    public static Charset toCharset(String charset) {\n        return charset == null ? Charset.defaultCharset() : Charset.forName(charset);\n    }\n    public static final Charset ISO_8859_1 = Charset.forName(\"ISO-8859-1\");\n    public static final Charset US_ASCII = Charset.forName(\"US-ASCII\");\n    public static final Charset UTF_16 = Charset.forName(\"UTF-16\");\n    public static final Charset UTF_16BE = Charset.forName(\"UTF-16BE\");\n    public static final Charset UTF_16LE = Charset.forName(\"UTF-16LE\");\n    public static final Charset UTF_8 = Charset.forName(\"UTF-8\");\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/io/FileUtil.java",
    "content": "package com.lib.aliocr.utils.io;\n\nimport android.app.ActivityManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.text.TextUtils;\nimport android.text.format.DateUtils;\nimport android.util.Log;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.FileReader;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.RandomAccessFile;\nimport java.io.StringReader;\nimport java.io.StringWriter;\nimport java.text.DecimalFormat;\nimport java.util.Date;\n\n\n\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\n@SuppressWarnings(\"WeakerAccess\")\npublic class FileUtil {\n    private static final String TAG = \"FileUtil\";\n    public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;\n\n\n    /*\n      ********************************************读写***************************\n     */\n\n    /**\n     * Returns a human-readable version of the file size, where the input\n     * represents a specific number of bytes.\n     *\n     * @param size the number of bytes\n     * @return a human-readable display value (includes units)\n     */\n    public static String formatSize(long size) {\n        float ONE_KB = 1024F;\n        float ONE_MB = ONE_KB * ONE_KB;\n        float ONE_GB = ONE_KB * ONE_MB;\n        String displaySize;\n        DecimalFormat df = new DecimalFormat(\"0.0\");\n        if (size >= ONE_KB && size < ONE_MB) {\n            displaySize = String.valueOf(df.format(size / ONE_KB)) + \" KB\";\n        } else if (size >= ONE_MB && size < ONE_GB) {\n            displaySize = String.valueOf(df.format(size / ONE_MB)) + \" MB\";\n        } else if (size >= ONE_GB) {\n            displaySize = String.valueOf(df.format(size / ONE_GB)) + \" GB\";\n        } else if (size == 0) {\n            displaySize = \" 0KB\";\n        } else {\n            displaySize = \"1KB\";\n            Log.d(\"formatSize\",\n                    String.valueOf(df.format(size)) + \" B\");\n        }\n        /*\n         * else {\n         * displaySize = String.valueOf(df.format(size)) + \" B\";\n         * }\n         */\n        return displaySize;\n    }\n\n\n    /**\n     * 递归删除文件目录\n     *\n     * @param dir 文件目录\n     */\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public static void deleteFileDir(File dir) {\n        Log.d(TAG,\n                \"deleteFileDir.dir = \" + dir);\n        try {\n            if (dir.exists() && dir.isDirectory()) {// 判断是文件还是目录\n                if (dir.listFiles().length == 0) {// 若目录下没有文件则直接删除\n                    dir.delete();\n                } else {// 若有则把文件放进数组，并判断是否有下级目录\n                    File delFile[] = dir.listFiles();\n                    int len = dir.listFiles().length;\n                    for (int j = 0; j < len; j++) {\n                        if (delFile[j].isDirectory()) {\n                            deleteFileDir(delFile[j]);// 递归调用deleteFileDir方法并取得子目录路径\n                        } else {\n                            boolean isDel = delFile[j].delete();// 删除文件\n                            Log.d(TAG,\n                                    \"deleteFileDir.delFile[\" + j + \"] = \" + delFile[j] + \", isDelete  = \" + isDel);\n                        }\n                    }\n                }\n                deleteFileDir(dir);// 递归调用\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * 删除单个文件\n     *\n     * @param file 文件目录\n     */\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public static void deleteFile(File file) {\n\n        try {\n            Log.d(TAG,\n                    \"deleteFile.file = \" + file);\n            if (file != null && file.isFile() && file.exists()) {\n                file.delete();\n                Log.d(TAG,\n                        \"deleteFile.file = \" + file + \" Succeed\");\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n            Log.d(TAG,\n                    \"deleteFile.file = \" + file + \" exception=\" + e.toString());\n        }\n    }\n\n    /**\n     * 读取指定文件的输出\n     */\n    public static byte[] getFileOutputBytes(String filePath) {\n        byte[] buffer = null;\n        try {\n            File file = new File(filePath);\n            FileInputStream fis = new FileInputStream(file);\n            ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);\n            byte[] b = new byte[1000];\n            int n;\n            while ((n = fis.read(b)) != -1) {\n                bos.write(b,\n                        0,\n                        n);\n            }\n            fis.close();\n            bos.close();\n            buffer = bos.toByteArray();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return buffer;\n    }\n\n    /**\n     * 读取文件内容到字节数组\n     *\n     * @param file\n     * @return\n     */\n    public static byte[] getFileOutputBytes(File file) {\n        byte[] bytes = null;\n        if (file.exists()) {\n            byte[] buffer;\n            BufferedInputStream bis = null;\n            BufferedOutputStream bos = null;\n            ByteArrayOutputStream byteArrayOutputStream = null;\n            try {\n                bis = new BufferedInputStream(new FileInputStream(file),\n                        DEFAULT_BUFFER_SIZE);\n                byteArrayOutputStream = new ByteArrayOutputStream();\n                bos = new BufferedOutputStream(byteArrayOutputStream,\n                        DEFAULT_BUFFER_SIZE);\n                buffer = new byte[DEFAULT_BUFFER_SIZE];\n                int len;\n                while ((len = bis.read(buffer,\n                        0,\n                        DEFAULT_BUFFER_SIZE)) != -1) {\n                    bos.write(buffer,\n                            0,\n                            len);\n                }\n                bos.flush();\n                bytes = byteArrayOutputStream.toByteArray();\n            } catch (Exception e) {\n                return null;\n            } finally {\n                try {\n                    if (bos != null) {\n                        bos.close();\n                    }\n                    if (byteArrayOutputStream != null) {\n                        byteArrayOutputStream.close();\n                    }\n                    if (bis != null) {\n                        bis.close();\n                    }\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        Log.d(TAG,\n                \"getFileOutputBytes.file = \" + file + \", bytes.length = \" + (bytes == null ? 0 : bytes.length));\n        return bytes;\n    }\n\n    /**\n     * 读取文件内容到字节数组\n     *\n     * @param file\n     * @param offset\n     * @param len\n     * @return\n     */\n    public static byte[] getFileOutputBytes(File file,\n                                            long offset,\n                                            long len) {\n        byte[] bytes = null;\n        if (file.exists() && offset >= 0 && len > offset && offset < file.length()) {\n            RandomAccessFile raf = null;\n            ByteArrayOutputStream bos = null;\n            try {\n                raf = new RandomAccessFile(file,\n                        \"r\");\n                raf.seek(offset);\n                bos = new ByteArrayOutputStream();\n                int b;\n                long count = offset;\n                while ((b = raf.read()) != -1 && count < len) {\n                    bos.write(b);\n                    count++;\n                }\n                bos.flush();\n                bytes = bos.toByteArray();\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                try {\n                    if (raf != null) {\n                        raf.close();\n                    }\n                    if (bos != null) {\n                        bos.close();\n                    }\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        return bytes;\n    }\n\n\n    /**\n     * 读取指定文件的输出\n     */\n    public static String getFileOutputString(String path) {\n        try {\n            BufferedReader bufferedReader = new BufferedReader(new FileReader(path), 8192);\n            StringBuilder sb = new StringBuilder();\n            String line;\n            while ((line = bufferedReader.readLine()) != null) {\n                sb.append(\"\\n\").append(line);\n            }\n            bufferedReader.close();\n            return sb.toString();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n\n    /**\n     * 读取指定文件的输出\n     *\n     * @param file\n     * @param encoding\n     * @return\n     */\n    public static String getFileOutputString(File file,\n                                             String encoding) {\n        String result = null;\n        if (file.exists()) {\n            char[] buffer;\n            BufferedReader br = null;\n            InputStreamReader isr = null;\n            BufferedWriter bw = null;\n            StringWriter sw = new StringWriter();\n            try {\n                isr = encoding == null ? new InputStreamReader(new FileInputStream(file)) : new InputStreamReader(new FileInputStream(file),\n                        encoding);\n                br = new BufferedReader(isr);\n                bw = new BufferedWriter(sw);\n                buffer = new char[DEFAULT_BUFFER_SIZE];\n                int len;\n                while ((len = br.read(buffer,\n                        0,\n                        DEFAULT_BUFFER_SIZE)) != -1) {\n                    bw.write(buffer,\n                            0,\n                            len);\n                }\n                bw.flush();\n                result = sw.toString();\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                try {\n                    if (bw != null) {\n                        bw.close();\n                    }\n                    if (br != null) {\n                        br.close();\n                    }\n                    if (isr != null) {\n                        isr.close();\n                    }\n                    sw.close();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        Log.d(TAG,\n                \"readFileToString.file = \" + file + \", encoding = \" + encoding + \", result = \" + result);\n        return result;\n    }\n\n\n    /**\n     * 将字节写入到文件\n     *\n     * @param file\n     * @param bytes\n     * @param offset\n     * @return\n     */\n    public static boolean writeBytesToFile(File file,\n                                           byte[] bytes,\n                                           long offset) {\n        boolean isOk = false;\n        if (!file.exists()) {\n            try {\n                // noinspection ResultOfMethodCallIgnored\n                file.createNewFile();\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        if (file.exists() && bytes != null && offset >= 0) {\n            RandomAccessFile raf = null;\n            try {\n                raf = new RandomAccessFile(file,\n                        \"rw\");\n                raf.seek(offset);\n                raf.write(bytes);\n                isOk = true;\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                try {\n                    if (raf != null) {\n                        raf.close();\n                    }\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n        return isOk;\n    }\n\n    /**\n     * 写字符串到文件，文件父目录如果不存在，会自动创建\n     *\n     * @param file\n     * @param content\n     * @return\n     */\n    public static boolean writeStringToFile(File file,\n                                            String content) {\n        return writeStringToFile(file,\n                content,\n                false);\n    }\n\n    /**\n     * 写字符串到文件，文件父目录如果不存在，会自动创建\n     *\n     * @param file\n     * @param content\n     * @param isAppend\n     * @return\n     */\n    public static boolean writeStringToFile(File file,\n                                            String content,\n                                            boolean isAppend) {\n        boolean isWriteOk = false;\n        if (TextUtils.isEmpty(content))\n            return false;\n\n        char[] buffer;\n        int count = 0;\n        BufferedReader br = null;\n        BufferedWriter bw = null;\n        try {\n            if (!file.exists()) {\n                createNewFileAndParentDir(file);\n            }\n            if (file.exists()) {\n                br = new BufferedReader(new StringReader(content));\n                bw = new BufferedWriter(new FileWriter(file,\n                        isAppend));\n                buffer = new char[DEFAULT_BUFFER_SIZE];\n                int len;\n                while ((len = br.read(buffer,\n                        0,\n                        DEFAULT_BUFFER_SIZE)) != -1) {\n                    bw.write(buffer,\n                            0,\n                            len);\n                    count += len;\n                }\n                bw.flush();\n            }\n            isWriteOk = content.length() == count;\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (bw != null) {\n                    bw.close();\n                }\n                if (br != null) {\n                    br.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n\n        return isWriteOk;\n    }\n\n    /**\n     * 写字节数组到文件，文件父目录如果不存在，会自动创建\n     *\n     * @param file\n     * @param bytes\n     * @return\n     */\n    public static boolean writeBytesToFile(File file,\n                                           byte[] bytes) {\n        return writeBytesToFile(file,\n                bytes,\n                false);\n    }\n\n    /**\n     * 写字节数组到文件，文件父目录如果不存在，会自动创建\n     *\n     * @param file\n     * @param bytes\n     * @param isAppend\n     * @return\n     */\n    public static boolean writeBytesToFile(File file,\n                                           byte[] bytes,\n                                           boolean isAppend) {\n        boolean isWriteOk = false;\n        byte[] buffer;\n        int count = 0;\n        ByteArrayInputStream byteArrayInputStream = null;\n        BufferedInputStream bis = null;\n        BufferedOutputStream bos = null;\n        try {\n            if (!file.exists()) {\n                createNewFileAndParentDir(file);\n            }\n            if (file.exists()) {\n                bos = new BufferedOutputStream(new FileOutputStream(file,\n                        isAppend),\n                        DEFAULT_BUFFER_SIZE);\n                byteArrayInputStream = new ByteArrayInputStream(bytes);\n                bis = new BufferedInputStream(byteArrayInputStream,\n                        DEFAULT_BUFFER_SIZE);\n                buffer = new byte[DEFAULT_BUFFER_SIZE];\n                int len;\n                while ((len = bis.read(buffer,\n                        0,\n                        DEFAULT_BUFFER_SIZE)) != -1) {\n                    bos.write(buffer,\n                            0,\n                            len);\n                    count += len;\n                }\n                bos.flush();\n            }\n            isWriteOk = bytes.length == count;\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (bos != null) {\n                    bos.close();\n                }\n                if (bis != null) {\n                    bis.close();\n                }\n                if (byteArrayInputStream != null) {\n                    byteArrayInputStream.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        Log.d(TAG,\n                \"writeByteArrayToFile.file = \" + file + \", bytes.length = \" + (bytes == null ? 0 : bytes.length) + \", isAppend = \" + isAppend + \", isWriteOk = \" + isWriteOk);\n        return isWriteOk;\n    }\n\n    /**\n     * ********************************************创建***************************\n     * /**\n     * 创建目录\n     *\n     * @param dir\n     * @return\n     */\n    public static boolean createDir(File dir) {\n        boolean isMkdirs = true;\n        if (!dir.exists()) {\n            isMkdirs = dir.mkdirs();\n            Log.d(TAG,\n                    \"createDir = \" + dir + \", isMkdirs = \" + isMkdirs);\n        }\n        return isMkdirs;\n    }\n\n    /**\n     * 创建文件父目录\n     *\n     * @param file\n     * @return\n     */\n    public static boolean createParentDir(File file) {\n        boolean isMkdirs = true;\n        if (!file.exists()) {\n            File dir = file.getParentFile();\n            isMkdirs = createDir(dir);\n        }\n        return isMkdirs;\n    }\n\n    /**\n     * 创建文件及其父目录\n     *\n     * @param file\n     * @return\n     */\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public static boolean createNewFileAndParentDir(File file) {\n        @SuppressWarnings(\"UnusedAssignment\")\n        boolean isCreateNewFileOk = true;\n        isCreateNewFileOk = createParentDir(file);\n        // 创建父目录失败，直接返回false，不再创建子文件\n        if (isCreateNewFileOk) {\n            if (!file.exists()) {\n                try {\n                    isCreateNewFileOk = file.createNewFile();\n                } catch (IOException e) {\n                    e.printStackTrace();\n                    isCreateNewFileOk = false;\n                }\n            }\n        }\n\n        return isCreateNewFileOk;\n    }\n\n    /**\n     * 根据文件名称获取文件的后缀字符串\n     *\n     * @param filename 文件的名称,可能带路径\n     * @return 文件的后缀字符串\n     */\n    public static String getFileExtensionFromUrl(String filename) {\n        if (!TextUtils.isEmpty(filename)) {\n            int dotPos = filename.lastIndexOf('.');\n            if (0 <= dotPos) {\n                return filename.substring(dotPos + 1);\n            }\n        }\n        return \"\";\n    }\n\n    /**\n     * 拷贝文件\n     *\n     * @param sourceFile 源文件\n     * @param destFile   目标文件\n     * @return 是否拷贝成功\n     */\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public static boolean copyFile(File sourceFile,\n                                   File destFile) {\n        boolean isCopyOk = false;\n        byte[] buffer;\n        BufferedInputStream bis = null;\n        BufferedOutputStream bos = null;\n        // 如果此时没有文件夹目录就创建\n        String canonicalPath = \"\";\n        try {\n            canonicalPath = destFile.getCanonicalPath();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        if (!destFile.exists()) {\n            if (canonicalPath.lastIndexOf(File.separator) >= 0) {\n                canonicalPath = canonicalPath.substring(0,\n                        canonicalPath.lastIndexOf(File.separator));\n                File dir = new File(canonicalPath);\n                if (!dir.exists()) {\n                    dir.mkdirs();\n                }\n            }\n        }\n\n        try {\n            bis = new BufferedInputStream(new FileInputStream(sourceFile),\n                    DEFAULT_BUFFER_SIZE);\n            bos = new BufferedOutputStream(new FileOutputStream(destFile),\n                    DEFAULT_BUFFER_SIZE);\n            buffer = new byte[DEFAULT_BUFFER_SIZE];\n            int len;\n            while ((len = bis.read(buffer,\n                    0,\n                    DEFAULT_BUFFER_SIZE)) != -1) {\n                bos.write(buffer,\n                        0,\n                        len);\n            }\n            bos.flush();\n            isCopyOk = sourceFile.length() == destFile.length();\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (bos != null) {\n                    bos.close();\n                }\n                if (bis != null) {\n                    bis.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        Log.d(TAG,\n                \"copyFile.sourceFile = \" + sourceFile + \", destFile = \" + destFile + \", isCopyOk = \" + isCopyOk);\n        return isCopyOk;\n    }\n\n    /*** 获取文件夹大小 ***/\n    public static long getFileSize(File f) {\n        long size = 0;\n        try {\n            File fList[] = f.listFiles();\n            for (File file : fList) {\n                if (file.isDirectory()) {\n                    size = size + getFileSize(file);\n                } else {\n                    size = size + file.length();\n                }\n            }\n            return size;\n        } catch (Exception e) {\n            return size;\n        }\n    }\n\n    /**\n     * 获取内存设置\n     *\n     * @param context\n     * @return\n     */\n    public static int getMemoryCacheSize(Context context) {\n        int memoryCacheSize = 0;\n        ActivityManager manager = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE));\n\n        int memClass;\n        if (manager != null){\n            memClass = manager.getMemoryClass();\n            memoryCacheSize = (memClass / 3) * 1024 * 1024; // 1/3 of app memory\n        }\n        // limit\n        return memoryCacheSize;\n    }\n\n   /* /**\n     * 保存APP资源文件到文件系统\n     *\n     * @param resID      drawable 资源文件\n     * @param defineName 定义的文件名\n     * @return 文件路径\n     */\n    /*public static String saveResImgToFile(int resID,\n                                          String defineName) {\n        String path = null;\n        try {\n            File Dir = FileConfig.getScreenShortCutDir();\n            path = Dir.getAbsolutePath() + File.separator + defineName;\n            File file = new File(path);\n            // 文件已存在\n            Bitmap bitmap = BitmapFactory.decodeResource(WithinApplication.getAppContext()\n                            .getResources(),\n                    resID);\n            boolean isCompressed = ImageUtil.compressSmallImage(bitmap,\n                    file,\n                    FileConfig.FEED_TEMP_PIC_MAX_SIZE);\n            if (!bitmap.isRecycled()) {\n                bitmap.recycle();\n            }\n            if (isCompressed) {\n                return file.getAbsolutePath();\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return path;\n    }\n            */\n\n    /**\n     * 如果存在就文件名追加（count），如report（1）\n     *\n     * @param file\n     * @return\n     */\n    public static File formatDownloadFileName(File file) {\n        if (file.getName()\n                .contains(\".\")) {\n            String postfix = file.getName()\n                    .substring(file.getName()\n                            .lastIndexOf(\".\"));\n            if (file.exists() && file.getParentFile()\n                    .exists()) {\n                int count = 0;\n                for (File child : file.getParentFile()\n                        .listFiles()) {\n                    if (child.getName()\n                            .contains(file.getName()\n                                    .replace(postfix,\n                                            \"\"))) {\n                        count++;\n                    }\n                }\n                String fileName = String.format(file.getName()\n                                .replace(postfix,\n                                        \"\") +\n                                \"(%d)\",\n                        count) +\n                        postfix;\n                file = new File(file.getParentFile(),\n                        fileName);\n            }\n        }\n        return file;\n    }\n\n\n    public static Intent getFileIntent(File file) {\n        Uri uri = Uri.fromFile(file);\n        String type = getMIMEType(file);\n        Log.i(\"tag\",\n                \"type=\" + type);\n        Intent intent = new Intent(\"android.intent.action.VIEW\");\n        intent.addCategory(\"android.intent.category.DEFAULT\");\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        intent.setDataAndType(uri,\n                type);\n        return intent;\n    }\n\n    public static String getMIMEType(File f) {\n        @SuppressWarnings(\"UnusedAssignment\")\n        String type = \"\";\n        String fName = f.getName();\n        /* 取得扩展名 */\n        String end = fName.substring(fName.lastIndexOf(\".\") + 1,\n                fName.length())\n                .toLowerCase();\n\n        /* 依扩展名的类型决定MimeType */\n        switch (end) {\n            case \"pdf\":\n                type = \"application/pdf\";//\n\n                break;\n            case \"m4a\":\n            case \"mp3\":\n            case \"mid\":\n            case \"xmf\":\n            case \"ogg\":\n            case \"wav\":\n                type = \"audio/*\";\n                break;\n            case \"3gp\":\n            case \"mp4\":\n                type = \"video/*\";\n                break;\n            case \"jpg\":\n            case \"gif\":\n            case \"png\":\n            case \"jpeg\":\n            case \"bmp\":\n                type = \"image/*\";\n                break;\n            case \"apk\":\n            /* android.permission.INSTALL_PACKAGES */\n                type = \"application/vnd.android.package-archive\";\n                break;\n            case \"pptx\":\n            case \"ppt\":\n                type = \"application/vnd.ms-powerpoint\";\n                break;\n            case \"docx\":\n            case \"doc\":\n                type = \"application/vnd.ms-word\";\n                break;\n            case \"xlsx\":\n            case \"xls\":\n                type = \"application/vnd.ms-excel\";\n                break;\n            case \"txt\":\n                type = \"text/plain\";\n                break;\n            default:\n            /* 如果无法直接打开，就跳出软件列表给用户选择 */\n                type = \"*/*\";\n                break;\n        }\n        return type;\n    }\n\n    public static int clearCacheFolder(final File dir,\n                                       final int numDays) {\n\n        int deletedFiles = 0;\n        if (dir != null && dir.isDirectory()) {\n            try {\n                for (File child : dir.listFiles()) {\n\n                    // first delete subdirectories recursively\n                    if (child.isDirectory()) {\n                        deletedFiles += clearCacheFolder(child,\n                                numDays);\n                    }\n\n                    // then delete the files and subdirectories in this dir\n                    // only empty directories can be deleted, so subDirs have\n                    // been done first\n                    if (child.lastModified() < new Date().getTime() - numDays * DateUtils.DAY_IN_MILLIS) {\n                        if (child.delete()) {\n                            deletedFiles++;\n                        }\n                    }\n                }\n            } catch (Exception e) {\n\n            }\n        }\n        return deletedFiles;\n    }\n\n\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    public static String getDiskCacheDir(Context context,\n                                         String uniqueName) {\n        // 检查是否安装或存储媒体是内置的,如果是这样的话,试着使用外部缓存dir否则使用内部缓存目录\n        boolean isMounted = SdCardUtil.isSdCardAvailable();\n        File cacheFile;\n        if (isMounted) {\n            cacheFile = getExternalCacheDir(context);\n            if (!cacheFile.exists()) {\n                cacheFile.mkdirs();\n            }\n            if (!cacheFile.canWrite()) {\n                cacheFile = context.getCacheDir();\n            }\n        } else {\n            cacheFile = context.getCacheDir();\n        }\n        File file = new File(cacheFile.getPath() + File.separator + uniqueName);\n        if (!file.exists()) {\n            file.mkdirs();\n        }\n        return file.getAbsolutePath();\n    }\n\n    /**\n     * 获取外设的缓存目录：Android/data/cache....<br>\n     * 在Froyo之前需要自己建立缓存目录\n     */\n    public static File getExternalCacheDir(Context context) {\n\n        if (SdCardUtil.isSdCardAvailable()) {\n\n            return context.getExternalCacheDir();\n\n\n        } else {\n            // 使用data/data文件夹\n            return context.getCacheDir();\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/io/IOUtils.java",
    "content": "\npackage com.lib.aliocr.utils.io;\n\nimport android.annotation.SuppressLint;\nimport android.os.Build;\nimport android.util.Log;\n\nimport com.lib.aliocr.utils.stream.ByteArrayOutputStream;\nimport com.lib.aliocr.utils.stream.StringBuilderWriter;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedReader;\nimport java.io.ByteArrayInputStream;\nimport java.io.CharArrayWriter;\nimport java.io.Closeable;\nimport java.io.EOFException;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.PrintWriter;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.io.UnsupportedEncodingException;\nimport java.io.Writer;\nimport java.net.HttpURLConnection;\nimport java.net.ServerSocket;\nimport java.net.Socket;\nimport java.net.URI;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.nio.channels.Selector;\nimport java.nio.charset.Charset;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\n\n/**\n * General IO stream manipulation utilities.\n * <p/>\n * This class provides static utility methods for input/output operations.\n * <ul>\n * <li>closeQuietly - these methods close a stream ignoring nulls and exceptions\n * <li>toXxx/read - these methods read data from a stream\n * <li>write - these methods write data to a stream\n * <li>copy - these methods copy all the data from one stream to another\n * <li>contentEquals - these methods compare the content of two streams\n * </ul>\n * <p/>\n * The byte-to-char methods and char-to-byte methods involve a conversion step.\n * Two methods are provided in each case, one that uses the platform default\n * encoding and the other which allows you to specify an encoding. You are\n * encouraged to always specify an encoding because relying on the platform\n * default can lead to unexpected results, for example when moving from\n * development to production.\n * <p/>\n * All the methods in this class that read a stream are buffered internally.\n * This means that there is no cause to use a <code>BufferedInputStream</code>\n * or <code>BufferedReader</code>. The default buffer size of 4K has been shown\n * to be efficient in tests.\n * <p/>\n * Wherever possible, the methods in this class do <em>not</em> flush or close\n * the stream. This is to avoid making non-portable assumptions about the\n * streams' origin and further use. Thus the caller is still responsible for\n * closing streams after use.\n * <p/>\n * Origin of code: Excalibur.\n *\n * @version $Id: IOUtils.java 1326636 2012-04-16 14:54:53Z ggregory $\n */\n@SuppressWarnings(\"WeakerAccess\")\npublic class IOUtils {\n    // NOTE: This class is focussed on InputStream, OutputStream, Reader and\n    // Writer. Each method should take at least one of these as a parameter,\n    // or return one of them.\n\n    private static final int EOF = -1;\n    /**\n     * The Unix directory separator character.\n     */\n    public static final char DIR_SEPARATOR_UNIX = '/';\n    /**\n     * The Windows directory separator character.\n     */\n    public static final char DIR_SEPARATOR_WINDOWS = '\\\\';\n    /**\n     * The system directory separator character.\n     */\n    public static final char DIR_SEPARATOR = File.separatorChar;\n    /**\n     * The Unix line separator string.\n     */\n    public static final String LINE_SEPARATOR_UNIX = \"\\n\";\n    /**\n     * The Windows line separator string.\n     */\n    public static final String LINE_SEPARATOR_WINDOWS = \"\\r\\n\";\n    /**\n     * The system line separator string.\n     */\n    public static final String LINE_SEPARATOR;\n\n    static {\n        // avoid security issues\n        StringBuilderWriter buf = new StringBuilderWriter(4);\n        PrintWriter out = new PrintWriter(buf);\n        out.println();\n        LINE_SEPARATOR = buf.toString();\n        out.close();\n    }\n\n    /**\n     * The default buffer size ({@value}) to use for\n     * {@link #copyLarge(InputStream, OutputStream)}\n     * and\n     * {@link #copyLarge(Reader, Writer)}\n     */\n    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;\n\n    /**\n     * The default buffer size to use for the skip() methods.\n     */\n    private static final int SKIP_BUFFER_SIZE = 2048;\n\n    // Allocated in the relevant skip method if necessary.\n    /*\n     * N.B. no need to synchronize these because:\n     * - we don't care if the buffer is created multiple times (the data is ignored)\n     * - we always use the same size buffer, so if it it is recreated it will still be OK\n     * (if the buffer size were variable, we would need to sync. to ensure some other thread\n     * did not create a smaller one)\n     */\n    private static char[] SKIP_CHAR_BUFFER;\n    private static byte[] SKIP_BYTE_BUFFER;\n\n    /**\n     * Instances should NOT be constructed in standard programming.\n     */\n    public IOUtils() {\n        super();\n    }\n\n    //-----------------------------------------------------------------------\n\n    /**\n     * Closes a URLConnection.\n     *\n     * @param conn the connection to close.\n     * @since 2.4\n     */\n    public static void close(URLConnection conn) {\n        if (conn instanceof HttpURLConnection) {\n            ((HttpURLConnection) conn).disconnect();\n        }\n    }\n\n    /**\n     * Unconditionally close an <code>Reader</code>.\n     * <p/>\n     * Equivalent to {@link Reader#close()}, except any exceptions will be ignored.\n     * This is typically used in finally blocks.\n     * <p/>\n     * Example code:\n     * <pre>\n     *   char[] data = new char[1024];\n     *   Reader in = null;\n     *   try {\n     *       in = new FileReader(\"foo.txt\");\n     *       in.read(data);\n     *       in.close(); //close errors are handled\n     *   } catch (Exception e) {\n     *       // error handling\n     *   } finally {\n     *       IOUtils.closeQuietly(in);\n     *   }\n     * </pre>\n     *\n     * @param input the Reader to close, may be null or already closed\n     */\n    public static void closeQuietly(Reader input) {\n        closeQuietly((Closeable) input);\n    }\n\n    /**\n     * Unconditionally close a <code>Writer</code>.\n     * <p/>\n     * Equivalent to {@link Writer#close()}, except any exceptions will be ignored.\n     * This is typically used in finally blocks.\n     * <p/>\n     * Example code:\n     * <pre>\n     *   Writer out = null;\n     *   try {\n     *       out = new StringWriter();\n     *       out.write(\"Hello World\");\n     *       out.close(); //close errors are handled\n     *   } catch (Exception e) {\n     *       // error handling\n     *   } finally {\n     *       IOUtils.closeQuietly(out);\n     *   }\n     * </pre>\n     *\n     * @param output the Writer to close, may be null or already closed\n     */\n    public static void closeQuietly(Writer output) {\n        closeQuietly((Closeable) output);\n    }\n\n    /**\n     * Unconditionally close an <code>InputStream</code>.\n     * <p/>\n     * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored.\n     * This is typically used in finally blocks.\n     * <p/>\n     * Example code:\n     * <pre>\n     *   byte[] data = new byte[1024];\n     *   InputStream in = null;\n     *   try {\n     *       in = new FileInputStream(\"foo.txt\");\n     *       in.read(data);\n     *       in.close(); //close errors are handled\n     *   } catch (Exception e) {\n     *       // error handling\n     *   } finally {\n     *       IOUtils.closeQuietly(in);\n     *   }\n     * </pre>\n     *\n     * @param input the InputStream to close, may be null or already closed\n     */\n    public static void closeQuietly(InputStream input) {\n        closeQuietly((Closeable) input);\n    }\n\n    /**\n     * Unconditionally close an <code>OutputStream</code>.\n     * <p/>\n     * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored.\n     * This is typically used in finally blocks.\n     * <p/>\n     * Example code:\n     * <pre>\n     * byte[] data = \"Hello, World\".getBytes();\n     *\n     * OutputStream out = null;\n     * try {\n     *     out = new FileOutputStream(\"foo.txt\");\n     *     out.write(data);\n     *     out.close(); //close errors are handled\n     * } catch (IOException e) {\n     *     // error handling\n     * } finally {\n     *     IOUtils.closeQuietly(out);\n     * }\n     * </pre>\n     *\n     * @param output the OutputStream to close, may be null or already closed\n     */\n    public static void closeQuietly(OutputStream output) {\n        closeQuietly((Closeable) output);\n    }\n\n    /**\n     * Unconditionally close a <code>Closeable</code>.\n     * <p/>\n     * Equivalent to {@link Closeable#close()}, except any exceptions will be ignored.\n     * This is typically used in finally blocks.\n     * <p/>\n     * Example code:\n     * <pre>\n     *   Closeable closeable = null;\n     *   try {\n     *       closeable = new FileReader(\"foo.txt\");\n     *       // process closeable\n     *       closeable.close();\n     *   } catch (Exception e) {\n     *       // error handling\n     *   } finally {\n     *       IOUtils.closeQuietly(closeable);\n     *   }\n     * </pre>\n     *\n     * @param closeable the object to close, may be null or already closed\n     * @since 2.0\n     */\n    public static void closeQuietly(Closeable closeable) {\n        try {\n            if (closeable != null) {\n                closeable.close();\n            }\n        } catch (IOException ioe) {\n            // ignore\n        }\n    }\n\n    /**\n     * Unconditionally close a <code>Socket</code>.\n     * <p/>\n     * Equivalent to {@link Socket#close()}, except any exceptions will be ignored.\n     * This is typically used in finally blocks.\n     * <p/>\n     * Example code:\n     * <pre>\n     *   Socket socket = null;\n     *   try {\n     *       socket = new Socket(\"http://www.foo.com/\", 80);\n     *       // process socket\n     *       socket.close();\n     *   } catch (Exception e) {\n     *       // error handling\n     *   } finally {\n     *       IOUtils.closeQuietly(socket);\n     *   }\n     * </pre>\n     *\n     * @param sock the Socket to close, may be null or already closed\n     * @since 2.0\n     */\n    public static void closeQuietly(Socket sock) {\n        if (sock != null) {\n            try {\n                sock.close();\n            } catch (IOException ioe) {\n                // ignored\n            }\n        }\n    }\n\n    /**\n     * Unconditionally close a <code>Selector</code>.\n     * <p/>\n     * Equivalent to {@link Selector#close()}, except any exceptions will be ignored.\n     * This is typically used in finally blocks.\n     * <p/>\n     * Example code:\n     * <pre>\n     *   Selector selector = null;\n     *   try {\n     *       selector = Selector.open();\n     *       // process socket\n     *\n     *   } catch (Exception e) {\n     *       // error handling\n     *   } finally {\n     *       IOUtils.closeQuietly(selector);\n     *   }\n     * </pre>\n     *\n     * @param selector the Selector to close, may be null or already closed\n     * @since 2.2\n     */\n    public static void closeQuietly(Selector selector) {\n        if (selector != null) {\n            try {\n                selector.close();\n            } catch (IOException ioe) {\n                // ignored\n            }\n        }\n    }\n\n    /**\n     * Unconditionally close a <code>ServerSocket</code>.\n     * <p/>\n     * Equivalent to {@link ServerSocket#close()}, except any exceptions will be ignored.\n     * This is typically used in finally blocks.\n     * <p/>\n     * Example code:\n     * <pre>\n     *   ServerSocket socket = null;\n     *   try {\n     *       socket = new ServerSocket();\n     *       // process socket\n     *       socket.close();\n     *   } catch (Exception e) {\n     *       // error handling\n     *   } finally {\n     *       IOUtils.closeQuietly(socket);\n     *   }\n     * </pre>\n     *\n     * @param sock the ServerSocket to close, may be null or already closed\n     * @since 2.2\n     */\n    public static void closeQuietly(ServerSocket sock) {\n        if (sock != null) {\n            try {\n                sock.close();\n            } catch (IOException ioe) {\n                // ignored\n            }\n        }\n    }\n\n    /**\n     * Fetches entire contents of an <code>InputStream</code> and represent\n     * same data as result InputStream.\n     * <p/>\n     * This method is useful where,\n     * <ul>\n     * <li>Source InputStream is slow.</li>\n     * <li>It has network resources associated, so we cannot keep it open for\n     * long time.</li>\n     * <li>It has network timeout associated.</li>\n     * </ul>\n     * It can be used in favor of {@link #toByteArray(InputStream)}, since it\n     * avoids unnecessary allocation and copy of byte[].<br>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param input Stream to be fully buffered.\n     * @return A fully buffered stream.\n     * @throws IOException if an I/O error occurs\n     * @since 2.0\n     */\n    public static InputStream toBufferedInputStream(InputStream input) throws IOException {\n        return  ByteArrayOutputStream.toBufferedInputStream(input);\n    }\n\n    /**\n     * Returns the given reader if it is a {@link BufferedReader}, otherwise creates a toBufferedReader for the given\n     * reader.\n     *\n     * @param reader the reader to wrap or return\n     * @return the given reader or a new {@link BufferedReader} for the given reader\n     * @since 2.2\n     */\n    public static BufferedReader toBufferedReader(Reader reader) {\n        return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader);\n    }\n\n    // read toByteArray\n    //-----------------------------------------------------------------------\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param input the <code>InputStream</code> to read from\n     * @return the requested byte array\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     */\n    public static byte[] toByteArray(InputStream input) throws IOException {\n        ByteArrayOutputStream output =\n                new  ByteArrayOutputStream();\n        copy(input, output);\n        return output.toByteArray();\n    }\n\n    public static byte[] toByteArray(Object input) {\n         ByteArrayOutputStream byteArrayOutputStream = null;\n        ObjectOutputStream oos = null;\n        try {\n            byteArrayOutputStream = new  ByteArrayOutputStream();\n            oos = new ObjectOutputStream(byteArrayOutputStream);\n            oos.writeObject(input);\n            oos.flush();\n            return byteArrayOutputStream.toByteArray();\n        } catch (IOException e) {\n            Log.e(\"\", \"toByteArray\");\n        } finally {\n            IOUtils.closeQuietly(oos);\n            IOUtils.closeQuietly(byteArrayOutputStream);\n        }\n        return null;\n    }\n\n    /**\n     * Get contents of an <code>InputStream</code> as a <code>byte[]</code>.\n     * Use this method instead of <code>toByteArray(InputStream)</code>\n     * when <code>InputStream</code> size is known.\n     * <b>NOTE:</b> the method checks that the length can safely be cast to an int without truncation\n     * before using {@link IOUtils#toByteArray(InputStream, int)} to read into the byte array.\n     * (Arrays can have no more than Integer.MAX_VALUE entries anyway)\n     *\n     * @param input the <code>InputStream</code> to read from\n     * @param size  the size of <code>InputStream</code>\n     * @return the requested byte array\n     * @throws IOException              if an I/O error occurs or <code>InputStream</code> size differ from parameter size\n     * @throws IllegalArgumentException if size is less than zero or size is greater than Integer.MAX_VALUE\n     * @see IOUtils#toByteArray(InputStream, int)\n     * @since 2.1\n     */\n    public static byte[] toByteArray(InputStream input, long size) throws IOException {\n\n        if (size > Integer.MAX_VALUE) {\n            throw new IllegalArgumentException(\"Size cannot be greater than Integer max value: \" + size);\n        }\n\n        return toByteArray(input, (int) size);\n    }\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.\n     * Use this method instead of <code>toByteArray(InputStream)</code>\n     * when <code>InputStream</code> size is known\n     *\n     * @param input the <code>InputStream</code> to read from\n     * @param size  the size of <code>InputStream</code>\n     * @return the requested byte array\n     * @throws IOException              if an I/O error occurs or <code>InputStream</code> size differ from parameter size\n     * @throws IllegalArgumentException if size is less than zero\n     * @since 2.1\n     */\n    public static byte[] toByteArray(InputStream input, int size) throws IOException {\n\n        if (size < 0) {\n            throw new IllegalArgumentException(\"Size must be equal or greater than zero: \" + size);\n        }\n\n        if (size == 0) {\n            return new byte[0];\n        }\n\n        byte[] data = new byte[size];\n        int offset = 0;\n        int read;\n\n        while (offset < size && (read = input.read(data, offset, size - offset)) != EOF) {\n            offset += read;\n        }\n\n        if (offset != size) {\n            throw new IOException(\"Unexpected read size. current: \" + offset + \", excepted: \" + size);\n        }\n\n        return data;\n    }\n\n    /**\n     * Get the contents of a <code>Reader</code> as a <code>byte[]</code>\n     * using the default character encoding of the platform.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     *\n     * @param input the <code>Reader</code> to read from\n     * @return the requested byte array\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     */\n    public static byte[] toByteArray(Reader input) throws IOException {\n        return toByteArray(input, Charset.defaultCharset());\n    }\n\n    /**\n     * Get the contents of a <code>Reader</code> as a <code>byte[]</code>\n     * using the specified character encoding.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     *\n     * @param input    the <code>Reader</code> to read from\n     * @param encoding the encoding to use, null means platform default\n     * @return the requested byte array\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static byte[] toByteArray(Reader input, Charset encoding) throws IOException {\n       ByteArrayOutputStream output = new  ByteArrayOutputStream();\n        copy(input, output, encoding);\n        return output.toByteArray();\n    }\n\n    /**\n     * Get the contents of a <code>Reader</code> as a <code>byte[]</code>\n     * using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa.org</a>.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     *\n     * @param input    the <code>Reader</code> to read from\n     * @param encoding the encoding to use, null means platform default\n     * @return the requested byte array\n     * @throws NullPointerException                         if the input is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    public static byte[] toByteArray(Reader input, String encoding) throws IOException {\n        return toByteArray(input, Charsets.toCharset(encoding));\n    }\n\n    /**\n     * Get the contents of a <code>String</code> as a <code>byte[]</code>\n     * using the default character encoding of the platform.\n     * <p/>\n     * This is the same as {@link String#getBytes()}.\n     *\n     * @param input the <code>String</code> to convert\n     * @return the requested byte array\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs (never occurs)\n     * @deprecated Use {@link String#getBytes()}\n     */\n    @Deprecated\n    public static byte[] toByteArray(String input) throws IOException {\n        return input.getBytes();\n    }\n\n    /**\n     * Get the contents of a <code>URI</code> as a <code>byte[]</code>.\n     *\n     * @param uri the <code>URI</code> to read\n     * @return the requested byte array\n     * @throws NullPointerException if the uri is null\n     * @throws IOException          if an I/O exception occurs\n     * @since 2.4\n     */\n    public static byte[] toByteArray(URI uri) throws IOException {\n        return IOUtils.toByteArray(uri.toURL());\n    }\n\n    /**\n     * Get the contents of a <code>URL</code> as a <code>byte[]</code>.\n     *\n     * @param url the <code>URL</code> to read\n     * @return the requested byte array\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O exception occurs\n     * @since 2.4\n     */\n    public static byte[] toByteArray(URL url) throws IOException {\n        URLConnection conn = url.openConnection();\n        try {\n            return IOUtils.toByteArray(conn);\n        } finally {\n            close(conn);\n        }\n    }\n\n    /**\n     * Get the contents of a <code>URLConnection</code> as a <code>byte[]</code>.\n     *\n     * @param urlConn the <code>URLConnection</code> to read\n     * @return the requested byte array\n     * @throws NullPointerException if the urlConn is null\n     * @throws IOException          if an I/O exception occurs\n     * @since 2.4\n     */\n    public static byte[] toByteArray(URLConnection urlConn) throws IOException {\n        InputStream inputStream = urlConn.getInputStream();\n        try {\n            return IOUtils.toByteArray(inputStream);\n        } catch (Exception e) {\n             Log.e(\"\", \"\");\n        } finally {\n            inputStream.close();\n        }\n        return new byte[0];\n    }\n\n    // read char[]\n    //-----------------------------------------------------------------------\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a character array\n     * using the default character encoding of the platform.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param is the <code>InputStream</code> to read from\n     * @return the requested character array\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static char[] toCharArray(InputStream is) throws IOException {\n        return toCharArray(is, Charset.defaultCharset());\n    }\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a character array\n     * using the specified character encoding.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param is       the <code>InputStream</code> to read from\n     * @param encoding the encoding to use, null means platform default\n     * @return the requested character array\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static char[] toCharArray(InputStream is, Charset encoding)\n            throws IOException {\n        CharArrayWriter output = new CharArrayWriter();\n        copy(is, output, encoding);\n        return output.toCharArray();\n    }\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a character array\n     * using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param is       the <code>InputStream</code> to read from\n     * @param encoding the encoding to use, null means platform default\n     * @return the requested character array\n     * @throws NullPointerException                         if the input is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    public static char[] toCharArray(InputStream is, String encoding) throws IOException {\n        return toCharArray(is, Charsets.toCharset(encoding));\n    }\n\n    /**\n     * Get the contents of a <code>Reader</code> as a character array.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     *\n     * @param input the <code>Reader</code> to read from\n     * @return the requested character array\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static char[] toCharArray(Reader input) throws IOException {\n        CharArrayWriter sw = new CharArrayWriter();\n        copy(input, sw);\n        return sw.toCharArray();\n    }\n\n    // read toString\n    //-----------------------------------------------------------------------\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a String\n     * using the default character encoding of the platform.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param input the <code>InputStream</code> to read from\n     * @return the requested String\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     */\n    public static String toString(InputStream input) throws IOException {\n        return toString(input, Charset.defaultCharset());\n    }\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a String\n     * using the specified character encoding.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * </p>\n     *\n     * @param input    the <code>InputStream</code> to read from\n     * @param encoding the encoding to use, null means platform default\n     * @return the requested String\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static String toString(InputStream input, Charset encoding) throws IOException {\n        StringBuilderWriter sw = new StringBuilderWriter();\n        copy(input, sw, encoding);\n        return sw.toString();\n    }\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a String\n     * using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param input    the <code>InputStream</code> to read from\n     * @param encoding the encoding to use, null means platform default\n     * @return the requested String\n     * @throws NullPointerException                         if the input is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     */\n    public static String toString(InputStream input, String encoding)\n            throws IOException {\n        return toString(input, Charsets.toCharset(encoding));\n    }\n\n    /**\n     * Get the contents of a <code>Reader</code> as a String.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     *\n     * @param input the <code>Reader</code> to read from\n     * @return the requested String\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     */\n    public static String toString(Reader input) throws IOException {\n        StringBuilderWriter sw = new StringBuilderWriter();\n        copy(input, sw);\n        return sw.toString();\n    }\n\n    /**\n     * Gets the contents at the given URI.\n     *\n     * @param uri The URI source.\n     * @return The contents of the URL as a String.\n     * @throws IOException if an I/O exception occurs.\n     * @since 2.1\n     */\n    public static String toString(URI uri) throws IOException {\n        return toString(uri, Charset.defaultCharset());\n    }\n\n    /**\n     * Gets the contents at the given URI.\n     *\n     * @param uri      The URI source.\n     * @param encoding The encoding name for the URL contents.\n     * @return The contents of the URL as a String.\n     * @throws IOException if an I/O exception occurs.\n     * @since 2.3.\n     */\n    public static String toString(URI uri, Charset encoding) throws IOException {\n        return toString(uri.toURL(), Charsets.toCharset(encoding));\n    }\n\n    /**\n     * Gets the contents at the given URI.\n     *\n     * @param uri      The URI source.\n     * @param encoding The encoding name for the URL contents.\n     * @return The contents of the URL as a String.\n     * @throws IOException                                  if an I/O exception occurs.\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 2.1\n     */\n    public static String toString(URI uri, String encoding) throws IOException {\n        return toString(uri, Charsets.toCharset(encoding));\n    }\n\n    /**\n     * Gets the contents at the given URL.\n     *\n     * @param url The URL source.\n     * @return The contents of the URL as a String.\n     * @throws IOException if an I/O exception occurs.\n     * @since 2.1\n     */\n    public static String toString(URL url) throws IOException {\n        return toString(url, Charset.defaultCharset());\n    }\n\n    /**\n     * Gets the contents at the given URL.\n     *\n     * @param url      The URL source.\n     * @param encoding The encoding name for the URL contents.\n     * @return The contents of the URL as a String.\n     * @throws IOException if an I/O exception occurs.\n     * @since 2.3\n     */\n    public static String toString(URL url, Charset encoding) throws IOException {\n        InputStream inputStream = url.openStream();\n        try {\n            return toString(inputStream, encoding);\n        } catch (Exception e) {\n            Log.e(\"\", \"\");\n        } finally {\n            inputStream.close();\n        }\n        return null;\n    }\n\n    /**\n     * Gets the contents at the given URL.\n     *\n     * @param url      The URL source.\n     * @param encoding The encoding name for the URL contents.\n     * @return The contents of the URL as a String.\n     * @throws IOException                                  if an I/O exception occurs.\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 2.1\n     */\n    public static String toString(URL url, String encoding) throws IOException {\n        return toString(url, Charsets.toCharset(encoding));\n    }\n\n    /**\n     * Get the contents of a <code>byte[]</code> as a String\n     * using the default character encoding of the platform.\n     *\n     * @param input the byte array to read from\n     * @return the requested String\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs (never occurs)\n     * @deprecated Use {@link String#String(byte[])}\n     */\n    @Deprecated\n    public static String toString(byte[] input) throws IOException {\n        return new String(input);\n    }\n\n    /**\n     * Get the contents of a <code>byte[]</code> as a String\n     * using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     *\n     * @param input    the byte array to read from\n     * @param encoding the encoding to use, null means platform default\n     * @return the requested String\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs (never occurs)\n     */\n    public static String toString(byte[] input, String encoding) throws IOException {\n        return new String(input, encoding);\n    }\n\n\n\n\n    public static Reader toReader(String str){\n        return new StringReader(str);\n    }\n\n    // readLines\n    //-----------------------------------------------------------------------\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a list of Strings,\n     * one entry per line, using the default character encoding of the platform.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param input the <code>InputStream</code> to read from, not null\n     * @return the list of Strings, never null\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static List<String> readLines(InputStream input) throws IOException {\n        return readLines(input, Charset.defaultCharset());\n    }\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a list of Strings,\n     * one entry per line, using the specified character encoding.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param input    the <code>InputStream</code> to read from, not null\n     * @param encoding the encoding to use, null means platform default\n     * @return the list of Strings, never null\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static List<String> readLines(InputStream input, Charset encoding) throws IOException {\n        InputStreamReader reader = new InputStreamReader(input, Charsets.toCharset(encoding));\n        return readLines(reader);\n    }\n\n    /**\n     * Get the contents of an <code>InputStream</code> as a list of Strings,\n     * one entry per line, using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     *\n     * @param input    the <code>InputStream</code> to read from, not null\n     * @param encoding the encoding to use, null means platform default\n     * @return the list of Strings, never null\n     * @throws NullPointerException                         if the input is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    public static List<String> readLines(InputStream input, String encoding) throws IOException {\n        return readLines(input, Charsets.toCharset(encoding));\n    }\n\n    /**\n     * Get the contents of a <code>Reader</code> as a list of Strings,\n     * one entry per line.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     *\n     * @param input the <code>Reader</code> to read from, not null\n     * @return the list of Strings, never null\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static List<String> readLines(Reader input) throws IOException {\n        BufferedReader reader = toBufferedReader(input);\n        List<String> list = new ArrayList<>();\n        String line = reader.readLine();\n        while (line != null) {\n            list.add(line);\n            line = reader.readLine();\n        }\n        return list;\n    }\n\n    //-----------------------------------------------------------------------\n\n    /**\n     * Convert the specified CharSequence to an input stream, encoded as bytes\n     * using the default character encoding of the platform.\n     *\n     * @param input the CharSequence to convert\n     * @return an input stream\n     * @since 2.0\n     */\n    public static InputStream toInputStream(CharSequence input) {\n        return toInputStream(input, Charset.defaultCharset());\n    }\n\n\n\n\n\n    /**\n     * Convert the specified CharSequence to an input stream, encoded as bytes\n     * using the specified character encoding.\n     *\n     * @param input    the CharSequence to convert\n     * @param encoding the encoding to use, null means platform default\n     * @return an input stream\n     * @since 2.3\n     */\n    public static InputStream toInputStream(CharSequence input, Charset encoding) {\n        return toInputStream(input.toString(), encoding);\n    }\n\n    /**\n     * Convert the specified CharSequence to an input stream, encoded as bytes\n     * using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     *\n     * @param input    the CharSequence to convert\n     * @param encoding the encoding to use, null means platform default\n     * @return an input stream\n     * @throws IOException                                  if the encoding is invalid\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 2.0\n     */\n    @SuppressWarnings(\"RedundantThrows\")\n    public static InputStream toInputStream(CharSequence input, String encoding) throws IOException {\n        return toInputStream(input, Charsets.toCharset(encoding));\n    }\n\n    //-----------------------------------------------------------------------\n\n    /**\n     * Convert the specified string to an input stream, encoded as bytes\n     * using the default character encoding of the platform.\n     *\n     * @param input the string to convert\n     * @return an input stream\n     * @since 1.1\n     */\n    public static InputStream toInputStream(String input) {\n        return toInputStream(input, Charset.defaultCharset());\n    }\n\n    /**\n     * Convert the specified string to an input stream, encoded as bytes\n     * using the specified character encoding.\n     *\n     * @param input    the string to convert\n     * @param encoding the encoding to use, null means platform default\n     * @return an input stream\n     * @since 2.3\n     */\n    public static InputStream toInputStream(String input, Charset encoding) {\n        return new ByteArrayInputStream(StringCodingUtils.getBytes(input, Charsets.toCharset(encoding)));\n    }\n\n    /**\n     * Convert the specified string to an input stream, encoded as bytes\n     * using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     *\n     * @param input    the string to convert\n     * @param encoding the encoding to use, null means platform default\n     * @return an input stream\n     * @throws IOException                                  if the encoding is invalid\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    @SuppressWarnings(\"RedundantThrows\")\n    public static InputStream toInputStream(String input, String encoding) throws IOException {\n        byte[] bytes = StringCodingUtils.getBytes(input, Charsets.toCharset(encoding));\n        return new ByteArrayInputStream(bytes);\n    }\n\n\n    public static Object toObject(byte[] input) {\n        if (input == null) return null;\n        ByteArrayInputStream byteArrayInputStream = null;\n        ObjectInputStream ois = null;\n        try {\n            byteArrayInputStream = new ByteArrayInputStream(input);\n            ois = new ObjectInputStream(byteArrayInputStream);\n            return ois.readObject();\n        } catch (Exception e) {\n            Log.e(\"\", \"\");\n        } finally {\n            IOUtils.closeQuietly(ois);\n            IOUtils.closeQuietly(byteArrayInputStream);\n        }\n        return null;\n    }\n\n\n    // write byte[]\n    //-----------------------------------------------------------------------\n\n    /**\n     * Writes bytes from a <code>byte[]</code> to an <code>OutputStream</code>.\n     *\n     * @param data   the byte array to write, do not modify during output,\n     *               null ignored\n     * @param output the <code>OutputStream</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void write(byte[] data, OutputStream output)\n            throws IOException {\n        if (data != null) {\n            output.write(data);\n        }\n    }\n\n    /**\n     * Writes bytes from a <code>byte[]</code> to chars on a <code>Writer</code>\n     * using the default character encoding of the platform.\n     * <p/>\n     * This method uses {@link String#String(byte[])}.\n     *\n     * @param data   the byte array to write, do not modify during output,\n     *               null ignored\n     * @param output the <code>Writer</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void write(byte[] data, Writer output) throws IOException {\n        write(data, output, Charset.defaultCharset());\n    }\n\n    /**\n     * Writes bytes from a <code>byte[]</code> to chars on a <code>Writer</code>\n     * using the specified character encoding.\n     * <p/>\n     * This method uses {@link String#String(byte[], String)}.\n     *\n     * @param data     the byte array to write, do not modify during output,\n     *                 null ignored\n     * @param output   the <code>Writer</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    @SuppressLint(\"ObsoleteSdkInt\")\n    public static void write(byte[] data, Writer output, Charset encoding) throws IOException {\n        if (data != null) {\n            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {\n                output.write(new String(data, Charsets.toCharset(encoding).name()));\n            } else {\n                output.write(new String(data, Charsets.toCharset(encoding)));\n            }\n        }\n    }\n\n    /**\n     * Writes bytes from a <code>byte[]</code> to chars on a <code>Writer</code>\n     * using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * This method uses {@link String#String(byte[], String)}.\n     *\n     * @param data     the byte array to write, do not modify during output,\n     *                 null ignored\n     * @param output   the <code>Writer</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException                         if output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    public static void write(byte[] data, Writer output, String encoding) throws IOException {\n        write(data, output, Charsets.toCharset(encoding));\n    }\n\n    // write char[]\n    //-----------------------------------------------------------------------\n\n    /**\n     * Writes chars from a <code>char[]</code> to a <code>Writer</code>\n     * using the default character encoding of the platform.\n     *\n     * @param data   the char array to write, do not modify during output,\n     *               null ignored\n     * @param output the <code>Writer</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void write(char[] data, Writer output) throws IOException {\n        if (data != null) {\n            output.write(data);\n        }\n    }\n\n    /**\n     * Writes chars from a <code>char[]</code> to bytes on an\n     * <code>OutputStream</code>.\n     * <p/>\n     * This method uses {@link String#String(char[])} and\n     * {@link String#getBytes()}.\n     *\n     * @param data   the char array to write, do not modify during output,\n     *               null ignored\n     * @param output the <code>OutputStream</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void write(char[] data, OutputStream output)\n            throws IOException {\n        write(data, output, Charset.defaultCharset());\n    }\n\n    /**\n     * Writes chars from a <code>char[]</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding.\n     * <p/>\n     * This method uses {@link String#String(char[])} and\n     * {@link String#getBytes(String)}.\n     *\n     * @param data     the char array to write, do not modify during output,\n     *                 null ignored\n     * @param output   the <code>OutputStream</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static void write(char[] data, OutputStream output, Charset encoding) throws IOException {\n        if (data != null) {\n            output.write(StringCodingUtils.getBytes(new String(data), Charsets.toCharset(encoding)));\n        }\n    }\n\n    /**\n     * Writes chars from a <code>char[]</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * This method uses {@link String#String(char[])} and\n     * {@link String#getBytes(String)}.\n     *\n     * @param data     the char array to write, do not modify during output,\n     *                 null ignored\n     * @param output   the <code>OutputStream</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException                         if output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    public static void write(char[] data, OutputStream output, String encoding)\n            throws IOException {\n        write(data, output, Charsets.toCharset(encoding));\n    }\n\n    // write CharSequence\n    //-----------------------------------------------------------------------\n\n    /**\n     * Writes chars from a <code>CharSequence</code> to a <code>Writer</code>.\n     *\n     * @param data   the <code>CharSequence</code> to write, null ignored\n     * @param output the <code>Writer</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.0\n     */\n    public static void write(CharSequence data, Writer output) throws IOException {\n        if (data != null) {\n            write(data.toString(), output);\n        }\n    }\n\n    /**\n     * Writes chars from a <code>CharSequence</code> to bytes on an\n     * <code>OutputStream</code> using the default character encoding of the\n     * platform.\n     * <p/>\n     * This method uses {@link String#getBytes()}.\n     *\n     * @param data   the <code>CharSequence</code> to write, null ignored\n     * @param output the <code>OutputStream</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.0\n     */\n    public static void write(CharSequence data, OutputStream output)\n            throws IOException {\n        write(data, output, Charset.defaultCharset());\n    }\n\n    /**\n     * Writes chars from a <code>CharSequence</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding.\n     * <p/>\n     * This method uses {@link String#getBytes(String)}.\n     *\n     * @param data     the <code>CharSequence</code> to write, null ignored\n     * @param output   the <code>OutputStream</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static void write(CharSequence data, OutputStream output, Charset encoding) throws IOException {\n        if (data != null) {\n            write(data.toString(), output, encoding);\n        }\n    }\n\n    /**\n     * Writes chars from a <code>CharSequence</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * This method uses {@link String#getBytes(String)}.\n     *\n     * @param data     the <code>CharSequence</code> to write, null ignored\n     * @param output   the <code>OutputStream</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException                         if output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 2.0\n     */\n    public static void write(CharSequence data, OutputStream output, String encoding) throws IOException {\n        write(data, output, Charsets.toCharset(encoding));\n    }\n\n    // write String\n    //-----------------------------------------------------------------------\n\n    /**\n     * Writes chars from a <code>String</code> to a <code>Writer</code>.\n     *\n     * @param data   the <code>String</code> to write, null ignored\n     * @param output the <code>Writer</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void write(String data, Writer output) throws IOException {\n        if (data != null) {\n            output.write(data);\n        }\n    }\n\n    /**\n     * Writes chars from a <code>String</code> to bytes on an\n     * <code>OutputStream</code> using the default character encoding of the\n     * platform.\n     * <p/>\n     * This method uses {@link String#getBytes()}.\n     *\n     * @param data   the <code>String</code> to write, null ignored\n     * @param output the <code>OutputStream</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void write(String data, OutputStream output)\n            throws IOException {\n        write(data, output, Charset.defaultCharset());\n    }\n\n    /**\n     * Writes chars from a <code>String</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding.\n     * <p/>\n     * This method uses {@link String#getBytes(String)}.\n     *\n     * @param data     the <code>String</code> to write, null ignored\n     * @param output   the <code>OutputStream</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static void write(String data, OutputStream output, Charset encoding) throws IOException {\n        if (data != null) {\n            output.write(StringCodingUtils.getBytes(data, Charsets.toCharset(encoding)));\n        }\n    }\n\n    /**\n     * Writes chars from a <code>String</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * This method uses {@link String#getBytes(String)}.\n     *\n     * @param data     the <code>String</code> to write, null ignored\n     * @param output   the <code>OutputStream</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException                         if output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    public static void write(String data, OutputStream output, String encoding)\n            throws IOException {\n        write(data, output, Charsets.toCharset(encoding));\n    }\n\n    // write StringBuffer\n    //-----------------------------------------------------------------------\n\n    /**\n     * Writes chars from a <code>StringBuffer</code> to a <code>Writer</code>.\n     *\n     * @param data   the <code>StringBuffer</code> to write, null ignored\n     * @param output the <code>Writer</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     * @deprecated replaced by write(CharSequence, Writer)\n     */\n    @Deprecated\n    public static void write(StringBuffer data, Writer output)\n            throws IOException {\n        if (data != null) {\n            output.write(data.toString());\n        }\n    }\n\n    /**\n     * Writes chars from a <code>StringBuffer</code> to bytes on an\n     * <code>OutputStream</code> using the default character encoding of the\n     * platform.\n     * <p/>\n     * This method uses {@link String#getBytes()}.\n     *\n     * @param data   the <code>StringBuffer</code> to write, null ignored\n     * @param output the <code>OutputStream</code> to write to\n     * @throws NullPointerException if output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     * @deprecated replaced by write(CharSequence, OutputStream)\n     */\n    @Deprecated\n    public static void write(StringBuffer data, OutputStream output)\n            throws IOException {\n        //noinspection deprecation\n        write(data, output, (String) null);\n    }\n\n    /**\n     * Writes chars from a <code>StringBuffer</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * This method uses {@link String#getBytes(String)}.\n     *\n     * @param data     the <code>StringBuffer</code> to write, null ignored\n     * @param output   the <code>OutputStream</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException                         if output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     * @deprecated replaced by write(CharSequence, OutputStream, String)\n     */\n    @Deprecated\n    public static void write(StringBuffer data, OutputStream output, String encoding) throws IOException {\n        if (data != null) {\n            output.write(StringCodingUtils.getBytes(data.toString(), Charsets.toCharset(encoding)));\n        }\n    }\n\n    // writeLines\n    //-----------------------------------------------------------------------\n\n    /**\n     * Writes the <code>toString()</code> value of each item in a collection to\n     * an <code>OutputStream</code> line by line, using the default character\n     * encoding of the platform and the specified line ending.\n     *\n     * @param lines      the lines to write, null entries produce blank lines\n     * @param lineEnding the line separator to use, null is system default\n     * @param output     the <code>OutputStream</code> to write to, not null, not closed\n     * @throws NullPointerException if the output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void writeLines(Collection<?> lines, String lineEnding,\n                                  OutputStream output) throws IOException {\n        writeLines(lines, lineEnding, output, Charset.defaultCharset());\n    }\n\n    /**\n     * Writes the <code>toString()</code> value of each item in a collection to\n     * an <code>OutputStream</code> line by line, using the specified character\n     * encoding and the specified line ending.\n     *\n     * @param lines      the lines to write, null entries produce blank lines\n     * @param lineEnding the line separator to use, null is system default\n     * @param output     the <code>OutputStream</code> to write to, not null, not closed\n     * @param encoding   the encoding to use, null means platform default\n     * @throws NullPointerException if the output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static void writeLines(Collection<?> lines, String lineEnding, OutputStream output, Charset encoding)\n            throws IOException {\n        if (lines == null) {\n            return;\n        }\n        if (lineEnding == null) {\n            lineEnding = LINE_SEPARATOR;\n        }\n        Charset cs = Charsets.toCharset(encoding);\n        for (Object line : lines) {\n            if (line != null) {\n                output.write(StringCodingUtils.getBytes(line.toString(), cs));\n            }\n            output.write(StringCodingUtils.getBytes(lineEnding, cs));\n        }\n    }\n\n    /**\n     * Writes the <code>toString()</code> value of each item in a collection to\n     * an <code>OutputStream</code> line by line, using the specified character\n     * encoding and the specified line ending.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     *\n     * @param lines      the lines to write, null entries produce blank lines\n     * @param lineEnding the line separator to use, null is system default\n     * @param output     the <code>OutputStream</code> to write to, not null, not closed\n     * @param encoding   the encoding to use, null means platform default\n     * @throws NullPointerException                         if the output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    public static void writeLines(Collection<?> lines, String lineEnding,\n                                  OutputStream output, String encoding) throws IOException {\n        writeLines(lines, lineEnding, output, Charsets.toCharset(encoding));\n    }\n\n    /**\n     * Writes the <code>toString()</code> value of each item in a collection to\n     * a <code>Writer</code> line by line, using the specified line ending.\n     *\n     * @param lines      the lines to write, null entries produce blank lines\n     * @param lineEnding the line separator to use, null is system default\n     * @param writer     the <code>Writer</code> to write to, not null, not closed\n     * @throws NullPointerException if the input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void writeLines(Collection<?> lines, String lineEnding,\n                                  Writer writer) throws IOException {\n        if (lines == null) {\n            return;\n        }\n        if (lineEnding == null) {\n            lineEnding = LINE_SEPARATOR;\n        }\n        for (Object line : lines) {\n            if (line != null) {\n                writer.write(line.toString());\n            }\n            writer.write(lineEnding);\n        }\n    }\n\n    // copy from InputStream\n    //-----------------------------------------------------------------------\n\n    /**\n     * Copy bytes from an <code>InputStream</code> to an\n     * <code>OutputStream</code>.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p/>\n     * Large streams (over 2GB) will return a bytes copied value of\n     * <code>-1</code> after the copy has completed since the correct\n     * number of bytes cannot be returned as an int. For large streams\n     * use the <code>copyLarge(InputStream, OutputStream)</code> method.\n     *\n     * @param input  the <code>InputStream</code> to read from\n     * @param output the <code>OutputStream</code> to write to\n     * @return the number of bytes copied, or -1 if &gt; Integer.MAX_VALUE\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public static int copy(InputStream input, OutputStream output) throws IOException {\n        long count = copyLarge(input, output);\n        if (count > Integer.MAX_VALUE) {\n            return -1;\n        }\n        return (int) count;\n    }\n\n    /**\n     * Copy bytes from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p/>\n     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.\n     *\n     * @param input  the <code>InputStream</code> to read from\n     * @param output the <code>OutputStream</code> to write to\n     * @return the number of bytes copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.3\n     */\n    public static long copyLarge(InputStream input, OutputStream output)\n            throws IOException {\n        return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]);\n    }\n\n    /**\n     * Copy bytes from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>.\n     * <p/>\n     * This method uses the provided buffer, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p/>\n     *\n     * @param input  the <code>InputStream</code> to read from\n     * @param output the <code>OutputStream</code> to write to\n     * @param buffer the buffer to use for the copy\n     * @return the number of bytes copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(InputStream input, OutputStream output, byte[] buffer)\n            throws IOException {\n        long count = 0;\n        int n;\n        while (EOF != (n = input.read(buffer))) {\n            output.write(buffer, 0, n);\n            count += n;\n        }\n        return count;\n    }\n\n    /**\n     * Copy some or all bytes from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>, optionally skipping input bytes.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p/>\n     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.\n     *\n     * @param input       the <code>InputStream</code> to read from\n     * @param output      the <code>OutputStream</code> to write to\n     * @param inputOffset : number of bytes to skip from input before copying\n     *                    -ve values are ignored\n     * @param length      : number of bytes to copy. -ve means all\n     * @return the number of bytes copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(InputStream input, OutputStream output, long inputOffset, long length)\n            throws IOException {\n        return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]);\n    }\n\n    /**\n     * Copy some or all bytes from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>, optionally skipping input bytes.\n     * <p/>\n     * This method uses the provided buffer, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p/>\n     *\n     * @param input       the <code>InputStream</code> to read from\n     * @param output      the <code>OutputStream</code> to write to\n     * @param inputOffset : number of bytes to skip from input before copying\n     *                    -ve values are ignored\n     * @param length      : number of bytes to copy. -ve means all\n     * @param buffer      the buffer to use for the copy\n     * @return the number of bytes copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(InputStream input, OutputStream output,\n                                 final long inputOffset, final long length, byte[] buffer) throws IOException {\n        if (inputOffset > 0) {\n            skipFully(input, inputOffset);\n        }\n        if (length == 0) {\n            return 0;\n        }\n        final int bufferLength = buffer.length;\n        int bytesToRead = bufferLength;\n        if (length > 0 && length < bufferLength) {\n            bytesToRead = (int) length;\n        }\n        int read;\n        long totalRead = 0;\n        while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) {\n            output.write(buffer, 0, read);\n            totalRead += read;\n            if (length > 0) { // only adjust length if not reading to the end\n                // Note the cast must work because buffer.length is an integer\n                bytesToRead = (int) Math.min(length - totalRead, bufferLength);\n            }\n        }\n        return totalRead;\n    }\n\n    /**\n     * Copy bytes from an <code>InputStream</code> to chars on a\n     * <code>Writer</code> using the default character encoding of the platform.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p/>\n     * This method uses {@link InputStreamReader}.\n     *\n     * @param input  the <code>InputStream</code> to read from\n     * @param output the <code>Writer</code> to write to\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void copy(InputStream input, Writer output)\n            throws IOException {\n        copy(input, output, Charset.defaultCharset());\n    }\n\n    /**\n     * Copy bytes from an <code>InputStream</code> to chars on a\n     * <code>Writer</code> using the specified character encoding.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p/>\n     * This method uses {@link InputStreamReader}.\n     *\n     * @param input    the <code>InputStream</code> to read from\n     * @param output   the <code>Writer</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static void copy(InputStream input, Writer output, Charset encoding) throws IOException {\n        InputStreamReader in = new InputStreamReader(input, Charsets.toCharset(encoding));\n        copy(in, output);\n    }\n\n    /**\n     * Copy bytes from an <code>InputStream</code> to chars on a\n     * <code>Writer</code> using the specified character encoding.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * This method uses {@link InputStreamReader}.\n     *\n     * @param input    the <code>InputStream</code> to read from\n     * @param output   the <code>Writer</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException                         if the input or output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    public static void copy(InputStream input, Writer output, String encoding) throws IOException {\n        copy(input, output, Charsets.toCharset(encoding));\n    }\n\n    // copy from Reader\n    //-----------------------------------------------------------------------\n\n    /**\n     * Copy chars from a <code>Reader</code> to a <code>Writer</code>.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p/>\n     * Large streams (over 2GB) will return a chars copied value of\n     * <code>-1</code> after the copy has completed since the correct\n     * number of chars cannot be returned as an int. For large streams\n     * use the <code>copyLarge(Reader, Writer)</code> method.\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>Writer</code> to write to\n     * @return the number of characters copied, or -1 if &gt; Integer.MAX_VALUE\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public static int copy(Reader input, Writer output) throws IOException {\n        long count = copyLarge(input, output);\n        if (count > Integer.MAX_VALUE) {\n            return -1;\n        }\n        return (int) count;\n    }\n\n    /**\n     * Copy chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p/>\n     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>Writer</code> to write to\n     * @return the number of characters copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.3\n     */\n    public static long copyLarge(Reader input, Writer output) throws IOException {\n        return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]);\n    }\n\n    /**\n     * Copy chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.\n     * <p/>\n     * This method uses the provided buffer, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p/>\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>Writer</code> to write to\n     * @param buffer the buffer to be used for the copy\n     * @return the number of characters copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(Reader input, Writer output, char[] buffer) throws IOException {\n        long count = 0;\n        int n;\n        while (EOF != (n = input.read(buffer))) {\n            output.write(buffer, 0, n);\n            count += n;\n        }\n        return count;\n    }\n\n    /**\n     * Copy some or all chars from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>, optionally skipping input chars.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p/>\n     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.\n     *\n     * @param input       the <code>Reader</code> to read from\n     * @param output      the <code>Writer</code> to write to\n     * @param inputOffset : number of chars to skip from input before copying\n     *                    -ve values are ignored\n     * @param length      : number of chars to copy. -ve means all\n     * @return the number of chars copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length)\n            throws IOException {\n        return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]);\n    }\n\n    /**\n     * Copy some or all chars from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>, optionally skipping input chars.\n     * <p/>\n     * This method uses the provided buffer, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p/>\n     *\n     * @param input       the <code>Reader</code> to read from\n     * @param output      the <code>Writer</code> to write to\n     * @param inputOffset : number of chars to skip from input before copying\n     *                    -ve values are ignored\n     * @param length      : number of chars to copy. -ve means all\n     * @param buffer      the buffer to be used for the copy\n     * @return the number of chars copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length, char[] buffer)\n            throws IOException {\n        if (inputOffset > 0) {\n            skipFully(input, inputOffset);\n        }\n        if (length == 0) {\n            return 0;\n        }\n        int bytesToRead = buffer.length;\n        if (length > 0 && length < buffer.length) {\n            bytesToRead = (int) length;\n        }\n        int read;\n        long totalRead = 0;\n        while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) {\n            output.write(buffer, 0, read);\n            totalRead += read;\n            if (length > 0) { // only adjust length if not reading to the end\n                // Note the cast must work because buffer.length is an integer\n                bytesToRead = (int) Math.min(length - totalRead, buffer.length);\n            }\n        }\n        return totalRead;\n    }\n\n    /**\n     * Copy chars from a <code>Reader</code> to bytes on an\n     * <code>OutputStream</code> using the default character encoding of the\n     * platform, and calling flush.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p/>\n     * Due to the implementation of OutputStreamWriter, this method performs a\n     * flush.\n     * <p/>\n     * This method uses {@link OutputStreamWriter}.\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>OutputStream</code> to write to\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static void copy(Reader input, OutputStream output)\n            throws IOException {\n        copy(input, output, Charset.defaultCharset());\n    }\n\n    /**\n     * Copy chars from a <code>Reader</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding, and\n     * calling flush.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * </p>\n     * <p>\n     * Due to the implementation of OutputStreamWriter, this method performs a\n     * flush.\n     * </p>\n     * <p>\n     * This method uses {@link OutputStreamWriter}.\n     * </p>\n     *\n     * @param input    the <code>Reader</code> to read from\n     * @param output   the <code>OutputStream</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static void copy(Reader input, OutputStream output, Charset encoding) throws IOException {\n        OutputStreamWriter out = new OutputStreamWriter(output, Charsets.toCharset(encoding));\n        copy(input, out);\n        // XXX Unless anyone is planning on rewriting OutputStreamWriter,\n        // we have to flush here.\n        out.flush();\n    }\n\n    /**\n     * Copy chars from a <code>Reader</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding, and\n     * calling flush.\n     * <p/>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p/>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">iaNa</a>.\n     * <p/>\n     * Due to the implementation of OutputStreamWriter, this method performs a\n     * flush.\n     * <p/>\n     * This method uses {@link OutputStreamWriter}.\n     *\n     * @param input    the <code>Reader</code> to read from\n     * @param output   the <code>OutputStream</code> to write to\n     * @param encoding the encoding to use, null means platform default\n     * @throws NullPointerException                         if the input or output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not\n     *                                                      supported.\n     * @since 1.1\n     */\n    public static void copy(Reader input, OutputStream output, String encoding) throws IOException {\n        copy(input, output, Charsets.toCharset(encoding));\n    }\n\n    // content equals\n    //-----------------------------------------------------------------------\n\n    /**\n     * Compare the contents of two Streams to determine if they are equal or\n     * not.\n     * <p/>\n     * This method buffers the input internally using\n     * <code>BufferedInputStream</code> if they are not already buffered.\n     *\n     * @param input1 the first stream\n     * @param input2 the second stream\n     * @return true if the content of the streams are equal or they both don't\n     * exist, false otherwise\n     * @throws NullPointerException if either input is null\n     * @throws IOException          if an I/O error occurs\n     */\n    public static boolean contentEquals(InputStream input1, InputStream input2)\n            throws IOException {\n        if (!(input1 instanceof BufferedInputStream)) {\n            input1 = new BufferedInputStream(input1);\n        }\n        if (!(input2 instanceof BufferedInputStream)) {\n            input2 = new BufferedInputStream(input2);\n        }\n\n        int ch = input1.read();\n        while (EOF != ch) {\n            int ch2 = input2.read();\n            if (ch != ch2) {\n                return false;\n            }\n            ch = input1.read();\n        }\n\n        int ch2 = input2.read();\n        return ch2 == EOF;\n    }\n\n    /**\n     * Compare the contents of two Readers to determine if they are equal or\n     * not.\n     * <p/>\n     * This method buffers the input internally using\n     * <code>BufferedReader</code> if they are not already buffered.\n     *\n     * @param input1 the first reader\n     * @param input2 the second reader\n     * @return true if the content of the readers are equal or they both don't\n     * exist, false otherwise\n     * @throws NullPointerException if either input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static boolean contentEquals(Reader input1, Reader input2)\n            throws IOException {\n\n        input1 = toBufferedReader(input1);\n        input2 = toBufferedReader(input2);\n\n        int ch = input1.read();\n        while (EOF != ch) {\n            int ch2 = input2.read();\n            if (ch != ch2) {\n                return false;\n            }\n            ch = input1.read();\n        }\n\n        int ch2 = input2.read();\n        return ch2 == EOF;\n    }\n\n    /**\n     * Compare the contents of two Readers to determine if they are equal or\n     * not, ignoring EOL characters.\n     * <p/>\n     * This method buffers the input internally using\n     * <code>BufferedReader</code> if they are not already buffered.\n     *\n     * @param input1 the first reader\n     * @param input2 the second reader\n     * @return true if the content of the readers are equal (ignoring EOL differences),  false otherwise\n     * @throws NullPointerException if either input is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static boolean contentEqualsIgnoreEOL(Reader input1, Reader input2)\n            throws IOException {\n        BufferedReader br1 = toBufferedReader(input1);\n        BufferedReader br2 = toBufferedReader(input2);\n\n        String line1 = br1.readLine();\n        String line2 = br2.readLine();\n        while (line1 != null && line2 != null && line1.equals(line2)) {\n            line1 = br1.readLine();\n            line2 = br2.readLine();\n        }\n        return line1 == null ? line2 == null : line1.equals(line2);\n    }\n\n    /**\n     * Skip bytes from an input byte stream.\n     * This implementation guarantees that it will read as many bytes\n     * as possible before giving up; this may not always be the case for\n     * subclasses of {@link Reader}.\n     *\n     * @param input  byte stream to skip\n     * @param toSkip number of bytes to skip.\n     * @return number of bytes actually skipped.\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if toSkip is negative\n     * @see InputStream#skip(long)\n     * @since 2.0\n     */\n    public static long skip(InputStream input, long toSkip) throws IOException {\n        if (toSkip < 0) {\n            throw new IllegalArgumentException(\"Skip count must be non-negative, actual: \" + toSkip);\n        }\n        /*\n         * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data\n         * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer\n         * size were variable, we would need to sync. to ensure some other thread did not create a smaller one)\n         */\n        if (SKIP_BYTE_BUFFER == null) {\n            SKIP_BYTE_BUFFER = new byte[SKIP_BUFFER_SIZE];\n        }\n        long remain = toSkip;\n        while (remain > 0) {\n            long n = input.read(SKIP_BYTE_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE));\n            if (n < 0) { // EOF\n                break;\n            }\n            remain -= n;\n        }\n        return toSkip - remain;\n    }\n\n    /**\n     * Skip characters from an input character stream.\n     * This implementation guarantees that it will read as many characters\n     * as possible before giving up; this may not always be the case for\n     * subclasses of {@link Reader}.\n     *\n     * @param input  character stream to skip\n     * @param toSkip number of characters to skip.\n     * @return number of characters actually skipped.\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if toSkip is negative\n     * @see Reader#skip(long)\n     * @since 2.0\n     */\n    public static long skip(Reader input, long toSkip) throws IOException {\n        if (toSkip < 0) {\n            throw new IllegalArgumentException(\"Skip count must be non-negative, actual: \" + toSkip);\n        }\n        /*\n         * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data\n         * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer\n         * size were variable, we would need to sync. to ensure some other thread did not create a smaller one)\n         */\n        if (SKIP_CHAR_BUFFER == null) {\n            SKIP_CHAR_BUFFER = new char[SKIP_BUFFER_SIZE];\n        }\n        long remain = toSkip;\n        while (remain > 0) {\n            long n = input.read(SKIP_CHAR_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE));\n            if (n < 0) { // EOF\n                break;\n            }\n            remain -= n;\n        }\n        return toSkip - remain;\n    }\n\n    /**\n     * Skip the requested number of bytes or fail if there are not enough left.\n     * <p/>\n     * This allows for the possibility that {@link InputStream#skip(long)} may\n     * not skip as many bytes as requested (most likely because of reaching EOF).\n     *\n     * @param input  stream to skip\n     * @param toSkip the number of bytes to skip\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if toSkip is negative\n     * @throws EOFException             if the number of bytes skipped was incorrect\n     * @see InputStream#skip(long)\n     * @since 2.0\n     */\n    public static void skipFully(InputStream input, long toSkip) throws IOException {\n        if (toSkip < 0) {\n            throw new IllegalArgumentException(\"Bytes to skip must not be negative: \" + toSkip);\n        }\n        long skipped = skip(input, toSkip);\n        if (skipped != toSkip) {\n            throw new EOFException(\"Bytes to skip: \" + toSkip + \" actual: \" + skipped);\n        }\n    }\n\n    /**\n     * Skip the requested number of characters or fail if there are not enough left.\n     * <p/>\n     * This allows for the possibility that {@link Reader#skip(long)} may\n     * not skip as many characters as requested (most likely because of reaching EOF).\n     *\n     * @param input  stream to skip\n     * @param toSkip the number of characters to skip\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if toSkip is negative\n     * @throws EOFException             if the number of characters skipped was incorrect\n     * @see Reader#skip(long)\n     * @since 2.0\n     */\n    public static void skipFully(Reader input, long toSkip) throws IOException {\n        long skipped = skip(input, toSkip);\n        if (skipped != toSkip) {\n            throw new EOFException(\"Chars to skip: \" + toSkip + \" actual: \" + skipped);\n        }\n    }\n\n\n    /**\n     * Read characters from an input character stream.\n     * This implementation guarantees that it will read as many characters\n     * as possible before giving up; this may not always be the case for\n     * subclasses of {@link Reader}.\n     *\n     * @param input  where to read input from\n     * @param buffer destination\n     * @param offset inital offset into buffer\n     * @param length length to read, must be >= 0\n     * @return actual length read; may be less than requested if EOF was reached\n     * @throws IOException if a read error occurs\n     * @since 2.2\n     */\n    public static int read(Reader input, char[] buffer, int offset, int length) throws IOException {\n        if (length < 0) {\n            throw new IllegalArgumentException(\"Length must not be negative: \" + length);\n        }\n        int remaining = length;\n        while (remaining > 0) {\n            int location = length - remaining;\n            int count = input.read(buffer, offset + location, remaining);\n            if (EOF == count) { // EOF\n                break;\n            }\n            remaining -= count;\n        }\n        return length - remaining;\n    }\n\n    /**\n     * Read characters from an input character stream.\n     * This implementation guarantees that it will read as many characters\n     * as possible before giving up; this may not always be the case for\n     * subclasses of {@link Reader}.\n     *\n     * @param input  where to read input from\n     * @param buffer destination\n     * @return actual length read; may be less than requested if EOF was reached\n     * @throws IOException if a read error occurs\n     * @since 2.2\n     */\n    public static int read(Reader input, char[] buffer) throws IOException {\n        return read(input, buffer, 0, buffer.length);\n    }\n\n    /**\n     * Read bytes from an input stream.\n     * This implementation guarantees that it will read as many bytes\n     * as possible before giving up; this may not always be the case for\n     * subclasses of {@link InputStream}.\n     *\n     * @param input  where to read input from\n     * @param buffer destination\n     * @param offset inital offset into buffer\n     * @param length length to read, must be >= 0\n     * @return actual length read; may be less than requested if EOF was reached\n     * @throws IOException if a read error occurs\n     * @since 2.2\n     */\n    public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException {\n        if (length < 0) {\n            throw new IllegalArgumentException(\"Length must not be negative: \" + length);\n        }\n        int remaining = length;\n        while (remaining > 0) {\n            int location = length - remaining;\n            int count = input.read(buffer, offset + location, remaining);\n            if (EOF == count) { // EOF\n                break;\n            }\n            remaining -= count;\n        }\n        return length - remaining;\n    }\n\n    /**\n     * Read bytes from an input stream.\n     * This implementation guarantees that it will read as many bytes\n     * as possible before giving up; this may not always be the case for\n     * subclasses of {@link InputStream}.\n     *\n     * @param input  where to read input from\n     * @param buffer destination\n     * @return actual length read; may be less than requested if EOF was reached\n     * @throws IOException if a read error occurs\n     * @since 2.2\n     */\n    public static int read(InputStream input, byte[] buffer) throws IOException {\n        return read(input, buffer, 0, buffer.length);\n    }\n\n    /**\n     * Read the requested number of characters or fail if there are not enough left.\n     * <p/>\n     * This allows for the possibility that {@link Reader#read(char[], int, int)} may\n     * not read as many characters as requested (most likely because of reaching EOF).\n     *\n     * @param input  where to read input from\n     * @param buffer destination\n     * @param offset inital offset into buffer\n     * @param length length to read, must be >= 0\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if length is negative\n     * @throws EOFException             if the number of characters read was incorrect\n     * @since 2.2\n     */\n    @SuppressWarnings(\"SameParameterValue\")\n    public static void readFully(Reader input, char[] buffer, int offset, int length) throws IOException {\n        int actual = read(input, buffer, offset, length);\n        if (actual != length) {\n            throw new EOFException(\"Length to read: \" + length + \" actual: \" + actual);\n        }\n    }\n\n    /**\n     * Read the requested number of characters or fail if there are not enough left.\n     * <p/>\n     * This allows for the possibility that {@link Reader#read(char[], int, int)} may\n     * not read as many characters as requested (most likely because of reaching EOF).\n     *\n     * @param input  where to read input from\n     * @param buffer destination\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if length is negative\n     * @throws EOFException             if the number of characters read was incorrect\n     * @since 2.2\n     */\n    public static void readFully(Reader input, char[] buffer) throws IOException {\n        readFully(input, buffer, 0, buffer.length);\n    }\n\n    /**\n     * Read the requested number of bytes or fail if there are not enough left.\n     * <p/>\n     * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may\n     * not read as many bytes as requested (most likely because of reaching EOF).\n     *\n     * @param input  where to read input from\n     * @param buffer destination\n     * @param offset inital offset into buffer\n     * @param length length to read, must be >= 0\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if length is negative\n     * @throws EOFException             if the number of bytes read was incorrect\n     * @since 2.2\n     */\n    @SuppressWarnings(\"SameParameterValue\")\n    public static void readFully(InputStream input, byte[] buffer, int offset, int length) throws IOException {\n        int actual = read(input, buffer, offset, length);\n        if (actual != length) {\n            throw new EOFException(\"Length to read: \" + length + \" actual: \" + actual);\n        }\n    }\n\n    /**\n     * Read the requested number of bytes or fail if there are not enough left.\n     * <p/>\n     * This allows for the possibility that {@link InputStream#read(byte[], int, int)} may\n     * not read as many bytes as requested (most likely because of reaching EOF).\n     *\n     * @param input  where to read input from\n     * @param buffer destination\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if length is negative\n     * @throws EOFException             if the number of bytes read was incorrect\n     * @since 2.2\n     */\n    public static void readFully(InputStream input, byte[] buffer) throws IOException {\n        readFully(input, buffer, 0, buffer.length);\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/io/SdCardUtil.java",
    "content": "package com.lib.aliocr.utils.io;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.os.StatFs;\nimport android.util.Log;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.util.ArrayList;\n\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class SdCardUtil {\n    private static final String TAG = SdCardUtil.class.getSimpleName();\n\n    /**\n     * is sd card available.\n     * @return true if available\n     */\n    public static boolean isSdCardAvailable() {\n        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());\n    }\n\n    /**\n     * Get {@link StatFs}.\n     */\n    public static StatFs getStatFs(String path) {\n        return new StatFs(path);\n    }\n\n    /**\n     * Get phone data path.\n     */\n    public static String getDataPath() {\n        return Environment.getDataDirectory().getPath();\n\n    }\n\n    /**\n     * Get SD card path.\n     */\n    public static String getNormalSDCardPath() {\n        return Environment.getExternalStorageDirectory().getPath();\n    }\n\n    /**\n     * Get SD card path by CMD.\n     */\n    public static String getSDCardPath() {\n        String cmd = \"cat /proc/mounts\";\n        String sdcard;\n        Runtime run = Runtime.getRuntime();// 返回与当前 Java 应用程序相关的运行时对象\n        BufferedReader bufferedReader = null;\n        try {\n            Process p = run.exec(cmd);// 启动另一个进程来执行命令\n            bufferedReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(p.getInputStream())));\n            String lineStr;\n            while ((lineStr = bufferedReader.readLine()) != null) {\n                if (lineStr.contains(\"sdcard\")\n                    && lineStr.contains(\".android_secure\")) {\n                    String[] strArray = lineStr.split(\" \");\n                    if (strArray.length >= 5) {\n                        sdcard = strArray[1].replace(\"/.android_secure\", \"\");\n                        return sdcard;\n                    }\n                }\n                if (p.waitFor() != 0 && p.exitValue() == 1) {\n                    // p.exitValue()==0表示正常结束，1：非正常结束\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        } finally {\n            try {\n                if (bufferedReader != null) {\n                    bufferedReader.close();\n                }\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n        sdcard = Environment.getExternalStorageDirectory().getPath();\n        return sdcard;\n    }\n\n    /**\n     * Get SD card path list.\n     */\n    public static ArrayList<String> getSDCardPathEx() {\n        ArrayList<String> list = new ArrayList<>();\n        try {\n            Runtime runtime = Runtime.getRuntime();\n            Process proc = runtime.exec(\"mount\");\n            InputStream is = proc.getInputStream();\n            InputStreamReader isr = new InputStreamReader(is);\n            String line;\n            BufferedReader br = new BufferedReader(isr);\n            while ((line = br.readLine()) != null) {\n                Log.i(TAG, \"mount:  \" + line);\n                if (line.contains(\"secure\")) {\n                    continue;\n                }\n                if (line.contains(\"asec\")) {\n                    continue;\n                }\n\n                if (line.contains(\"fat\")) {\n                    String columns[] = line.split(\" \");\n                    if (columns.length > 1) {\n                        list.add(\"*\" + columns[1]);\n                    }\n                } else if (line.contains(\"fuse\")) {\n                    String columns[] = line.split(\" \");\n                    if (columns.length > 1) {\n                        list.add(columns[1]);\n                    }\n                }\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n        return list;\n    }\n\n    /**\n     * Get available size of SD card.\n     */\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)\n    public static long getAvailableSize(String path) {\n        try {\n            File base = new File(path);\n            StatFs stat = new StatFs(base.getPath());\n            return stat.getBlockSizeLong() * stat.getAvailableBlocksLong();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return 0;\n    }\n\n\n\n\n\n\n    /**\n     * Get SD card info detail.\n     */\n    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)\n    public static SDCardInfo getSDCardInfo() {\n        SDCardInfo sd = new SDCardInfo();\n        String state = Environment.getExternalStorageState();\n        if (Environment.MEDIA_MOUNTED.equals(state)) {\n            sd.isExist = true;\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n                File sdcardDir = Environment.getExternalStorageDirectory();\n                StatFs sf = new StatFs(sdcardDir.getPath());\n\n                sd.totalBlocks = sf.getBlockCountLong();\n                sd.blockByteSize = sf.getBlockSizeLong();\n\n                sd.availableBlocks = sf.getAvailableBlocksLong();\n                sd.availableBytes = sf.getAvailableBytes();\n\n                sd.freeBlocks = sf.getFreeBlocksLong();\n                sd.freeBytes = sf.getFreeBytes();\n\n                sd.totalBytes = sf.getTotalBytes();\n            }\n        }\n\n            Log.i(TAG, sd.toString());\n\n        return sd;\n    }\n\n\n    /**\n     * see more {@link StatFs}\n     */\n    public static class SDCardInfo {\n        public boolean isExist;\n        public long totalBlocks;\n        public long freeBlocks;\n        public long availableBlocks;\n\n        public long blockByteSize;\n\n        public long totalBytes;\n        public long freeBytes;\n        public long availableBytes;\n\n        @Override\n        public String toString() {\n            return \"SDCardInfo{\" +\n                   \"isExist=\" + isExist +\n                   \", totalBlocks=\" + totalBlocks +\n                   \", freeBlocks=\" + freeBlocks +\n                   \", availableBlocks=\" + availableBlocks +\n                   \", blockByteSize=\" + blockByteSize +\n                   \", totalBytes=\" + totalBytes +\n                   \", freeBytes=\" + freeBytes +\n                   \", availableBytes=\" + availableBytes +\n                   '}';\n        }\n    }\n\n\n    /*\n   * Delete the files older than numDays days from the application cache\n   * 0 means all files.\n   */\n    @SuppressLint(\"DefaultLocale\")\n    public static void clearWebViewCache(final Context context,\n                                         final int numDays) {\n        try {\n            Log.i(TAG,\n                    String.format(\"开始删除缓存,删除%d天前文件\",\n                            numDays));\n            @SuppressLint(\"SdCardPath\")\n            int numDeletedFiles = FileUtil.clearCacheFolder(new File(\"/data/data/\" + context.getPackageName() + \"/app_webview\"),\n                    numDays);\n            Log.i(TAG,\n                    String.format(\"缓存完成, 删除%d个文件\",\n                            numDeletedFiles));\n        } catch (Exception e) {\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/io/StringCodingUtils.java",
    "content": "package com.lib.aliocr.utils.io;\n\nimport java.nio.charset.Charset;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class StringCodingUtils {\n\n    public static byte[] getBytes(String src, Charset charSet) {\n        return src.getBytes(charSet);\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/stream/ByteArrayOutputStream.java",
    "content": "\npackage com.lib.aliocr.utils.stream;\n \nimport android.support.annotation.NonNull;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.SequenceInputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * This class implements an output stream in which the data is \n * written into a byte array. The buffer automatically grows as data \n * is written to it.\n * <p> \n * The data can be retrieved using <code>toByteArray()</code> and\n * <code>toString()</code>.\n * <p>\n * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in\n * this class can be called after the stream has been closed without\n * generating an <tt>IOException</tt>.\n * <p>\n * This is an alternative implementation of the {@link java.io.ByteArrayOutputStream}\n * class. The original implementation only allocates 32 bytes at the beginning.\n * As this class is designed for heavy duty it starts at 1024 bytes. In contrast\n * to the original it doesn't reallocate the whole memory block but allocates\n * additional buffers. This way no buffers need to be garbage collected and\n * the contents don't have to be copied to the new buffer. This class is\n * designed to behave exactly like the original. The only exception is the\n * deprecated toString(int) method that has been ignored.\n * \n * @version $Id: ByteArrayOutputStream.java 1304052 2012-03-22 20:55:29Z ggregory $\n */\npublic class ByteArrayOutputStream extends OutputStream {\n\n    /** A singleton empty byte array. */\n    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];\n\n    /** The list of buffers, which grows and never reduces. */\n    private final List<byte[]> buffers = new ArrayList<>();\n    /** The index of the current buffer. */\n    private int currentBufferIndex;\n    /** The total count of bytes in all the filled buffers. */\n    private int filledBufferSum;\n    /** The current buffer. */\n    private byte[] currentBuffer;\n    /** The total count of bytes written. */\n    private int count;\n\n    /**\n     * Creates a new byte array output stream. The buffer capacity is \n     * initially 1024 bytes, though its size increases if necessary. \n     */\n    public ByteArrayOutputStream() {\n        this(1024);\n    }\n\n    /**\n     * Creates a new byte array output stream, with a buffer capacity of \n     * the specified size, in bytes. \n     *\n     * @param size  the initial size\n     * @throws IllegalArgumentException if size is negative\n     */\n    @SuppressWarnings(\"WeakerAccess\")\n    public ByteArrayOutputStream(int size) {\n        if (size < 0) {\n            throw new IllegalArgumentException(\n                \"Negative initial size: \" + size);\n        }\n        synchronized (this) {\n            needNewBuffer(size);\n        }\n    }\n\n    /**\n     * Makes a new buffer available either by allocating\n     * a new one or re-cycling an existing one.\n     *\n     * @param newCount  the size of the buffer if one is created\n     */\n    private void needNewBuffer(int newCount) {\n        if (currentBufferIndex < buffers.size() - 1) {\n            //Recycling old buffer\n            filledBufferSum += currentBuffer.length;\n            \n            currentBufferIndex++;\n            currentBuffer = buffers.get(currentBufferIndex);\n        } else {\n            //Creating new buffer\n            int newBufferSize;\n            if (currentBuffer == null) {\n                newBufferSize = newCount;\n                filledBufferSum = 0;\n            } else {\n                newBufferSize = Math.max(\n                    currentBuffer.length << 1, \n                    newCount - filledBufferSum);\n                filledBufferSum += currentBuffer.length;\n            }\n            \n            currentBufferIndex++;\n            currentBuffer = new byte[newBufferSize];\n            buffers.add(currentBuffer);\n        }\n    }\n\n    /**\n     * Write the bytes to byte array.\n     * @param b the bytes to write\n     * @param off The start offset\n     * @param len The number of bytes to write\n     */\n    @Override\n    public void write(@NonNull byte[] b, int off, int len) {\n        if ((off < 0) \n                || (off > b.length) \n                || (len < 0) \n                || ((off + len) > b.length) \n                || ((off + len) < 0)) {\n            throw new IndexOutOfBoundsException();\n        } else if (len == 0) {\n            return;\n        }\n        synchronized (this) {\n            int newCount = count + len;\n            int remaining = len;\n            int inBufferPos = count - filledBufferSum;\n            while (remaining > 0) {\n                int part = Math.min(remaining, currentBuffer.length - inBufferPos);\n                System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);\n                remaining -= part;\n                if (remaining > 0) {\n                    needNewBuffer(newCount);\n                    inBufferPos = 0;\n                }\n            }\n            count = newCount;\n        }\n    }\n\n    /**\n     * Write a byte to byte array.\n     * @param b the byte to write\n     */\n    @Override\n    public synchronized void write(int b) {\n        int inBufferPos = count - filledBufferSum;\n        if (inBufferPos == currentBuffer.length) {\n            needNewBuffer(count + 1);\n            inBufferPos = 0;\n        }\n        currentBuffer[inBufferPos] = (byte) b;\n        count++;\n    }\n\n    /**\n     * Writes the entire contents of the specified input stream to this\n     * byte stream. Bytes from the input stream are read directly into the\n     * internal buffers of this streams.\n     *\n     * @param in the input stream to read from\n     * @return total number of bytes read from the input stream\n     *         (and written to this stream)\n     * @throws IOException if an I/O error occurs while reading the input stream\n     * @since 1.4\n     */\n    @SuppressWarnings({\"WeakerAccess\", \"UnusedReturnValue\"})\n    public synchronized int write(InputStream in) throws IOException {\n        int readCount = 0;\n        int inBufferPos = count - filledBufferSum;\n        int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);\n        while (n != -1) {\n            readCount += n;\n            inBufferPos += n;\n            count += n;\n            if (inBufferPos == currentBuffer.length) {\n                needNewBuffer(currentBuffer.length);\n                inBufferPos = 0;\n            }\n            n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);\n        }\n        return readCount;\n    }\n\n    /**\n     * Return the current size of the byte array.\n     * @return the current size of the byte array\n     */\n    public synchronized int size() {\n        return count;\n    }\n\n    /**\n     * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in\n     * this class can be called after the stream has been closed without\n     * generating an <tt>IOException</tt>.\n     *\n     * @throws IOException never (this method should not declare this exception\n     * but it has to now due to backwards compatability)\n     */\n    @Override\n    public void close() throws IOException {\n        //nop\n    }\n\n    /**\n     * @see java.io.ByteArrayOutputStream#reset()\n     */\n    public synchronized void reset() {\n        count = 0;\n        filledBufferSum = 0;\n        currentBufferIndex = 0;\n        currentBuffer = buffers.get(currentBufferIndex);\n    }\n\n    /**\n     * Writes the entire contents of this byte stream to the\n     * specified output stream.\n     *\n     * @param out  the output stream to write to\n     * @throws IOException if an I/O error occurs, such as if the stream is closed\n     * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)\n     */\n    public synchronized void writeTo(OutputStream out) throws IOException {\n        int remaining = count;\n        for (byte[] buf : buffers) {\n            int c = Math.min(buf.length, remaining);\n            out.write(buf, 0, c);\n            remaining -= c;\n            if (remaining == 0) {\n                break;\n            }\n        }\n    }\n\n    /**\n     * Fetches entire contents of an <code>InputStream</code> and represent\n     * same data as result InputStream.\n     * <p>\n     * This method is useful where,\n     * <ul>\n     * <li>Source InputStream is slow.</li>\n     * <li>It has network resources associated, so we cannot keep it open for\n     * long time.</li>\n     * <li>It has network timeout associated.</li>\n     * </ul>\n     * It can be used in favor of {@link #toByteArray()}, since it\n     * avoids unnecessary allocation and copy of byte[].<br>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * \n     * @param input Stream to be fully buffered.\n     * @return A fully buffered stream.\n     * @throws IOException if an I/O error occurs\n     * @since 2.0\n     */\n    public static InputStream toBufferedInputStream(InputStream input)\n            throws IOException {\n        ByteArrayOutputStream output = new ByteArrayOutputStream();\n        output.write(input);\n        return output.toBufferedInputStream();\n    }\n\n    /**\n     * Gets the current contents of this byte stream as a Input Stream. The\n     * returned stream is backed by buffers of <code>this</code> stream,\n     * avoiding memory allocation and copy, thus saving space and time.<br>\n     * \n     * @return the current contents of this output stream.\n     * @see java.io.ByteArrayOutputStream#toByteArray()\n     * @see #reset()\n     * @since 2.0\n     */\n    private InputStream toBufferedInputStream() {\n        int remaining = count;\n        if (remaining == 0) {\n            return new ClosedInputStream();\n        }\n        List<ByteArrayInputStream> list = new ArrayList<>(buffers.size());\n        for (byte[] buf : buffers) {\n            int c = Math.min(buf.length, remaining);\n            list.add(new ByteArrayInputStream(buf, 0, c));\n            remaining -= c;\n            if (remaining == 0) {\n                break;\n            }\n        }\n        return new SequenceInputStream(Collections.enumeration(list));\n    }\n\n    /**\n     * Gets the current contents of this byte stream as a byte array.\n     * The result is independent of this stream.\n     *\n     * @return the current contents of this output stream, as a byte array\n     * @see java.io.ByteArrayOutputStream#toByteArray()\n     */\n    public synchronized byte[] toByteArray() {\n        int remaining = count;\n        if (remaining == 0) {\n            return EMPTY_BYTE_ARRAY; \n        }\n        byte newBuf[] = new byte[remaining];\n        int pos = 0;\n        for (byte[] buf : buffers) {\n            int c = Math.min(buf.length, remaining);\n            System.arraycopy(buf, 0, newBuf, pos, c);\n            pos += c;\n            remaining -= c;\n            if (remaining == 0) {\n                break;\n            }\n        }\n        return newBuf;\n    }\n\n    /**\n     * Gets the current contents of this byte stream as a string.\n     * @return the contents of the byte array as a String\n     * @see java.io.ByteArrayOutputStream#toString()\n     */\n    @Override\n    public String toString() {\n        return new String(toByteArray());\n    }\n\n    /**\n     * Gets the current contents of this byte stream as a string\n     * using the specified encoding.\n     *\n     * @param enc  the name of the character encoding\n     * @return the string converted from the byte array\n     * @throws UnsupportedEncodingException if the encoding is not supported\n     * @see java.io.ByteArrayOutputStream#toString(String)\n     */\n    public String toString(String enc) throws UnsupportedEncodingException {\n        return new String(toByteArray(), enc);\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/stream/ClosedInputStream.java",
    "content": "\npackage com.lib.aliocr.utils.stream;\n\nimport java.io.InputStream;\n\n/**\n * Closed input stream. This stream returns -1 to all attempts to read\n * something from the stream.\n * <p>\n * Typically uses of this class include testing for corner cases in methods\n * that accept input streams and acting as a sentinel value instead of a\n * {@code null} input stream.\n *\n * @version $Id: ClosedInputStream.java 1307459 2012-03-30 15:11:44Z ggregory $\n * @since 1.4\n */\npublic class ClosedInputStream extends InputStream {\n    \n    /**\n     * A singleton.\n     */\n    public static final ClosedInputStream CLOSED_INPUT_STREAM = new ClosedInputStream();\n\n    /**\n     * Returns -1 to indicate that the stream is closed.\n     *\n     * @return always -1\n     */\n    @Override\n    public int read() {\n        return -1;\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/utils/stream/StringBuilderWriter.java",
    "content": "\npackage com.lib.aliocr.utils.stream;\n\nimport android.support.annotation.NonNull;\n\nimport java.io.Serializable;\nimport java.io.Writer;\n\n/**\n * {@link Writer} implementation that outputs to a {@link StringBuilder}.\n * <p>\n * <strong>NOTE:</strong> This implementation, as an alternative to\n * <code>java.io.StringWriter</code>, provides an <i>un-synchronized</i>\n * (i.e. for use in a single thread) implementation for better performance.\n * For safe usage with multiple {@link Thread}s then\n * <code>java.io.StringWriter</code> should be used.\n *\n * @version $Id: StringBuilderWriter.java 1304052 2012-03-22 20:55:29Z ggregory $\n * @since 2.0\n */\npublic class StringBuilderWriter extends Writer implements Serializable {\n\n    private static final long serialVersionUID = 8043747493392913171L;\n    private final StringBuilder builder;\n\n    /**\n     * Construct a new {@link StringBuilder} instance with default capacity.\n     */\n    public StringBuilderWriter() {\n        this.builder = new StringBuilder();\n    }\n\n    /**\n     * Construct a new {@link StringBuilder} instance with the specified capacity.\n     *\n     * @param capacity The initial capacity of the underlying {@link StringBuilder}\n     */\n    @SuppressWarnings(\"SameParameterValue\")\n    public StringBuilderWriter(int capacity) {\n        this.builder = new StringBuilder(capacity);\n    }\n\n    /**\n     * Construct a new instance with the specified {@link StringBuilder}.\n     *\n     * @param builder The String builder\n     */\n    public StringBuilderWriter(StringBuilder builder) {\n        this.builder = builder != null ? builder : new StringBuilder();\n    }\n\n    /**\n     * Append a single character to this Writer.\n     *\n     * @param value The character to append\n     * @return This writer instance\n     */\n    @Override\n    public Writer append(char value) {\n        builder.append(value);\n        return this;\n    }\n\n    /**\n     * Append a character sequence to this Writer.\n     *\n     * @param value The character to append\n     * @return This writer instance\n     */\n    @Override\n    public Writer append(CharSequence value) {\n        builder.append(value);\n        return this;\n    }\n\n    /**\n     * Append a portion of a character sequence to the {@link StringBuilder}.\n     *\n     * @param value The character to append\n     * @param start The index of the first character\n     * @param end The index of the last character + 1\n     * @return This writer instance\n     */\n    @Override\n    public Writer append(CharSequence value, int start, int end) {\n        builder.append(value, start, end);\n        return this;\n    }\n\n    /**\n     * Closing this writer has no effect. \n     */\n    @Override\n    public void close() {\n    }\n\n    /**\n     * Flushing this writer has no effect. \n     */\n    @Override\n    public void flush() {\n    }\n\n\n    /**\n     * Write a String to the {@link StringBuilder}.\n     * \n     * @param value The value to write\n     */\n    @Override\n    public void write(@NonNull String value) {\n        builder.append(value);\n    }\n\n    /**\n     * Write a portion of a character array to the {@link StringBuilder}.\n     *\n     * @param value The value to write\n     * @param offset The index of the first character\n     * @param length The number of characters to write\n     */\n    @Override\n    public void write(@NonNull char[] value, int offset, int length) {\n        builder.append(value, offset, length);\n    }\n\n    /**\n     * Return the underlying builder.\n     *\n     * @return The underlying builder\n     */\n    public StringBuilder getBuilder() {\n        return builder;\n    }\n\n    /**\n     * Returns {@link StringBuilder#toString()}.\n     *\n     * @return The contents of the String builder.\n     */\n    @Override\n    public String toString() {\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/view/OCRMainActivity.java",
    "content": "package com.lib.aliocr.view;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.support.annotation.NonNull;\nimport android.support.design.widget.FloatingActionButton;\nimport android.support.v4.app.ActivityCompat;\nimport android.support.v4.content.ContextCompat;\nimport android.support.v7.app.AppCompatActivity;\nimport android.util.Log;\nimport android.view.View;\n\nimport com.lib.aliocr.R;\nimport com.lib.aliocr.callback.OcrCallback;\nimport com.lib.aliocr.contact.OcrContact;\nimport com.lib.aliocr.presenter.OCRPresenter;\nimport com.lib.aliocr.utils.Ocr;\n\nimport cameraview.CameraView;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n@SuppressWarnings(\"StatementWithEmptyBody\")\npublic class OCRMainActivity extends AppCompatActivity implements OcrContact.V {\n    private static final int REQUEST_CAMERA_PERMISSION = 1;\n    private static final String TAG = OCRMainActivity.class.getSimpleName();\n    private CameraView mCameraView;\n    private Handler mBackgroundHandler;\n    private OCRPresenter presenter;\n    private boolean mIsFace;\n\n    public static void Launcher(Context context, boolean isFace) {\n\n        Intent intent = new Intent(context, OCRMainActivity.class);\n\n\n        if (!(context instanceof Activity)) {\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n        intent.putExtra(\"mIsFace\", isFace);\n\n\n        context.startActivity(intent);\n    }\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mIsFace = getIntent().getBooleanExtra(\"mIsFace\", true);\n\n        setContentView(R.layout.activity_ocr_main);\n        presenter = new OCRPresenter(this);\n        mCameraView = findViewById(R.id.my_camera);\n        if (mCameraView != null) {\n            mCameraView.addCallback(mCallback);\n        }\n        FloatingActionButton fab = findViewById(R.id.take_picture_);\n        if (fab != null) {\n            fab.setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (mCameraView != null) {\n                        mCameraView.takePicture();\n                    }\n                }\n            });\n        }\n    }\n\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n\n\n        if ((ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) &\n                ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) &\n                ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA))\n                == PackageManager.PERMISSION_GRANTED) {\n            mCameraView.start();\n        } /*else if (ActivityCompat.shouldShowRequestPermissionRationale(this,\n                Manifest.permission.CAMERA)) {\n            ConfirmationDialogFragment\n                    .newInstance(R.string.camera_permission_confirmation,\n                            new String[]{Manifest.      .CAMERA},\n                            REQUEST_CAMERA_PERMISSION,\n                            R.string.camera_permission_not_granted)\n                    .show(getSupportFragmentManager(), FRAGMENT_DIALOG);\n        }*/ else {\n            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA\n                            , Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE},\n                    REQUEST_CAMERA_PERMISSION);\n        }\n    }\n\n\n    @Override\n    protected void onPause() {\n        mCameraView.stop();\n        super.onPause();\n    }\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        if (mBackgroundHandler != null) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n                mBackgroundHandler.getLooper().quitSafely();\n            } else {\n                mBackgroundHandler.getLooper().quit();\n            }\n            mBackgroundHandler = null;\n        }\n    }\n\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,\n                                           @NonNull int[] grantResults) {\n        switch (requestCode) {\n            case REQUEST_CAMERA_PERMISSION:\n                if (permissions.length != 1 || grantResults.length != 1) {\n                    throw new RuntimeException(\"Error on requesting camera permission.\");\n                }\n                if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {\n\n                }\n                // No need to start camera here; it is handled by onResume\n                break;\n        }\n    }\n\n    private Handler getBackgroundHandler() {\n        if (mBackgroundHandler == null) {\n            HandlerThread thread = new HandlerThread(\"background\");\n            thread.start();\n            mBackgroundHandler = new Handler(thread.getLooper());\n        }\n        return mBackgroundHandler;\n    }\n\n    private CameraView.Callback mCallback\n            = new CameraView.Callback() {\n\n        @Override\n        public void onCameraOpened(CameraView cameraView) {\n            Log.d(TAG, \"onCameraOpened\");\n        }\n\n        @Override\n        public void onCameraClosed(CameraView cameraView) {\n            Log.d(TAG, \"onCameraClosed\");\n        }\n\n        @Override\n        public void onPictureTaken(CameraView cameraView, final byte[] data) {\n            Log.d(TAG, \"onPictureTaken \" + data.length);\n\n            presenter.onPictureTaken(data );\n\n            //   mCameraView.stop();\n\n        }\n\n    };\n\n\n    @Override\n    protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n\n        Ocr.onCropImgResult(this, null, requestCode, resultCode, data, new OcrCallback() {\n            @Override\n            public void onPicResult(String picPath) {\n                presenter.request(mIsFace, picPath);\n            }\n\n            @Override\n            public void onPicError() {\n\n            }\n        });\n    }\n\n    @Override\n    public Activity getActivity() {\n        return this;\n    }\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/view/OcrLaunchFragment.java",
    "content": "package com.lib.aliocr.view;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.lib.aliocr.R;\nimport com.lib.aliocr.callback.OcrCallback;\nimport com.lib.aliocr.contact.OcrContact;\nimport com.lib.aliocr.presenter.OCRPresenter;\nimport com.lib.aliocr.utils.Ocr;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class OcrLaunchFragment extends Fragment implements View.OnClickListener, OcrContact.V {\n\n    boolean mIsFace;\n    private View rootView;\n\n    private OcrContact.P mPresenter;\n\n    @Override\n    public void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        mPresenter = new OCRPresenter(this);\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {\n\n        rootView = inflater.inflate(R.layout.fragment_ocr_launch, null);\n        rootView.findViewById(R.id.side_face).setOnClickListener(this);\n        rootView.findViewById(R.id.side_back).setOnClickListener(this);\n\n        return rootView;\n    }\n\n    @Override\n    public void onClick(View v) {\n        int i = v.getId();\n        if (i == R.id.side_face) {\n            mIsFace = true;\n        } else if (i == R.id.side_back) {\n            mIsFace = false;\n        }\n        Ocr.doOcr(getActivity(),this, rootView, mIsFace);\n    }\n\n\n    //  ----------------------------------ocr使用-------------------------------------\n\n    @Override\n    public void onActivityResult(int requestCode, int resultCode, Intent data) {\n        super.onActivityResult(requestCode, resultCode, data);\n\n        Ocr.onCropImgResult(getActivity(),this, requestCode, resultCode, data, new OcrCallback() {\n            @Override\n            public void onPicResult(String picPath) {\n                mPresenter.request(mIsFace, picPath);\n            }\n\n            @Override\n            public void onPicError() {\n\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/java/com/lib/aliocr/widget/crop/Crop.java",
    "content": "package com.lib.aliocr.widget.crop;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.Fragment;\nimport android.content.ActivityNotFoundException;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.provider.MediaStore;\nimport android.widget.Toast;\n\nimport com.lib.aliocr.R;\n\n/**\n * Builder for crop Intents and utils for handling result\n */\npublic class Crop {\n\n    public static final int REQUEST_CROP = 6709;\n    public static final int REQUEST_PICK = 9162;\n    public static final int RESULT_ERROR = 404;\n\n    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(float x, float 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": "aliocrlib/src/main/java/com/lib/aliocr/widget/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.lib.aliocr.widget.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 com.lib.aliocr.R;\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 = 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": "aliocrlib/src/main/java/com/lib/aliocr/widget/crop/CropImageView.java",
    "content": "package com.lib.aliocr.widget.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": "aliocrlib/src/main/java/com/lib/aliocr/widget/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.lib.aliocr.widget.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 != null ? pfd.getFileDescriptor() : null;\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": "aliocrlib/src/main/java/com/lib/aliocr/widget/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.lib.aliocr.widget.crop;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Matrix;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Region;\nimport android.os.Build;\nimport android.util.TypedValue;\nimport android.view.View;\n\nimport com.lib.aliocr.R;\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": "aliocrlib/src/main/java/com/lib/aliocr/widget/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.lib.aliocr.widget.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 android.support.v7.widget.AppCompatImageView {\n\n    private static final float SCALE_RATE = 1.25F;\n\n    // This is the base transformation which is used to show the image\n    // initially.  The current computation for this shows the image in\n    // it's entirety, letterboxing as needed.  One could choose to\n    // show the image as cropped instead.\n    //\n    // This matrix is recomputed when we go from the thumbnail image to\n    // the full size image.\n    protected Matrix baseMatrix = new Matrix();\n\n    // This is the supplementary transformation which reflects what\n    // the user has done in terms of zooming and panning.\n    //\n    // This matrix remains the same when we go from the thumbnail image\n    // to the full size image.\n    protected Matrix suppMatrix = new Matrix();\n\n    // This is the final matrix which is computed as the concatentation\n    // of the base matrix and the supplementary matrix.\n    private final Matrix displayMatrix = new Matrix();\n\n    // Temporary buffer used for getting the values out of a matrix.\n    private final float[] matrixValues = new float[9];\n\n    // The current bitmap being displayed.\n    protected final RotateBitmap bitmapDisplayed = new RotateBitmap(null, 0);\n\n    int thisWidth = -1;\n    int thisHeight = -1;\n\n    float maxZoom;\n\n    private Runnable onLayoutRunnable;\n\n    protected Handler handler = new Handler();\n\n    // ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished\n    // its use of that Bitmap\n    public interface Recycler {\n        public void recycle(Bitmap b);\n    }\n\n    private Recycler recycler;\n\n    public ImageViewTouchBase(Context context) {\n        super(context);\n        init();\n    }\n\n    public ImageViewTouchBase(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init();\n    }\n\n    public ImageViewTouchBase(Context context, AttributeSet attrs, int defStyle) {\n        super(context, attrs, defStyle);\n        init();\n    }\n\n    public void setRecycler(Recycler recycler) {\n        this.recycler = recycler;\n    }\n\n    @Override\n    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {\n        super.onLayout(changed, left, top, right, bottom);\n        thisWidth = right - left;\n        thisHeight = bottom - top;\n        Runnable r = onLayoutRunnable;\n        if (r != null) {\n            onLayoutRunnable = null;\n            r.run();\n        }\n        if (bitmapDisplayed.getBitmap() != null) {\n            getProperBaseMatrix(bitmapDisplayed, baseMatrix, true);\n            setImageMatrix(getImageViewMatrix());\n        }\n    }\n\n    @Override\n    public boolean onKeyDown(int keyCode, KeyEvent event) {\n        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {\n            event.startTracking();\n            return true;\n        }\n        return super.onKeyDown(keyCode, event);\n    }\n\n    @Override\n    public boolean onKeyUp(int keyCode, KeyEvent event) {\n        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {\n            if (getScale() > 1.0f) {\n                // If we're zoomed in, pressing Back jumps out to show the\n                // entire image, otherwise Back returns the user to the gallery\n                zoomTo(1.0f);\n                return true;\n            }\n        }\n        return super.onKeyUp(keyCode, event);\n    }\n\n    @Override\n    public void setImageBitmap(Bitmap bitmap) {\n        setImageBitmap(bitmap, 0);\n    }\n\n    private void setImageBitmap(Bitmap bitmap, int rotation) {\n        super.setImageBitmap(bitmap);\n        Drawable d = getDrawable();\n        if (d != null) {\n            d.setDither(true);\n        }\n\n        Bitmap old = bitmapDisplayed.getBitmap();\n        bitmapDisplayed.setBitmap(bitmap);\n        bitmapDisplayed.setRotation(rotation);\n\n        if (old != null && old != bitmap && recycler != null) {\n            recycler.recycle(old);\n        }\n    }\n\n    public void clear() {\n        setImageBitmapResetBase(null, true);\n    }\n\n\n    // This function changes bitmap, reset base matrix according to the size\n    // of the bitmap, and optionally reset the supplementary matrix\n    public void setImageBitmapResetBase(final Bitmap bitmap, final boolean resetSupp) {\n        setImageRotateBitmapResetBase(new RotateBitmap(bitmap, 0), resetSupp);\n    }\n\n    public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, final boolean resetSupp) {\n        final int viewWidth = getWidth();\n\n        if (viewWidth <= 0)  {\n            onLayoutRunnable = new Runnable() {\n                public void run() {\n                    setImageRotateBitmapResetBase(bitmap, resetSupp);\n                }\n            };\n            return;\n        }\n\n        if (bitmap.getBitmap() != null) {\n            getProperBaseMatrix(bitmap, baseMatrix, true);\n            setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());\n        } else {\n            baseMatrix.reset();\n            setImageBitmap(null);\n        }\n\n        if (resetSupp) {\n            suppMatrix.reset();\n        }\n        setImageMatrix(getImageViewMatrix());\n        maxZoom = calculateMaxZoom();\n    }\n\n    // Center as much as possible in one or both axis.  Centering is defined as follows:\n    // * If the image is scaled down below the view's dimensions then center it.\n    // * If the image is scaled larger than the view and is translated out of view then translate it back into view.\n    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(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": "aliocrlib/src/main/java/com/lib/aliocr/widget/crop/Log.java",
    "content": "package com.lib.aliocr.widget.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": "aliocrlib/src/main/java/com/lib/aliocr/widget/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.lib.aliocr.widget.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": "aliocrlib/src/main/java/com/lib/aliocr/widget/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.lib.aliocr.widget.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": "aliocrlib/src/main/java/com/lib/aliocr/widget/popup/XinPopWindow.java",
    "content": "package com.lib.aliocr.widget.popup;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.support.v4.content.ContextCompat;\nimport android.view.LayoutInflater;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.AdapterView;\nimport android.widget.BaseAdapter;\nimport android.widget.ListView;\nimport android.widget.PopupWindow;\nimport android.widget.TextView;\n\nimport com.lib.aliocr.R;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n/**\n * 使用方法：\n * <p>\n * <p>\n * List<XinPopWindow.MenuItem> menuItems=new ArrayList<>();\n * menuItems.add(new MyPopWindow.MenuItem(\"拍照\"));\n * menuItems.add(new MyPopWindow.MenuItem(\"找下那个\"));\n * menuItems.add(new MyPopWindow.MenuItem(\"拍照\"));\n * menuItems.add(new MyPopWindow.MenuItem(\"拍照\"));\n * <p>\n * XinPopWindow myPopWindow = new XinPopWindow(MainActivity.this);\n * myPopWindow.setData(menuItems);\n * myPopWindow.showAtLocation(root, Gravity.BOTTOM, 0, 0);\n * <p>\n * <p>\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class XinPopWindow extends PopupWindow {\n    OnItemClickListener mOnItemClickListener;\n    Activity mContext;\n    private Window mWindow;\n    private ListView mItems;\n    private MenuAdapter menuAdapter;\n\n    public XinPopWindow(Activity context) {\n        mContext = context;\n        mWindow = mContext.getWindow();\n        backgroundAlpha(1);\n        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);\n        //noinspection ConstantConditions\n        View mMenuView = inflater.inflate(R.layout.common_popwindow, null, false);\n\n        View btnCancel = mMenuView.findViewById(R.id.btn_cancel);\n        btnCancel.setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                dismiss();\n            }\n        });\n        mItems = mMenuView.findViewById(R.id.itemList);\n        menuAdapter = new MenuAdapter();\n        mItems.setAdapter(menuAdapter);\n        mItems.setOnItemClickListener(new AdapterView.OnItemClickListener() {\n                                          @Override\n                                          public void onItemClick(AdapterView<?> parent, View view, int position, long id) {\n                                              if (mOnItemClickListener != null)\n                                                  mOnItemClickListener.onItemClick(parent, view, position, id, menuAdapter.getMenuItems().get(position).type);\n                                          }\n                                      }\n        );\n\n        mMenuView.setOnTouchListener(new View.OnTouchListener() {\n            @SuppressLint(\"ClickableViewAccessibility\")\n            @Override\n            public boolean onTouch(View v, MotionEvent event) {\n                if (event.getAction() == MotionEvent.ACTION_UP || event.getY() < mItems.getTop())\n                    XinPopWindow.this.dismiss();\n                return true;\n            }\n        });\n        setOutsideTouchable(true);\n        this.setFocusable(true);\n        this.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);\n        this.setWidth(WindowManager.LayoutParams.MATCH_PARENT);\n        this.setHeight(WindowManager.LayoutParams.MATCH_PARENT);\n        this.setContentView(mMenuView);\n        setBackgroundDrawable(ContextCompat.getDrawable(mContext.getApplication(), android.R.color.transparent));\n        this.setAnimationStyle(R.style.common_PopupAnimation);\n\n\n        setOnDismissListener(new OnDismissListener() {\n            @Override\n            public void onDismiss() {\n                backgroundAlpha(1);\n            }\n        });\n    }\n\n    public void backgroundAlpha(float bgAlpha) {\n        if (mWindow == null) {\n            mWindow = mContext.getWindow();\n        }\n        if (mWindow == null)\n            return;\n        WindowManager.LayoutParams lp = mWindow.getAttributes();\n        mWindow.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);\n        lp.alpha = bgAlpha; //0.0-1.0\n        mWindow.setAttributes(lp);\n    }\n\n    @Override\n    public void showAtLocation(View parent, int gravity, int x, int y) {\n        super.showAtLocation(parent, gravity, x, y);\n        backgroundAlpha(0.6f);\n    }\n\n    public void setData(List<MenuItem> menuItems) {\n        menuAdapter.setMenuItems(menuItems);\n    }\n\n    public void setOnItemClickListener(final OnItemClickListener listener) {\n\n        mOnItemClickListener = listener;\n\n    }\n\n    /////////////////////////////////////事件监听\n\n    public interface OnItemClickListener {\n        void onItemClick(AdapterView<?> parent, View view, int position, long id, int type);\n    }\n\n    public static class MenuAdapter extends BaseAdapter {\n\n        List<MenuItem> menuItems;\n\n        public MenuAdapter() {\n\n            menuItems = new ArrayList<>();\n        }\n\n        public List<MenuItem> getMenuItems() {\n            return menuItems;\n        }\n\n        public void setMenuItems(List<MenuItem> menuItems) {\n            this.menuItems = menuItems;\n            notifyDataSetChanged();\n        }\n\n        static class ViewHolder {\n            TextView btn;\n            View line;\n\n        }\n\n        @Override\n        public int getCount() {\n            return menuItems.size();\n        }\n\n        @Override\n        public MenuItem getItem(int position) {\n            return menuItems.isEmpty() ? null : menuItems.get(position);\n        }\n\n        @Override\n        public long getItemId(int position) {\n            return position;\n        }\n\n        @Override\n        public View getView(int position, View convertView, ViewGroup parent) {\n            ViewHolder holder;\n            if (convertView == null) {\n                holder = new ViewHolder();\n                convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.common_item_popup, null, false);\n                holder.btn = convertView.findViewById(R.id.btn_);\n                holder.line = convertView.findViewById(R.id.line_);\n                convertView.setTag(holder);\n            } else {\n                holder = (ViewHolder) convertView.getTag();\n            }\n\n            holder.btn.setText(getItem(position).name);\n\n\n            if (position == 0) {\n                holder.btn.setBackgroundResource(R.drawable.common_btn_pop_choose_pic_up);\n            } else {\n                holder.btn.setBackgroundResource(R.drawable.common_btn_pop_choose_pic_mid);\n            }\n\n\n            if (position == getCount() - 1) {\n                holder.line.setVisibility(View.GONE);\n            } else {\n                holder.line.setVisibility(View.VISIBLE);\n            }\n\n            return convertView;\n        }\n\n\n    }\n\n    public static class MenuItem {\n        public String name;\n        public int type;\n\n        public MenuItem(String name, int type) {\n            this.name = name;\n            this.type = type;\n        }\n\n        @Override\n        public boolean equals(Object o) {\n            if (this == o) return true;\n            if (!(o instanceof MenuItem)) return false;\n\n            MenuItem menuItem = (MenuItem) o;\n\n            return type == menuItem.type && (name != null ? name.equals(menuItem.name) : menuItem.name == null);\n        }\n\n        @Override\n        public int hashCode() {\n            int result = name != null ? name.hashCode() : 0;\n            result = 31 * result + type;\n            return result;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "aliocrlib/src/main/res/anim/push_bottom_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"100%p\"\n        android:toYDelta=\"0\"\n        />\n    <alpha\n        android:duration=\"200\"\n        android:fromAlpha=\"0.0\"\n        android:toAlpha=\"1.0\"\n        />\n</set>"
  },
  {
    "path": "aliocrlib/src/main/res/anim/push_bottom_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- 上下滑入式 -->\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <translate\n        android:duration=\"200\"\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"50%p\"/>\n\n    <alpha\n        android:duration=\"200\"\n        android:fromAlpha=\"1.0\"\n        android:toAlpha=\"0.0\"/>\n\n</set>"
  },
  {
    "path": "aliocrlib/src/main/res/drawable/common_btn_pop_choose_pic_down.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/common_btn_album_pop_btn_bg_bottom_gray\" android:state_focused=\"true\"/>\n    <item android:drawable=\"@drawable/common_btn_album_pop_btn_bg_bottom_gray\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@drawable/common_btn_album_pop_btn_bg_bottom\"/>\n\n</selector>"
  },
  {
    "path": "aliocrlib/src/main/res/drawable/common_btn_pop_choose_pic_mid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@color/popup_gray\" android:state_focused=\"true\"/>\n    <item android:drawable=\"@color/popup_gray\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@color/white\"/>\n\n</selector>"
  },
  {
    "path": "aliocrlib/src/main/res/drawable/common_btn_pop_choose_pic_up.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:drawable=\"@drawable/common_btn_album_pop_btn_bg_top_gray\" android:state_focused=\"true\"/>\n    <item android:drawable=\"@drawable/common_btn_album_pop_btn_bg_top_gray\" android:state_pressed=\"true\"/>\n    <item android:drawable=\"@drawable/common_btn_album_pop_btn_bg_top\"/>\n\n</selector>"
  },
  {
    "path": "aliocrlib/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": "aliocrlib/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": "aliocrlib/src/main/res/drawable/ic_camera.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2016 The Android Open Source Project\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         http://www.apache.org/licenses/LICENSE-2.0\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<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0\"/>\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z\"/>\n</vector>\n"
  },
  {
    "path": "aliocrlib/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": "aliocrlib/src/main/res/layout/activity_ocr_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout\n    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    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    tools:context=\"com.lib.aliocr.view.OCRMainActivity\">\n\n\n    <cameraview.CameraView\n        android:id=\"@+id/my_camera\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:adjustViewBounds=\"true\"\n        android:background=\"@android:color/black\"/>\n\n    <android.support.design.widget.FloatingActionButton\n        android:id=\"@+id/take_picture_\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_centerHorizontal=\"true\"\n        app:srcCompat=\"@drawable/ic_camera\"\n        app:useCompatPadding=\"true\"\n        tools:ignore=\"VectorDrawableCompat\"/>\n\n\n</RelativeLayout>\n"
  },
  {
    "path": "aliocrlib/src/main/res/layout/common_item_popup.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/btn_\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dip\"\n        android:gravity=\"center\"\n        android:paddingBottom=\"14dp\"\n        android:paddingTop=\"14dp\"\n        android:textColor=\"#636B73\"\n        android:textSize=\"16sp\"\n        />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/white\">\n\n        <View\n            android:id=\"@+id/line_\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"1px\"\n\n            android:layout_marginLeft=\"15dp\"\n            android:layout_marginRight=\"15dp\"\n            android:background=\"@color/line_color\"\n            />\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "aliocrlib/src/main/res/layout/common_popwindow.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout 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    android:background=\"@android:color/transparent\">\n\n    <RelativeLayout\n        android:id=\"@+id/popup_root\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentBottom=\"true\"\n        android:focusable=\"true\"\n        android:focusableInTouchMode=\"true\"\n        tools:ignore=\"UselessParent\">\n\n        <!-- 取消按钮 -->\n\n        <TextView\n            android:id=\"@+id/btn_cancel\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_alignParentBottom=\"true\"\n            android:background=\"@drawable/common_btn_pop_choose_pic_mid\"\n            android:gravity=\"center\"\n            android:paddingBottom=\"14dp\"\n            android:paddingTop=\"14dp\"\n            android:text=\"@string/cancel\"\n            android:textColor=\"#636B73\"\n            android:textSize=\"16sp\" />\n\n        <View\n            android:id=\"@+id/line_cancel\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"10dp\"\n            android:layout_above=\"@+id/btn_cancel\"\n            android:background=\"@color/line_color\" />\n\n\n        <ListView\n            android:id=\"@+id/itemList\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_above=\"@id/line_cancel\"\n            android:cacheColorHint=\"@android:color/transparent\"\n            android:divider=\"@null\"\n            android:dividerHeight=\"0dp\" />\n\n\n    </RelativeLayout>\n\n</RelativeLayout>"
  },
  {
    "path": "aliocrlib/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.lib.aliocr.widget.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": "aliocrlib/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": "aliocrlib/src/main/res/layout/fragment_ocr_launch.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n\n    <RelativeLayout\n        android:id=\"@+id/side_face\"\n        android:layout_marginTop=\"20dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"200dp\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:background=\"@color/popup_gray\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:text=\"正面\"/>\n\n    </RelativeLayout>\n\n\n    <RelativeLayout\n        android:id=\"@+id/side_back\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"200dp\"\n        android:layout_marginLeft=\"16dp\"\n        android:layout_marginRight=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:background=\"@color/popup_gray\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_centerInParent=\"true\"\n            android:text=\"反面\"/>\n    </RelativeLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "aliocrlib/src/main/res/layout/surface_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2016 The Android Open Source Project\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         http://www.apache.org/licenses/LICENSE-2.0\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<merge xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <SurfaceView\n        android:id=\"@+id/surface_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center\"\n        />\n\n</merge>\n"
  },
  {
    "path": "aliocrlib/src/main/res/layout-v14/texture_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2016 The Android Open Source Project\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         http://www.apache.org/licenses/LICENSE-2.0\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<merge xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <TextureView\n        android:id=\"@+id/texture_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center\"\n        />\n\n</merge>\n"
  },
  {
    "path": "aliocrlib/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2016 The Android Open Source Project\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         http://www.apache.org/licenses/LICENSE-2.0\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<resources>\n    <declare-styleable name=\"CameraView\">\n        <!--\n          Set this to true if you want the CameraView to adjust its bounds to preserve the aspect\n          ratio of its camera preview.\n        -->\n        <attr name=\"android:adjustViewBounds\"/>\n        <!-- Direction the camera faces relative to device screen. -->\n        <attr name=\"facing\" format=\"enum\">\n            <!-- The camera device faces the opposite direction as the device's screen. -->\n            <enum name=\"back\" value=\"0\"/>\n            <!-- The camera device faces the same direction as the device's screen. -->\n            <enum name=\"front\" value=\"1\"/>\n        </attr>\n        <!-- Aspect ratio of camera preview and pictures. -->\n        <attr name=\"aspectRatio\" format=\"string\"/>\n        <!-- Continuous auto focus mode. -->\n        <attr name=\"autoFocus\" format=\"boolean\"/>\n        <!-- The flash mode. -->\n        <attr name=\"flash\" format=\"enum\">\n            <!-- Flash will not be fired. -->\n            <enum name=\"off\" value=\"0\"/>\n            <!--\n              Flash will always be fired during snapshot.\n              The flash may also be fired during preview or auto-focus depending on the driver.\n            -->\n            <enum name=\"on\" value=\"1\"/>\n            <!--\n              Constant emission of light during preview, auto-focus and snapshot.\n              This can also be used for video recording.\n            -->\n            <enum name=\"torch\" value=\"2\"/>\n            <!--\n              Flash will be fired automatically when required.\n              The flash may be fired during preview, auto-focus, or snapshot depending on the\n              driver.\n            -->\n            <enum name=\"auto\" value=\"3\"/>\n            <!--\n              Flash will be fired in red-eye reduction mode.\n            -->\n            <enum name=\"redEye\" value=\"4\"/>\n        </attr>\n    </declare-styleable>\n\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</resources>\n"
  },
  {
    "path": "aliocrlib/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"white\">@android:color/white</color>\n    <color name=\"popup_gray\">#DFE1E0</color>\n    <color name=\"line_color\">#EFF0F0</color>\n\n    <color name=\"crop__button_bar\">#f3f3f3</color>\n    <color name=\"crop__button_text\">#666666</color>\n    <color name=\"crop__selector_pressed\">#1a000000</color>\n    <color name=\"crop__selector_focused\">#77000000</color>\n</resources>"
  },
  {
    "path": "aliocrlib/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <dimen name=\"crop__bar_height\">56dp</dimen>\n</resources>"
  },
  {
    "path": "aliocrlib/src/main/res/values/public.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2016 The Android Open Source Project\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         http://www.apache.org/licenses/LICENSE-2.0\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<resources>\n    <public name=\"facing\" type=\"attr\"/>\n    <public name=\"aspectRatio\" type=\"attr\"/>\n    <public name=\"autoFocus\" type=\"attr\"/>\n    <public name=\"flash\" type=\"attr\"/>\n\n    <public name=\"Widget.CameraView\" type=\"style\"/>\n</resources>\n"
  },
  {
    "path": "aliocrlib/src/main/res/values/strings.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <string name=\"app_name\">aliocrlib</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"pick_image_intent_chooser_title\">选择图片</string>\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</resources>\n"
  },
  {
    "path": "aliocrlib/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n    Copyright (C) 2016 The Android Open Source Project\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         http://www.apache.org/licenses/LICENSE-2.0\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<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <style name=\"Widget.CameraView\" parent=\"android:Widget\">\n        <item name=\"android:adjustViewBounds\">false</item>\n        <item name=\"facing\">back</item>\n        <item name=\"aspectRatio\">4:3</item>\n        <item name=\"autoFocus\">true</item>\n        <item name=\"flash\">auto</item>\n    </style>\n\n    <!--弹框-->\n    <style name=\"common_PopupAnimation\" parent=\"android:Animation\">\n        <item name=\"android:windowEnterAnimation\">@anim/push_bottom_in</item>\n        <item name=\"android:windowExitAnimation\">@anim/push_bottom_out</item>\n    </style>\n\n    <style name=\"Crop\"></style>\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>\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n    defaultConfig {\n        applicationId \"com.xin.lockdemo\"\n        minSdkVersion rootProject.ext.android[\"minSdkVersion\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode 1\n        versionName rootProject.ext.android[\"versionName\"]\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    /* android support */\n    compile rootProject.ext.dependencies[\"appcompat-v7\"]\n    compile project(':gesturepswlib')\n    implementation project(':aliocrlib')\n    implementation project(':fingerprintlib')\n}\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.xin.lockdemo\"\n          xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:name=\"com.xin.lockdemo.App\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\"com.xin.lockdemo.MainActivity\"\n            android:screenOrientation=\"portrait\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <activity android:name=\"com.xin.lockdemo.GesturePswMainActivity\"/>\n         </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/xin/lockdemo/App.java",
    "content": "package com.xin.lockdemo;\n\nimport android.app.Application;\n\nimport com.lib.lock.fingerprint.utils.FingerContext;\nimport com.lib.lock.gesture.utils.ContextUtils;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class App extends Application {\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        //   ContextFingerUtils.init(this);\n        FingerContext.init(this);\n        ContextUtils.init(this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/xin/lockdemo/GesturePswMainActivity.java",
    "content": "package com.xin.lockdemo;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v7.app.AppCompatActivity;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.Button;\n\nimport com.lib.lock.gesture.activities.GestureSettingsActivity;\nimport com.lib.lock.gesture.activities.GestureVerifyActivity;\nimport com.lib.lock.gesture.content.SPManager;\n\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class GesturePswMainActivity extends AppCompatActivity implements View.OnClickListener {\n\n    private Button mBtnPswSettings;\n    private Button mBtnVerifyPsw;\n\n\n    public static void Launcher(Context context) {\n\n        Intent intent = new Intent(context, GesturePswMainActivity.class);\n\n        if (!(context instanceof Activity)) {\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n\n        context.startActivity(intent);\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(com.xin.lockdemo.R.layout.activity_gesture_psw_main);\n\n        mBtnPswSettings = findViewById(com.xin.lockdemo.R.id.btn_settings_gesture_password);\n        mBtnVerifyPsw = findViewById(com.xin.lockdemo.R.id.btn_verify_gesture_password);\n\n        mBtnPswSettings.setOnClickListener(this);\n        mBtnVerifyPsw.setOnClickListener(this);\n    }\n\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        if (hasPsw()) {\n            mBtnVerifyPsw.setVisibility(View.VISIBLE);\n        }\n\n    }\n\n\n    @Override\n    public void onClick(View v) {\n        switch (v.getId()) {\n            case  R.id.btn_settings_gesture_password:\n\n                if (hasPsw()) {\n                    GestureVerifyActivity.launch(this,true);\n                } else {\n                    GestureSettingsActivity.launch(this);\n                }\n\n                break;\n\n            case  R.id.btn_verify_gesture_password:\n                GestureVerifyActivity.launch(this,false);\n                break;\n\n        }\n    }\n\n\n    private boolean hasPsw() {\n        return !TextUtils.isEmpty(SPManager.getInstance().getPatternPSW());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/xin/lockdemo/MainActivity.java",
    "content": "package com.xin.lockdemo;\n\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.support.v4.app.FragmentManager;\nimport android.support.v4.app.FragmentTransaction;\nimport android.support.v7.app.AppCompatActivity;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.Toast;\n\nimport com.lib.aliocr.view.OcrLaunchFragment;\nimport com.lib.lock.fingerprint.activities.FingerprintPswMainActivity;\nimport com.lib.lock.fingerprint.core.FingerprintCore;\nimport com.lib.lock.fingerprint.utils.FingerprintUtil;\nimport com.lib.lock.gesture.content.SPManager;\nimport com.xin.lockdemo.dialog.CustomDialog;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\npublic class MainActivity extends AppCompatActivity implements View.OnClickListener {\n\n    private Button mBtnFingprint;\n    private Uri imgUri;\n    private FragmentManager fragmentManager;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(com.xin.lockdemo.R.layout.activity_main);\n\n        findViewById(com.xin.lockdemo.R.id.gesture_password).setOnClickListener(this);\n        findViewById(com.xin.lockdemo.R.id.ocr_auth).setOnClickListener(this);\n        mBtnFingprint = findViewById(com.xin.lockdemo.R.id.fingerprint_password);\n        mBtnFingprint.setOnClickListener(this);\n        fragmentManager = getSupportFragmentManager();\n\n    }\n\n    @Override\n    public void onClick(View v) {\n        switch (v.getId()) {\n\n            case com.xin.lockdemo.R.id.ocr_auth:\n\n                FragmentTransaction transition = fragmentManager.beginTransaction();\n\n                transition.replace(com.xin.lockdemo.R.id.fragment_content_root, new OcrLaunchFragment(), OcrLaunchFragment.class.getSimpleName());\n                if (transition.isAddToBackStackAllowed())\n                    transition.addToBackStack(OcrLaunchFragment.class.getSimpleName());\n                transition.commit();\n                findViewById(com.xin.lockdemo.R.id.layout_).setVisibility(View.GONE);\n                break;\n            case com.xin.lockdemo.R.id.gesture_password:\n                GesturePswMainActivity.Launcher(this);\n                break;\n            case com.xin.lockdemo.R.id.fingerprint_password:\n\n                FingerprintUtil.startFingerprintRecognition(this, null);\n                //    authActivity();\n\n\n                break;\n        }\n    }\n\n\n    @Override\n    public void onBackPressed() {\n\n        if (fragmentManager.getBackStackEntryCount() > 0) {\n            fragmentManager.popBackStack();\n            findViewById(com.xin.lockdemo.R.id.layout_).setVisibility(View.VISIBLE);\n        } else {\n\n            super.onBackPressed();\n        }\n\n    }\n\n    /**\n     * 跳转验证\n     */\n    private void authActivity() {\n\n        if (\"验证指纹密码\".contentEquals(mBtnFingprint.getText())) {\n\n            FingerprintPswMainActivity.Launcher(this);\n            return;\n        }\n        if (!FingerprintCore.getInstance().isSupport()) {\n            Toast.makeText(this, \"系统不支持指纹识别\", Toast.LENGTH_LONG).show();\n            return;\n        }\n        if (!FingerprintCore.getInstance().isHasEnrolledFingerprints()) {\n            Toast.makeText(this, \"请检查是否录入指纹\", Toast.LENGTH_LONG).show();\n            final CustomDialog selfDialog = new CustomDialog(this);\n\n            View view = LayoutInflater.from(this).inflate(com.xin.lockdemo.R.layout.login_finger_change_dialog, null, false);\n\n            view.findViewById(com.xin.lockdemo.R.id.open_fingerprint).setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    FingerprintUtil.openFingerPrintSettingPage(getApplicationContext());\n                }\n            });\n            selfDialog.setCustom(view);\n\n            selfDialog.show();\n\n            return;\n        } else {\n            SPManager.getInstance().setHasFingerPrint(true);\n            mBtnFingprint.setText(\"验证指纹密码\");\n        }\n    }\n\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n\n        if (FingerprintUtil.supportAndSysOpenedFingerPrint() &&\n                SPManager.getInstance().getHasFingerPrint()) {\n            mBtnFingprint.setText(\"验证指纹密码\");\n        } else {\n            mBtnFingprint.setText(\"开启指纹密码\");\n        }\n    }\n\n\n\n   /* @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n\n        Ocr.onRequestPermissionsResult(this, requestCode, permissions, grantResults, imgUri);\n    }*/\n}\n"
  },
  {
    "path": "app/src/main/java/com/xin/lockdemo/dialog/CustomDialog.java",
    "content": "package com.xin.lockdemo.dialog;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.RelativeLayout;\n\nimport com.xin.lockdemo.R;\n\n\n/**\n * 使用方法：\n * final CustomDialog dialog = new CustomDialog(MainActivity.this);\n * <p>\n * dialog.setCustom(你的布局);\n * dialog.show();\n *\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class CustomDialog extends Dialog {\n\n\n    private View mContentView;\n\n\n    private RelativeLayout contentRoot;\n\n\n\n    public CustomDialog(Context context) {\n        super(context, R.style.dialog_normal);\n\n    }\n\n\n    @Override\n\n\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.common_dialog_custom_view_layout);\n        //按空白处不能取消动画\n        setCanceledOnTouchOutside(true);\n\n        //初始化界面控件\n        initView();\n        //初始化界面数据\n        initData();\n        //初始化界面控件的事件\n        initEvent();\n\n\n    }\n\n\n    private void initView() {\n\n        contentRoot = findViewById(R.id.content_view_root);\n\n    }\n\n    /**\n     * * 初始化界面控件的显示数据\n     */\n\n\n    private void initData() {\n\n\n        if (mContentView != null) {\n            RelativeLayout.LayoutParams params = new RelativeLayout.\n                    LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,\n                    ViewGroup.LayoutParams.WRAP_CONTENT);\n            contentRoot.addView(mContentView, params);\n\n        }\n\n    }\n\n    /**\n     * * 初始化界面控件\n     */\n\n    private void initEvent() {\n\n\n    }\n\n\n    public View getContentView() {\n        return mContentView;\n    }\n\n    public void setCustom(View mContentView) {\n        this.mContentView = mContentView;\n\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportHeight=\"108\"\n    android:viewportWidth=\"108\">\n    <path\n        android:fillColor=\"#26A69A\"\n        android:pathData=\"M0,0h108v108h-108z\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeColor=\"#33FFFFFF\"\n        android:strokeWidth=\"0.8\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/login_shape_bg_finger_change_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <corners android:radius=\"2dp\"/>\n    <solid android:color=\"@android:color/white\"/>\n</shape>"
  },
  {
    "path": "app/src/main/res/layout/activity_gesture_psw_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              android:gravity=\"center_horizontal\"\n              android:orientation=\"vertical\">\n\n\n    <Button\n        android:id=\"@+id/btn_settings_gesture_password\"\n        android:layout_width=\"150dp\"\n        android:layout_height=\"45dp\"\n        android:layout_marginTop=\"100dp\"\n        android:textColor=\"@android:color/white\"\n        android:background=\"@drawable/btn_blue_normal\"\n        android:text=\"设置手势密码\"/>\n\n\n    <Button\n        android:id=\"@+id/btn_verify_gesture_password\"\n        android:layout_width=\"150dp\"\n        android:layout_height=\"45dp\"\n        android:textColor=\"@android:color/white\"\n        android:background=\"@drawable/btn_blue_normal\"\n        android:layout_marginTop=\"50dp\"\n        android:visibility=\"gone\"\n        android:text=\"验证手势密码\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_height=\"match_parent\"\n    android:id=\"@+id/root_main\"\n    android:layout_width=\"match_parent\">\n\n    <LinearLayout\n        android:id=\"@+id/layout_\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:gravity=\"center\"\n        >\n\n        <Button\n            android:id=\"@+id/gesture_password\"\n            android:layout_width=\"200dp\"\n            android:layout_height=\"40dp\"\n            android:layout_centerHorizontal=\"true\"\n            android:background=\"@drawable/btn_blue_normal\"\n            android:text=\"手势密码\"\n            android:textColor=\"@android:color/white\"/>\n\n\n        <Button\n            android:id=\"@+id/fingerprint_password\"\n            android:layout_width=\"200dp\"\n            android:layout_height=\"40dp\"\n            android:layout_centerHorizontal=\"true\"\n            android:layout_marginTop=\"50dp\"\n            android:background=\"@drawable/btn_blue_normal\"\n            android:text=\"开启指纹密码\"\n            android:textColor=\"@android:color/white\"/>\n\n\n        <Button\n            android:id=\"@+id/ocr_auth\"\n            android:layout_width=\"200dp\"\n            android:layout_height=\"40dp\"\n            android:layout_centerHorizontal=\"true\"\n            android:layout_marginTop=\"50dp\"\n            android:background=\"@drawable/btn_blue_normal\"\n            android:text=\"身份证识别\"\n            android:textColor=\"@android:color/white\"/>\n\n    </LinearLayout>\n\n    <FrameLayout\n        android:id=\"@+id/fragment_content_root\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n\n</FrameLayout>\n\n\n"
  },
  {
    "path": "app/src/main/res/layout/common_dialog_custom_view_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:paddingLeft=\"42dp\"\n                android:paddingRight=\"42dp\">\n    <!-- android:background=\"#11ffffff\"-->\n\n\n    <RelativeLayout\n        android:layout_centerInParent=\"true\"\n        android:id=\"@+id/content_view_root\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/login_finger_change_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:background=\"@drawable/login_shape_bg_finger_change_dialog\"\n              android:gravity=\"center_horizontal\"\n              android:orientation=\"vertical\"\n              android:paddingBottom=\"30dp\"\n              android:paddingTop=\"30dp\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/login_dialog_title_finger_change\"\n        android:textColor=\"@color/common_text_color\"\n        android:textSize=\"16sp\"/>\n\n\n\n    <TextView\n        android:id=\"@+id/open_fingerprint\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"45dp\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginLeft=\"30dp\"\n        android:layout_marginRight=\"30dp\"\n        android:layout_marginTop=\"14dp\"\n        android:background=\"@drawable/btn_blue_normal\"\n        android:gravity=\"center\"\n        android:text=\"@string/login_btn_text_fingerprint_psw\"\n        android:textColor=\"@android:color/white\"\n        android:textSize=\"16sp\"/>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#15A9F2</color>\n    <color name=\"colorPrimaryDark\">#15A9F2</color>\n    <color name=\"colorAccent\">#FF4081</color>\n\n\n    <color name=\"common_text_color\">#333333</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">lock demo</string>\n\n    <string name=\"login_btn_text_fingerprint_psw\">设置录纹密码</string>\n    <string name=\"login_dialog_title_finger_change\">设置开启或录入指纹登录</string>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n\n    <style name=\"dialog_normal\" parent=\"@android:style/Theme.Dialog\">\n        <item name=\"android:windowFrame\">@null</item>\n        <item name=\"android:windowIsFloating\">true</item>\n        <item name=\"android:windowIsTranslucent\">false</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:backgroundDimEnabled\">true</item>\n        <item name=\"android:windowAnimationStyle\">@android:style/Animation.Dialog</item>\n    </style>\n\n\n</resources>\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    apply from: \"config.gradle\"\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.1.3'\n        \n\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "config.gradle",
    "content": "ext {\n\n\n    android = [\n            compileSdkVersion: 26,\n            minSdkVersion    : 16,\n            targetSdkVersion : 26,\n            versionName      : \"1.0.0\",\n\n    ]\n\n\n    version = [\n            androidSupportSdkVersion: \"26.1.0\",\n    ]\n\n\n    dependencies = [\n            //Android support\n            \"appcompat-v7\"                 : \"com.android.support:appcompat-v7:${version[\"androidSupportSdkVersion\"]}\",\n    ]\n}"
  },
  {
    "path": "fingerprintlib/README.MD",
    "content": "## 指纹识别封装\n\n#### 使用：\n  程序入口处或使用前初始化指纹密码\n    FingerContext.init(this);\n\n\n * 作者：xin\n * 邮箱：ittfxin@126.com\n"
  },
  {
    "path": "fingerprintlib/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 27\n    defaultConfig {\n        minSdkVersion 16\n        targetSdkVersion 27\n        versionCode 1\n        versionName \"1.0.0\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n}\n\ndependencies {\n    //   implementation fileTree(dir: 'libs', include: ['*.jar'])\n    /* android support */\n    // implementation rootProject.ext.dependencies[\"appcompat-v7\"]\n    compile 'com.android.support:appcompat-v7:27.1.1'\n}\n"
  },
  {
    "path": "fingerprintlib/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "fingerprintlib/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.lib.lock.fingerprint\"\n          xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.USE_FINGERPRINT\"/>\n\n\n    <application>\n        <activity android:name=\".activities.FingerprintPswMainActivity\"\n            android:screenOrientation=\"portrait\"/>\n    </application>\n</manifest>\n"
  },
  {
    "path": "fingerprintlib/src/main/java/com/lib/lock/fingerprint/activities/FingerprintPswMainActivity.java",
    "content": "package com.lib.lock.fingerprint.activities;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v7.app.AppCompatActivity;\nimport android.widget.Toast;\n\nimport com.lib.lock.fingerprint.R;\nimport com.lib.lock.fingerprint.core.FingerprintCore;\n\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class FingerprintPswMainActivity extends AppCompatActivity {\n\n\n    public static void Launcher(Context context) {\n\n        Intent intent = new Intent(context, FingerprintPswMainActivity.class);\n\n        if (!(context instanceof Activity)) {\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        }\n\n        context.startActivity(intent);\n    }\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        setContentView(R.layout.activity_fingerprint_psw_main);\n\n        FingerprintCore.getInstance().setFingerprintManager(new FingerprintCore.IFingerprintResultListener() {\n            @Override\n            public void onAuthenticateSuccess() {\n                Toast.makeText(getApplicationContext(),\"指纹识别成功\",Toast.LENGTH_LONG).show();\n            }\n\n            @Override\n            public void onAuthenticateFailed(int helpId,String msg) {\n                Toast.makeText(getApplicationContext(),\"指纹识别失败，请重试！\",Toast.LENGTH_LONG).show();\n            }\n\n            @Override\n            public void onAuthenticateError(int errMsgId) {\n                Toast.makeText(getApplicationContext(),\"指纹识别错误，等待几秒之后再重试\",Toast.LENGTH_LONG).show();\n            }\n\n            @Override\n            public void onStartAuthenticateResult(boolean isSuccess) {\n\n            }\n        });\n\n        startFingerprintRecognition();\n    }\n\n\n    /**\n     * 开始指纹识别\n     */\n    private void startFingerprintRecognition() {\n\n\n        if (FingerprintCore.getInstance().isAuthenticating()) {\n            Toast.makeText(this, \"指纹识别已经开启，长按指纹解锁键\", Toast.LENGTH_LONG).show();\n        } else {\n            FingerprintCore.getInstance().startAuthenticate();\n        }\n    }\n\n\n    /**\n     * 取消指纹识别\n     */\n    private void cancelFingerprintRecognition() {\n        if (FingerprintCore.getInstance().isAuthenticating()) {\n            FingerprintCore.getInstance().cancelAuthenticate();\n        }\n    }\n\n\n    @Override\n    protected void onDestroy() {\n        super.onDestroy();\n        FingerprintCore.getInstance().cancelAuthenticate();\n        FingerprintCore.getInstance().setFingerprintManager(null);\n    }\n}\n"
  },
  {
    "path": "fingerprintlib/src/main/java/com/lib/lock/fingerprint/core/CryptoObjectCreator.java",
    "content": "package com.lib.lock.fingerprint.core;\n\nimport android.annotation.TargetApi;\nimport android.hardware.fingerprint.FingerprintManager;\nimport android.os.Build;\nimport android.security.keystore.KeyGenParameterSpec;\nimport android.security.keystore.KeyPermanentlyInvalidatedException;\nimport android.security.keystore.KeyProperties;\nimport android.util.Log;\n\nimport java.io.IOException;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.security.InvalidKeyException;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.UnrecoverableKeyException;\nimport java.security.cert.CertificateException;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.KeyGenerator;\nimport javax.crypto.SecretKey;\n\n\n\n\n\n@TargetApi(Build.VERSION_CODES.M)\npublic class CryptoObjectCreator {\n\n    private static final String KEY_NAME = \"crypto_object_fingerprint_key\";\n\n    private FingerprintManager.CryptoObject mCryptoObject;\n    private KeyStore mKeyStore;\n    private KeyGenerator mKeyGenerator;\n    private Cipher mCipher;\n\n    public interface ICryptoObjectCreateListener {\n        void onDataPrepared(FingerprintManager.CryptoObject cryptoObject);\n    }\n\n    public CryptoObjectCreator(ICryptoObjectCreateListener createListener) {\n        mKeyStore = providesKeystore();\n        mKeyGenerator = providesKeyGenerator();\n        mCipher = providesCipher(mKeyStore);\n        if (mKeyStore != null && mKeyGenerator != null && mCipher != null) {\n            mCryptoObject = new FingerprintManager.CryptoObject(mCipher);\n        }\n        prepareData(createListener);\n    }\n\n    private void prepareData(final ICryptoObjectCreateListener createListener) {\n        new Thread(\"FingerprintLogic:InitThread\") {\n            @Override\n            public void run() {\n                try {\n                    if (mCryptoObject != null) {\n                        createKey();\n                        // Set up the crypto object for later. The object will be authenticated by use\n                        // of the fingerprint.\n                        if (!initCipher()) {\n                            Log.i(\"\",\"Failed to init Cipher.\");\n                        }\n                    }\n                } catch (Exception e) {\n                    Log.e(\"\",\" Failed to init Cipher, e:\" );\n                }\n                if (createListener != null) {\n                    createListener.onDataPrepared(mCryptoObject);\n                }\n            }\n        }.start();\n    }\n\n    /**\n     * Creates a symmetric key in the Android Key Store which can only be used after the user has\n     * authenticated with fingerprint.\n     */\n    private void createKey() {\n        // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint\n        // for your flow. Use of keys is necessary if you need to know if the set of\n        // enrolled fingerprints has changed.\n        try {\n            mKeyStore.load(null);\n            // Set the alias of the entry in Android KeyStore where the key will appear\n            // and the constrains (purposes) in the constructor of the Builder\n            mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,\n                    KeyProperties.PURPOSE_ENCRYPT |\n                            KeyProperties.PURPOSE_DECRYPT)\n                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)\n                    // Require the user to authenticate with a fingerprint to authorize every use\n                    // of the key\n                    .setUserAuthenticationRequired(true)\n                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)\n                    .build());\n            mKeyGenerator.generateKey();\n        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException\n                | CertificateException | IOException e) {\n            Log.e(\"\",\" Failed to createKey, e:\");\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Initialize the {@link Cipher} instance with the created key in the {@link #createKey()}\n     * method.\n     *\n     * @return {@code true} if initialization is successful, {@code false} if the lock screen has\n     * been disabled or reset after the key was generated, or if a fingerprint got enrolled after\n     * the key was generated.\n     */\n    private boolean initCipher() {\n        try {\n            mKeyStore.load(null);\n            SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null);\n            mCipher.init(Cipher.ENCRYPT_MODE, key);\n            return true;\n        } catch (KeyPermanentlyInvalidatedException e) {\n             Log.e(\"\",\" Failed to initCipher, e:\"  );\n            return false;\n        } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException\n                | NoSuchAlgorithmException | InvalidKeyException e) {\n             Log.e(\"\",\" Failed to initCipher, e :\"  );\n            throw new RuntimeException(\"Failed to init Cipher\", e);\n        }\n    }\n\n    public static KeyStore providesKeystore() {\n        try {\n            return KeyStore.getInstance(\"AndroidKeyStore\");\n        } catch (Throwable e) {\n            return null;\n        }\n    }\n\n    public static KeyGenerator providesKeyGenerator() {\n        try {\n            return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, \"AndroidKeyStore\");\n        } catch (Throwable e) {\n            return null;\n        }\n    }\n\n    public static Cipher providesCipher(KeyStore keyStore) {\n        try {\n            return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + \"/\"\n                    + KeyProperties.BLOCK_MODE_CBC + \"/\"\n                    + KeyProperties.ENCRYPTION_PADDING_PKCS7);\n        } catch (Throwable e) {\n            return null;\n        }\n    }\n\n    public FingerprintManager.CryptoObject getCryptoObject() {\n        return mCryptoObject;\n    }\n\n    public void onDestroy() {\n        mCipher = null;\n        mCryptoObject = null;\n        mCipher = null;\n        mKeyStore = null;\n    }\n}\n"
  },
  {
    "path": "fingerprintlib/src/main/java/com/lib/lock/fingerprint/core/FingerprintCore.java",
    "content": "package com.lib.lock.fingerprint.core;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.hardware.fingerprint.FingerprintManager;\nimport android.os.Build;\nimport android.os.CancellationSignal;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.util.Log;\n\nimport com.lib.lock.fingerprint.utils.FingerContext;\n\nimport java.lang.ref.WeakReference;\n\n\n\n\n\n@TargetApi(Build.VERSION_CODES.M)\npublic class FingerprintCore {\n\n    private static final int NONE = 0;\n    private static final int CANCEL = 1;\n    private static final int AUTHENTICATING = 2;\n    private int mState = NONE;\n\n    private FingerprintManager mFingerprintManager;\n    private WeakReference<IFingerprintResultListener> mFpResultListener;\n    private CancellationSignal mCancellationSignal;\n    private CryptoObjectCreator mCryptoObjectCreator;\n    private FingerprintManager.AuthenticationCallback mAuthCallback;\n\n    private int mFailedTimes = 0;\n    private boolean isSupport;\n    private Handler mHandler = new Handler(Looper.getMainLooper());\n    public boolean isFirst = true;\n\n\n    /**\n     * 指纹识别回调接口\n     */\n    public interface IFingerprintResultListener {\n        /** 指纹识别成功 */\n        void onAuthenticateSuccess();\n\n        /** 指纹识别失败 */\n        void onAuthenticateFailed(int helpId,String errString);\n\n        /** 指纹识别发生错误-不可短暂恢复 */\n        void onAuthenticateError(int errMsgId);\n\n        /** 开始指纹识别监听成功 */\n        void onStartAuthenticateResult(boolean isSuccess);\n    }\n\n    private FingerprintCore(Context context) {\n        mFingerprintManager = getFingerprintManager(context);\n        isSupport = (mFingerprintManager != null && isHardwareDetected());\n\n        initCryptoObject();\n    }\n\n    private void initCryptoObject() {\n        try {\n            mCryptoObjectCreator = new  CryptoObjectCreator(new  CryptoObjectCreator.ICryptoObjectCreateListener() {\n                @Override\n                public void onDataPrepared(FingerprintManager.CryptoObject cryptoObject) {\n                    // startAuthenticate(cryptoObject);\n                    // 如果需要一开始就进行指纹识别，可以在秘钥数据创建之后就启动指纹认证\n                }\n            });\n        } catch (Throwable throwable) {\n\n        }\n    }\n\n    public void setFingerprintManager(IFingerprintResultListener fingerprintResultListener) {\n        mFpResultListener = new WeakReference<>(fingerprintResultListener);\n    }\n\n    public void startAuthenticate() {\n        startAuthenticate(mCryptoObjectCreator.getCryptoObject());\n    }\n\n    public boolean isAuthenticating() {\n        return mState == AUTHENTICATING;\n    }\n\n    private void startAuthenticate(FingerprintManager.CryptoObject cryptoObject) {\n        prepareData();\n        mState = AUTHENTICATING;\n        try {\n            mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, 0, mAuthCallback, null);\n            notifyStartAuthenticateResult(true, \"\");\n        } catch (SecurityException e) {\n            try {\n                mFingerprintManager.authenticate(null, mCancellationSignal, 0, mAuthCallback, null);\n                notifyStartAuthenticateResult(true, \"\");\n            } catch (SecurityException e2) {\n                notifyStartAuthenticateResult(false,Log.getStackTraceString(e2));\n\n            } catch (Throwable throwable) {\n\n            }\n        } catch (Throwable throwable) {\n\n        }\n    }\n\n    private void notifyStartAuthenticateResult(boolean isSuccess, String exceptionMsg) {\n        if (isSuccess) {\n\n            if (mFpResultListener.get() != null) {\n                mFpResultListener.get().onStartAuthenticateResult(true);\n            }\n        } else {\n\n            if (mFpResultListener.get() != null) {\n                mFpResultListener.get().onStartAuthenticateResult(false);\n            }\n        }\n    }\n\n    private void notifyAuthenticationSucceeded() {\n        mFailedTimes = 0;\n        if (null != mFpResultListener && null != mFpResultListener.get()) {\n            mFpResultListener.get().onAuthenticateSuccess();\n        }\n    }\n\n    private void notifyAuthenticationError(int errMsgId, CharSequence errString) {\n\n        if (null != mFpResultListener && null != mFpResultListener.get()) {\n            mFpResultListener.get().onAuthenticateError(errMsgId);\n        }\n    }\n\n    private void notifyAuthenticationFailed(int msgId, String errString) {\n\n        if (null != mFpResultListener && null != mFpResultListener.get()) {\n            mFpResultListener.get().onAuthenticateFailed(msgId,errString);\n        }\n    }\n\n    private void prepareData() {\n        if (mCancellationSignal == null) {\n            mCancellationSignal = new CancellationSignal();\n        }\n        if (mAuthCallback == null) {\n            mAuthCallback = new FingerprintManager.AuthenticationCallback() {\n                @Override\n                public void onAuthenticationError(int errMsgId, CharSequence errString) {\n                    // 多次指纹密码验证错误后，进入此方法；并且，不能短时间内调用指纹验证,一般间隔从几秒到几十秒不等\n                    // 这种情况不建议重试，建议提示用户用其他的方式解锁或者认证\n                    mState = NONE;\n                    notifyAuthenticationError(errMsgId, errString);\n                }\n\n                @Override\n                public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {\n                    mState = NONE;\n                    // 建议根据参数helpString返回值，并且仅针对特定的机型做处理，并不能保证所有厂商返回的状态一致\n                    notifyAuthenticationFailed(helpMsgId , helpString.toString());\n                    onFailedRetry(helpMsgId, helpString.toString());\n                }\n\n                @Override\n                public void onAuthenticationFailed() {\n                    mState = NONE;\n                    notifyAuthenticationFailed(0 , \"onAuthenticationFailed\");\n                    onFailedRetry(-1, \"onAuthenticationFailed\");\n                }\n\n                @Override\n                public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {\n                    mState = NONE;\n                    notifyAuthenticationSucceeded();\n                }\n            };\n        }\n    }\n\n    public void cancelAuthenticate() {\n        if (mCancellationSignal != null && mState != CANCEL) {\n\n            mState = CANCEL;\n            mCancellationSignal.cancel();\n            mCancellationSignal = null;\n        }\n    }\n\n    private void onFailedRetry(int msgId, String helpString) {\n        mFailedTimes++;\n\n      /*  if (mFailedTimes > 5) { // 每个验证流程最多重试5次，这个根据使用场景而定，验证成功时清0\n\n            return;\n        }*/\n        if (msgId == 1001 && \"等待手指按下\".equals(helpString)) {\n            return;\n        }\n        if (msgId == 1002 && \"手指按下\".equals(helpString)) {\n            return;\n        }\n        if (msgId == 1003 && \"手指抬起\".equals(helpString)) {\n            return;\n        }\n        cancelAuthenticate();\n        mHandler.removeCallbacks(mFailedRetryRunnable);\n        mHandler.postDelayed(mFailedRetryRunnable, 300); // 每次重试间隔一会儿再启动\n        isFirst=false;\n    }\n\n\n    public void startDelay(){\n        if(mHandler!=null){\n\n            mHandler.removeCallbacks(mFailedRetryRunnable);\n            mHandler.postDelayed(mFailedRetryRunnable, 300); // 每次重试间隔一会儿再启动\n        }\n    }\n\n    private Runnable mFailedRetryRunnable = new Runnable() {\n        @Override\n        public void run() {\n            startAuthenticate(mCryptoObjectCreator.getCryptoObject());\n        }\n    };\n\n    public boolean isSupport() {\n        return isSupport;\n    }\n\n    /**\n     * 时候有指纹识别硬件支持\n     * @return\n     */\n    public boolean isHardwareDetected() {\n        try {\n            return mFingerprintManager.isHardwareDetected();\n        } catch (SecurityException e) {\n        } catch (Throwable e) {}\n        return false;\n    }\n\n    /**\n     * 是否录入指纹，有些设备上即使录入了指纹，但是没有开启锁屏密码的话此方法还是返回false\n     * @return\n     */\n    public boolean isHasEnrolledFingerprints() {\n        try {\n            // 有些厂商api23之前的版本可能没有做好兼容，这个方法内部会崩溃（redmi note2, redmi note3等）\n            return mFingerprintManager.hasEnrolledFingerprints();\n        } catch (SecurityException e) {\n        } catch (Throwable e) {\n        }\n        return false;\n    }\n\n    public static FingerprintManager getFingerprintManager(Context context) {\n        FingerprintManager fingerprintManager = null;\n        try {\n            fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);\n        } catch (Throwable e) {\n\n        }\n        return fingerprintManager;\n    }\n\n    public void onDestroy() {\n        cancelAuthenticate();\n        mHandler = null;\n        mAuthCallback = null;\n        mFpResultListener = null;\n        mCancellationSignal = null;\n        mFingerprintManager = null;\n        if (mCryptoObjectCreator != null) {\n            mCryptoObjectCreator.onDestroy();\n            mCryptoObjectCreator = null;\n        }\n    }\n\n\n    public static final FingerprintCore getInstance() {\n        return FingerprintCore.LazyHolder.INSTANCE;\n    }\n\n    public static class LazyHolder {\n        public static FingerprintCore INSTANCE=new FingerprintCore(FingerContext.getContext());\n    }\n\n\n    /*public void reset() {\n        LazyHolder.INSTANCE=new FingerprintCore(FingerContext.getContext());\n    }*/\n}\n"
  },
  {
    "path": "fingerprintlib/src/main/java/com/lib/lock/fingerprint/core/MyListener.java",
    "content": "package com.lib.lock.fingerprint.core;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic abstract class MyListener implements FingerprintCore.IFingerprintResultListener {\n}\n"
  },
  {
    "path": "fingerprintlib/src/main/java/com/lib/lock/fingerprint/dialog/FingerDialog.java",
    "content": "package com.lib.lock.fingerprint.dialog;\n\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.RelativeLayout;\n\nimport com.lib.lock.fingerprint.R;\n\n\n/**\n * 使用方法：\n * final FingerDialog selfDialog = new FingerDialog(MainActivity.this);\n * <p>\n * selfDialog.setCustom(你的布局);\n * <p>\n * FingerDialog.show();\n * <p>\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\n\npublic class FingerDialog extends Dialog {\n\n\n    private View mContentView;\n\n\n    private RelativeLayout contentRoot;\n\n\n    public FingerDialog(Context context) {\n        super(context, R.style.dialog_finger);\n\n    }\n\n\n    @Override\n\n\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.finger_dialog_custom_view_layout);\n        //按空白处不能取消动画\n        setCanceledOnTouchOutside(false);\n\n        //初始化界面控件\n        initView();\n        //初始化界面数据\n        initData();\n        //初始化界面控件的事件\n        initEvent();\n\n       setCancelable(false);\n    }\n\n\n    private void initView() {\n\n        contentRoot = findViewById(R.id.content_view_root);\n\n    }\n\n    /**\n     * 初始化界面控件的显示数据\n     */\n\n\n    private void initData() {\n\n\n        if (mContentView != null) {\n            RelativeLayout.LayoutParams params = new RelativeLayout.\n                    LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,\n                    ViewGroup.LayoutParams.WRAP_CONTENT);\n            contentRoot.addView(mContentView, params);\n\n        }\n\n    }\n\n    /**\n     * 初始化界面控件\n     */\n\n    private void initEvent() {\n\n\n    }\n\n\n    public View getContentView() {\n        return mContentView;\n    }\n\n    public void setCustom(View mContentView) {\n        this.mContentView = mContentView;\n\n\n    }\n}\n"
  },
  {
    "path": "fingerprintlib/src/main/java/com/lib/lock/fingerprint/utils/FingerContext.java",
    "content": "package com.lib.lock.fingerprint.utils;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\n/**\n * 作者：xin on 2018/7/9 0009 15:03\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class FingerContext {\n\n\n\n    @SuppressLint(\"StaticFieldLeak\")\n    private static Context context;\n\n    private FingerContext() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * 初始化工具类\n     *\n     * @param context 上下文\n     */\n    public static void init(@NonNull Context context) {\n        FingerContext.context = context.getApplicationContext();\n    }\n\n    /**\n     * 获取ApplicationContext\n     *\n     * @return ApplicationContext\n     */\n    public static Context getContext() {\n        if (context != null) return context;\n        throw new NullPointerException(\"u should init first\");\n    }\n}\n"
  },
  {
    "path": "fingerprintlib/src/main/java/com/lib/lock/fingerprint/utils/FingerprintUtil.java",
    "content": "package com.lib.lock.fingerprint.utils;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Typeface;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.animation.Animation;\nimport android.view.animation.AnimationUtils;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.lib.lock.fingerprint.BuildConfig;\nimport com.lib.lock.fingerprint.R;\nimport com.lib.lock.fingerprint.core.FingerprintCore;\nimport com.lib.lock.fingerprint.core.MyListener;\nimport com.lib.lock.fingerprint.dialog.FingerDialog;\n\n\n/**\n * <p>\n * create by xin\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class FingerprintUtil {\n\n    private static final String ACTION_SETTING = \"android.settings.SETTINGS\";\n    /**\n     * 重试次数\n     */\n    private static int retryCount;\n\n    /**\n     * 打开设置页面根据需求调用\n     *\n     * @param context\n     */\n    public static void openFingerPrintSettingPage(Context context) {\n        Intent intent = new Intent(ACTION_SETTING);\n        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n        try {\n            context.startActivity(intent);\n        } catch (Exception e) {\n        }\n    }\n\n    /**\n     * 系统支持和系统开启了指纹\n     */\n    public static boolean supportAndSysOpenedFingerPrint() {\n        FingerprintCore mFingerprintCore = FingerprintCore.getInstance();\n\n        return mFingerprintCore.isSupport() && mFingerprintCore.isHasEnrolledFingerprints();\n    }\n\n\n    /**\n     * 开始识别\n     */\n    public static void startFingerprintRecognition(final Activity activity, final MyListener listener) {\n\n        final FingerDialog dialog = new FingerDialog(activity);\n        retryCount = 4;\n        // 硬件支持且有指纹密码\n        if (supportAndSysOpenedFingerPrint()) {\n\n            final View[] rootContent = {LayoutInflater.from(FingerContext.getContext()).inflate(R.layout.finger_dialog_icon_info, null)};\n            dialog.setCustom(rootContent[0]);\n            final TextView textRetry = rootContent[0].findViewById(R.id.text_retry);\n            final TextView textContent = rootContent[0].findViewById(R.id.text_title);\n            final ImageView iconFinger = rootContent[0].findViewById(R.id.icon_finger);\n            final TextView textOk = rootContent[0].findViewById(R.id.btn_cancle);\n\n            textRetry.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));\n            textRetry.setVisibility(View.GONE);\n            rootContent[0].findViewById(R.id.btn_cancle).setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (dialog.isShowing()) {\n                        dialog.dismiss();\n                        FingerprintCore.getInstance().cancelAuthenticate();\n\n                        retryCount = 4;\n                    }\n                }\n            });\n\n            FingerprintCore.getInstance().setFingerprintManager(new FingerprintCore.IFingerprintResultListener() {\n                @Override\n                public void onAuthenticateSuccess() {\n\n                    if (listener != null) {\n                        listener.onAuthenticateSuccess();\n                    }\n                    dialog.dismiss();\n                    textContent.setVisibility(View.VISIBLE);\n                    textContent.setText(R.string.auth_ok);\n                    textRetry.setVisibility(View.GONE);\n                    iconFinger.setVisibility(View.GONE);\n                    textOk.setText(R.string.ok);\n                    FingerprintCore.getInstance().cancelAuthenticate();\n\n                    showLog(\"指纹识别成功\");\n                    dialog.show();\n                }\n\n                @Override\n                public void onAuthenticateFailed(int helpId,String msg) {\n\n\n                    if (helpId == 1001 && \"等待手指按下\".equals(msg)) {\n                        return;\n                    }\n                    if (helpId == 1002 && \"手指按下\".equals(msg)) {\n                        return;\n                    }\n                    if (helpId == 1003 && \"手指抬起\".equals(msg)) {\n                        return;\n                    }\n\n                    textRetry.setVisibility(View.VISIBLE);\n                    iconFinger.clearAnimation();\n                    Animation anim = AnimationUtils.loadAnimation(FingerContext.getContext(), R.anim.anim_shake);\n                    iconFinger.startAnimation(anim);\n                }\n\n                @Override\n                public void onAuthenticateError(int errMsgId) {\n\n                    if (errMsgId == 7) {\n                        Toast.makeText(FingerContext.getContext(), R.string.toast_text_retry_to_much, Toast.LENGTH_LONG).show();\n                        dialog.dismiss();\n                    }\n                }\n\n                @Override\n                public void onStartAuthenticateResult(boolean isSuccess) {\n                    if (listener != null) {\n                        listener.onStartAuthenticateResult(isSuccess);\n                    }\n\n                    if (isSuccess && FingerprintCore.getInstance().isFirst) {\n                        dialog.show();// 成功开启指纹硬件情况下显示\n                    }\n                }\n            });\n            FingerprintCore.getInstance().isFirst = true;\n            FingerprintCore.getInstance().startDelay();\n\n            showLog(FingerContext.getContext().getString(R.string.log_info_start));\n\n        } else if (!FingerprintCore.getInstance().isSupport()) {\n            // 硬件不支持\n            View rootContent = LayoutInflater.from(FingerContext.getContext()).inflate(R.layout.finger_dialog_error, null);\n            TextView errInfo = rootContent.findViewById(R.id.text_info);\n            rootContent.findViewById(R.id.btn_i_see).setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (dialog.isShowing()) {\n                        dialog.dismiss();\n                        FingerprintCore.getInstance().cancelAuthenticate();\n                        retryCount = 4;\n                    }\n                }\n            });\n            errInfo.setText(R.string.finger_info_error_un_support);\n            dialog.setCustom(rootContent);\n            dialog.show();\n        } else if (!FingerprintCore.getInstance().isHasEnrolledFingerprints()) {\n            // 没有指纹\n            View rootContent = LayoutInflater.from(FingerContext.getContext()).inflate(R.layout.finger_dialog_error, null);\n            TextView errInfo = rootContent.findViewById(R.id.text_info);\n            rootContent.findViewById(R.id.btn_i_see).setOnClickListener(new View.OnClickListener() {\n                @Override\n                public void onClick(View v) {\n                    if (dialog.isShowing()) {\n                        dialog.dismiss();\n                        FingerprintCore.getInstance().cancelAuthenticate();\n                    }\n                }\n            });\n            errInfo.setText(R.string.finger_info_error_un_get_fingerprint);\n            dialog.setCustom(rootContent);\n            dialog.show();\n        }\n\n\n    }\n\n    private static void showLog(String msg) {\n\n        if (BuildConfig.DEBUG) {\n            Log.d(FingerContext.getContext().getString(R.string.app_name), msg);\n        }\n    }\n}\n"
  },
  {
    "path": "fingerprintlib/src/main/java/com/lib/lock/fingerprint/utils/KeyguardLockScreenManager.java",
    "content": "package com.lib.lock.fingerprint.utils;\n\nimport android.app.Activity;\nimport android.app.KeyguardManager;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Build;\nimport android.util.Log;\n\n\n/**\n */\n\npublic class KeyguardLockScreenManager {\n    public final static int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 0;\n    private KeyguardManager mKeyManager;\n\n    /**\n     * 是否开启锁屏密码，注意：有Api版本限制\n     * @return\n     */\n    public boolean isOpenLockScreenPwd() {\n        try {\n            //noinspection SimplifiableIfStatement\n            if (Build.VERSION.SDK_INT < 16) {\n                return false;\n            }\n            return mKeyManager != null && mKeyManager.isKeyguardSecure();\n        } catch (Exception e) {\n            return false;\n        }\n    }\n\n    public KeyguardLockScreenManager(Context context) {\n        mKeyManager = getKeyguardManager(context);\n    }\n\n    public static KeyguardManager getKeyguardManager(Context context) {\n        KeyguardManager keyguardManager = null;\n        try {\n            keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);\n        } catch (Throwable throwable) {\n            Log.e(\"getKeyguardManager\",\"getKeyguardManager exception\");\n        }\n        return keyguardManager;\n    }\n\n    /**\n     * 锁屏密码，注意：有Api版本限制\n     */\n    public void showAuthenticationScreen(Activity activity) {\n        if (Build.VERSION.SDK_INT < 21) {\n            return;\n        }\n        Intent intent = mKeyManager.createConfirmDeviceCredentialIntent(\"锁屏密码\", \"测试锁屏密码\");\n        if (intent != null) {\n            activity.startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);\n        }\n    }\n\n    public void onDestroy() {\n        mKeyManager = null;\n    }\n}\n"
  },
  {
    "path": "fingerprintlib/src/main/res/anim/anim_shake.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      >\n    <!-- android:interpolator=\"@anim/cycle\"-->\n    <translate\n        android:duration=\"300\"\n        android:fromXDelta=\"0\"\n        android:fromYDelta=\"0\"\n        android:toXDelta=\"-10\"\n        android:toYDelta=\"0\" />\n    <translate\n        android:duration=\"300\"\n        android:fromXDelta=\"0\"\n        android:fromYDelta=\"0\"\n        android:startOffset=\"300\"\n        android:toXDelta=\"10\"\n        android:toYDelta=\"0\" />\n    <translate\n        android:duration=\"300\"\n        android:fromXDelta=\"0\"\n        android:fromYDelta=\"0\"\n        android:startOffset=\"600\"\n        android:toXDelta=\"-10\"\n        android:toYDelta=\"0\" />\n    <translate\n        android:duration=\"300\"\n        android:fromXDelta=\"0\"\n        android:fromYDelta=\"0\"\n        android:startOffset=\"900\"\n        android:toXDelta=\"10\"\n        android:toYDelta=\"0\" />\n</set>\n"
  },
  {
    "path": "fingerprintlib/src/main/res/anim/cycle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<cycleInterpolator xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                   android:cycles=\"1\" />\n"
  },
  {
    "path": "fingerprintlib/src/main/res/drawable/bg_finger_change_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <corners android:radius=\"10dp\"/>\n    <solid android:color=\"@android:color/white\"/>\n</shape>"
  },
  {
    "path": "fingerprintlib/src/main/res/layout/activity_fingerprint_psw_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:orientation=\"vertical\">\n\n\n    <ImageView\n        android:id=\"@+id/img_icon\"\n        android:layout_width=\"100dp\"\n        android:layout_height=\"100dp\"\n        android:layout_marginTop=\"35dp\"\n        android:layout_centerHorizontal=\"true\"\n        android:src=\"@drawable/icon_fingerprint\"/>\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/img_icon\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginTop=\"10dp\"\n        android:text=\"使用指纹登录\"\n        android:textColor=\"#ff222222\"\n        android:textSize=\"16sp\"/>\n\n\n</RelativeLayout>"
  },
  {
    "path": "fingerprintlib/src/main/res/layout/finger_dialog_custom_view_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:paddingLeft=\"42dp\"\n                android:paddingRight=\"42dp\">\n\n\n    <RelativeLayout\n        android:layout_centerInParent=\"true\"\n        android:id=\"@+id/content_view_root\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n\n</RelativeLayout>"
  },
  {
    "path": "fingerprintlib/src/main/res/layout/finger_dialog_error.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              xmlns:tools=\"http://schemas.android.com/tools\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"wrap_content\"\n              android:background=\"@drawable/bg_finger_change_dialog\"\n              android:layout_gravity=\"center\"\n              android:orientation=\"vertical\">\n\n\n    <TextView\n        android:id=\"@+id/text_info\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center\"\n        android:layout_marginRight=\"10dp\"\n        android:layout_marginLeft=\"10dp\"\n        android:paddingBottom=\"10dp\"\n        android:paddingTop=\"30dp\"\n        android:textColor=\"#333333\"\n        android:textSize=\"12sp\"\n        tools:text=\"weonfksnfksnfksnkfkkkkkkkkkkkkkkkssssssssssssssssskkkkkkkkkkk\"/>\n\n    <View\n        android:id=\"@+id/line\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1px\"\n        android:layout_below=\"@+id/text_root\"\n        android:layout_marginTop=\"20dp\"\n        android:background=\"@android:color/darker_gray\"/>\n    <TextView\n        android:id=\"@+id/btn_i_see\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"45dp\"\n        android:layout_below=\"@+id/line\"\n        android:gravity=\"center\"\n        android:text=\"知道了\"\n        android:textColor=\"@android:color/holo_blue_dark\"\n        android:textSize=\"16sp\"/>\n\n\n</LinearLayout>"
  },
  {
    "path": "fingerprintlib/src/main/res/layout/finger_dialog_icon_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"@drawable/bg_finger_change_dialog\"\n                android:paddingTop=\"30dp\">\n\n    <ImageView\n        android:id=\"@+id/icon_finger\"\n        android:layout_width=\"60dp\"\n        android:scaleType=\"fitXY\"\n        android:layout_height=\"60dp\"\n        android:layout_centerHorizontal=\"true\"\n        android:src=\"@drawable/icon_finger_\"/>\n\n    <LinearLayout\n        android:id=\"@+id/text_root\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/icon_finger\"\n        android:layout_marginTop=\"18dp\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/text_retry\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:paddingBottom=\"15dp\"\n            android:textColor=\"#333333\"\n            android:textSize=\"16sp\"\n            android:textStyle=\"bold\"\n            android:text=\"@string/text_retry\"/>\n\n        <TextView\n            android:id=\"@+id/text_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:layout_marginRight=\"14dp\"\n            android:layout_marginLeft=\"14dp\"\n            android:textColor=\"#333333\"\n            android:textSize=\"14sp\"\n            android:text=\"@string/finger_info_text\"/>\n\n    </LinearLayout>\n\n    <View\n        android:id=\"@+id/line\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1px\"\n        android:layout_below=\"@+id/text_root\"\n        android:layout_marginTop=\"20dp\"\n        android:background=\"@android:color/darker_gray\"/>\n\n\n    <TextView\n        android:id=\"@+id/btn_cancle\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"45dp\"\n        android:layout_below=\"@+id/line\"\n        android:gravity=\"center\"\n        android:text=\"@string/btn_cancel\"\n        android:textColor=\"@android:color/holo_blue_dark\"\n        android:textSize=\"16sp\"/>\n\n\n</RelativeLayout>"
  },
  {
    "path": "fingerprintlib/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">fingerprintlib</string>\n\n    <string name=\"ok\">确定</string>\n    <string name=\"auth_ok\">验证通过</string>\n    <string name=\"btn_cancel\">取消</string>\n    <string name=\"text_retry\">再试一次</string>\n\n    <string name=\"log_info_failed\">指纹识别失败，请重试！</string>\n    <string name=\"log_info_error\">指纹识别错误，等待几秒之后再重试</string>\n    <string name=\"log_info_start\">指纹识别已经开启，长按指纹解锁键</string>\n    <string name=\"toast_text_retry_to_much\">尝试次数过多，请稍后尝试</string>\n\n    <string name=\"finger_info_text\">通过识别设备验证已有手机指纹</string>\n    <string name=\"finger_info_error_retry_count_format\">纹不匹配，还可尝试%d次</string>\n    <string name=\"finger_info_error_un_support\">此设备不支持指纹识别功能，请更换设备！</string>\n    <string name=\"finger_info_error_un_get_fingerprint\">此设备未录入指纹，请先录入！</string>\n</resources>\n"
  },
  {
    "path": "fingerprintlib/src/main/res/values/style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--对话框-->\n    <style name=\"dialog_finger\" parent=\"@android:style/Theme.Dialog\">\n        <item name=\"android:windowFrame\">@null</item>\n        <item name=\"android:windowIsFloating\">true</item>\n        <item name=\"android:windowIsTranslucent\">false</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:backgroundDimEnabled\">true</item>\n        <item name=\"android:windowAnimationStyle\">@android:style/Animation.Dialog</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "gesturepswlib/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "gesturepswlib/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\n\n\nandroid {\n    compileSdkVersion rootProject.ext.android[\"compileSdkVersion\"]\n\n\n    defaultConfig {\n        minSdkVersion rootProject.ext.android[\"minSdkVersion\"]\n        targetSdkVersion rootProject.ext.android[\"targetSdkVersion\"]\n        versionCode 1\n        versionName rootProject.ext.android[\"versionName\"]\n\n\n    }\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n/* android support */\n    compile rootProject.ext.dependencies[\"appcompat-v7\"]\n\n}"
  },
  {
    "path": "gesturepswlib/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /Users/hsg/Library/Android/sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "gesturepswlib/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.lib.lock.gesture\"\n          xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:label=\"@string/app_name\"\n        android:supportsRtl=\"true\">\n        <activity\n            android:name=\".activities.GestureSettingsActivity\"\n            android:screenOrientation=\"portrait\"/>\n        <activity\n            android:name=\".activities.GestureVerifyActivity\"\n            android:screenOrientation=\"portrait\"/>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/activities/GestureSettingsActivity.java",
    "content": "package com.lib.lock.gesture.activities;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.support.annotation.Nullable;\nimport android.support.v7.app.AppCompatActivity;\nimport android.view.View;\nimport android.widget.TextView;\n\nimport com.lib.lock.gesture.customView.OnPatternChangeListener;\nimport com.lib.lock.gesture.customView.PatternLockerView;\nimport com.lib.lock.gesture.R;\nimport com.lib.lock.gesture.utils.PatternHelper;\n\nimport java.util.List;\n\n\n/**\n * 作者：xin on 2018/6/13 17:00\n * 手势密码设置\n * <p>\n * 邮箱：ittfxin@126.com\n * <P>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class GestureSettingsActivity extends AppCompatActivity {\n\n\n    private TextView textMsg;\n    private PatternHelper patternHelper;\n\n    public static void launch(Activity activity) {\n        Intent intent = new Intent(activity, GestureSettingsActivity.class);\n        activity.startActivity(intent);\n    }\n\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_gesture_settings);\n        PatternLockerView patternLockerView = findViewById(R.id.pattern_lock_view);\n        findViewById(R.id.iv_close).setOnClickListener(new View.OnClickListener() {\n            @Override\n            public void onClick(View v) {\n                finish();\n            }\n        });\n        this.textMsg = findViewById(R.id.text_msg);\n        patternLockerView.setOnPatternChangedListener(new OnPatternChangeListener() {\n            @Override\n            public void onStart(PatternLockerView view) {\n            }\n\n            @Override\n            public void onChange(PatternLockerView view, List<Integer> hitList) {\n            }\n\n            @Override\n            public void onComplete(PatternLockerView view, List<Integer> hitList) {\n                boolean isOk = isPatternOk(hitList);\n                view.updateStatus(!isOk);\n                updateMsg();\n            }\n\n            @Override\n            public void onClear(PatternLockerView view) {\n                finishIfNeeded();\n            }\n        });\n\n        this.textMsg.setText(\"设置解锁图案\");\n        this.patternHelper = new PatternHelper();\n\n    }\n\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n\n    }\n\n    private boolean isPatternOk(List<Integer> hitList) {\n        this.patternHelper.validateForSetting(hitList);\n        return this.patternHelper.isOk();\n    }\n\n    private void updateMsg() {\n        this.textMsg.setText(this.patternHelper.getMessage());\n\n    }\n\n    private void finishIfNeeded() {\n        if (this.patternHelper.isFinish()) {\n            new Handler().postDelayed(new Runnable() {\n                @Override\n                public void run() {\n                    finish();\n                }\n            }, 500);\n\n        }\n    }\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/activities/GestureVerifyActivity.java",
    "content": "package com.lib.lock.gesture.activities;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.lib.lock.gesture.R;\nimport com.lib.lock.gesture.customView.OnPatternChangeListener;\nimport com.lib.lock.gesture.customView.PatternLockerView;\nimport com.lib.lock.gesture.utils.PatternHelper;\n\nimport java.util.List;\n\n/**\n * 作者：xin on 2018/6/20 0020 11:01\n * <p>\n * <p>\n * 邮箱：ittfxin@126.com\n * <P>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\npublic class GestureVerifyActivity extends AppCompatActivity {\n\n    boolean isSettings;\n\n    private PatternLockerView patternLockerView;\n    private TextView textMsg;\n    private PatternHelper patternHelper;\n    private boolean isError;\n\n\n    public static void launch(Activity activity, boolean isSettings) {\n        Intent intent = new Intent(activity, GestureVerifyActivity.class);\n        intent.putExtra(\"isSettings\", isSettings);\n        activity.startActivity(intent);\n    }\n\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.activity_gesture_verify);\n        isSettings = getIntent().getBooleanExtra(\"isSettings\", false);\n\n\n        this.patternLockerView = findViewById(R.id.pattern_lock_view);\n        this.textMsg = findViewById(R.id.text_msg);\n\n        this.patternLockerView.setOnPatternChangedListener(new OnPatternChangeListener() {\n            @Override\n            public void onStart(PatternLockerView view) {\n            }\n\n            @Override\n            public void onChange(PatternLockerView view, List<Integer> hitList) {\n            }\n\n            @Override\n            public void onComplete(PatternLockerView view, List<Integer> hitList) {\n                isError = !isPatternOk(hitList);\n                view.updateStatus(isError);\n                //    patternIndicatorView.updateState(hitList, isError);\n                updateMsg();\n            }\n\n            @Override\n            public void onClear(PatternLockerView view) {\n                finishIfNeeded();\n            }\n        });\n\n        this.textMsg.setText(\"绘制解锁图案\");\n        this.patternHelper = new PatternHelper();\n    }\n\n    private boolean isPatternOk(List<Integer> hitList) {\n        this.patternHelper.validateForChecking(hitList);\n        return this.patternHelper.isOk();\n    }\n\n    private void updateMsg() {\n        this.textMsg.setText(this.patternHelper.getMessage());\n\n    }\n\n    private void finishIfNeeded() {\n        if (this.patternHelper.isFinish()) {\n\n            if (isSettings && !isError) {\n                GestureSettingsActivity.launch(this);\n            } else if (!isError) {\n                Toast.makeText(this, \"验证成功！\", Toast.LENGTH_LONG).show();\n            } else {\n                Toast.makeText(this, \"验证失败！\", Toast.LENGTH_LONG).show();\n            }\n            finish();\n\n\n        }\n    }\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/bean/CellBean.java",
    "content": "package com.lib.lock.gesture.bean;\n\n\n\npublic class CellBean {\n\n    public int id;\n    public float x;\n    public float y;\n    public float radius;\n\n    public boolean isHit;\n\n    public CellBean(int id, float x, float y, float radius) {\n        this.id = id;\n        this.x = x;\n        this.y = y;\n        this.radius = radius;\n    }\n\n    /**\n     * 是否触碰到该view\n     *\n     * @param x\n     * @param y\n     * @return\n     */\n    public boolean of(float x, float y) {\n        final float dx = this.x - x;\n        final float dy = this.y - y;\n        return Math.sqrt(dx * dx + dy * dy) <= this.radius;\n    }\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/bean/CellFactory.java",
    "content": "package com.lib.lock.gesture.bean;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n\n\npublic class CellFactory {\n    private int width;\n    private int height;\n    private List<CellBean> cellBeanList;\n\n    public CellFactory(int width, int height) {\n        this.width = width;\n        this.height = height;\n        this.cellBeanList = new ArrayList<>();\n        this.create();\n    }\n\n    private void create() {\n        final float pWidth = this.width / 8f;\n        final float pHeight = this.height / 8f;\n\n        for (int i = 0; i < 3; i++) {\n            for (int j = 0; j < 3; j++) {\n                this.cellBeanList.add(new CellBean(\n                        (i * 3 + j),\n                        (j * 3 + 1) * pWidth,\n                        (i * 3 + 1) * pHeight,\n                        pWidth));\n            }\n        }\n    }\n\n    public List<CellBean> getCellBeanList() {\n        return this.cellBeanList;\n    }\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/config/Config.java",
    "content": "package com.lib.lock.gesture.config;\n\nimport android.content.res.Resources;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.support.annotation.ColorInt;\nimport android.util.TypedValue;\n\n\n\npublic class Config {\n    private static final String DEFAULT_NORMAL_COLOR = \"#2196F3\";\n    private static final String DEFAULT_HIT_COLOR = \"#3F51B5\";\n    private static final String DEFAULT_ERROR_COLOR = \"#F44336\";\n    private static final String DEFAULT_FILL_COLOR = \"#FFFFFF\";\n    private static final int DEFAULT_LINE_WIDTH = 1;\n    private static final int DEFAULT_DELAY_TIME = 1000;//ms\n    private static final boolean DEFAULT_ENABLE_AUTO_CLEAN = true;\n\n    public static @ColorInt\n    int getDefaultNormalColor() {\n        return Color.parseColor(DEFAULT_NORMAL_COLOR);\n    }\n\n    public static @ColorInt\n    int getDefaultHitColor() {\n        return Color.parseColor(DEFAULT_HIT_COLOR);\n    }\n\n    public static @ColorInt\n    int getDefaultErrorColor() {\n        return Color.parseColor(DEFAULT_ERROR_COLOR);\n    }\n\n    public static @ColorInt\n    int getDefaultFillColor() {\n        return Color.parseColor(DEFAULT_FILL_COLOR);\n    }\n\n    public static float getDefaultLineWidth(Resources resources) {\n        return convertDpToPx(DEFAULT_LINE_WIDTH, resources);\n    }\n\n    public static int getDefaultDelayTime() {\n        return DEFAULT_DELAY_TIME;\n    }\n\n    public static boolean getDefaultEnableAutoClean() {\n        return DEFAULT_ENABLE_AUTO_CLEAN;\n    }\n\n    public static Paint createPaint() {\n        final Paint paint = new Paint();\n        paint.setDither(true);\n        paint.setAntiAlias(true);\n        paint.setStrokeJoin(Paint.Join.ROUND);\n        paint.setStrokeCap(Paint.Cap.ROUND);\n        return paint;\n    }\n\n    private static float convertDpToPx(float dp, Resources resources) {\n        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());\n    }\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/content/DataKeeper.java",
    "content": "/*\n * Copyright (C) 2013 litesuits.com\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 */\npackage com.lib.lock.gesture.content;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\n\n/**\n * <p>\n * 邮箱：ittfxin@126.com\n * <P>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\n\npublic class DataKeeper {\n    private final SharedPreferences.Editor editor;\n    private SharedPreferences sp;\n    private static final String TAG = DataKeeper.class.getSimpleName();\n\n    @SuppressLint(\"CommitPrefEdits\")\n    DataKeeper(Context context, String fileName) {\n        sp = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);\n        editor = sp.edit();\n    }\n\n    /**\n     * *************** get ******************\n     */\n\n    public String get(String key, String defValue) {\n        return sp.getString(key, defValue);\n    }\n\n    public boolean get(String key, boolean defValue) {\n        return sp.getBoolean(key, defValue);\n    }\n\n    public float get(String key, float defValue) {\n        return sp.getFloat(key, defValue);\n    }\n\n    public int getInt(String key, int defValue) {\n        return sp.getInt(key, defValue);\n    }\n\n    public long get(String key, long defValue) {\n        return sp.getLong(key, defValue);\n    }\n\n\n    public SharedPreferences.Editor put(String key, String value) {\n       /* if (value == null) {\n            editor.remove(key);\n        } else {*/\n        editor.putString(key, value);\n       /* }*/\n        return editor;\n    }\n\n    public SharedPreferences.Editor put(String key, boolean value) {\n        editor.putBoolean(key, value);\n        return editor;\n    }\n\n    public SharedPreferences.Editor put(String key, float value) {\n        editor.putFloat(key, value);\n        return editor;\n    }\n\n    public SharedPreferences.Editor put(String key, long value) {\n        editor.putLong(key, value);\n        return editor;\n    }\n\n    public SharedPreferences.Editor putInt(String key, int value) {\n        editor.putInt(key, value);\n        return editor;\n    }\n\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/content/SPManager.java",
    "content": "package com.lib.lock.gesture.content;\n\n\nimport com.lib.lock.gesture.utils.ContextUtils;\n\n/**\n * Description :SharedPreferences\n * Created by xin on 2017/5/16 0016.\n */\n\npublic class SPManager implements SharedPreferencesKeys {\n\n\n    private DataKeeper mDk;\n    private static SPManager spManager;\n\n    private SPManager() {\n        mDk = new DataKeeper(ContextUtils.getContext(), spFileName);\n    }\n\n    public static SPManager getInstance() {\n        if (spManager == null) {\n            synchronized (SPManager.class) {\n                if (spManager == null) {\n                    spManager = new SPManager();\n                }\n            }\n\n        }\n\n        return spManager;\n    }\n\n\n\n\n\n\n    /**\n     * 存手势密码\n     *\n     * @param encryptPwd\n     * @return\n     */\n    public void   putPatternPSW(String encryptPwd) {\n\n         mDk.put(KEY_GESTURE_PWD, encryptPwd).commit();\n\n    }\n\n\n    /**\n     * 手势密码\n     */\n    public String getPatternPSW() {\n        return mDk.get(KEY_GESTURE_PWD, \"\");\n    }\n\n\n    /**\n     * 指纹密码\n     *\n     * @return\n     */\n    public void setHasFingerPrint(boolean isSet) {\n          mDk.put(KEY_HAS_FINGERPRINT, isSet).apply();\n    }\n\n    /**\n     * 指纹密码\n     *\n     * @return\n     */\n    public boolean getHasFingerPrint() {\n        return mDk.get(KEY_HAS_FINGERPRINT, false);\n    }\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/content/SharedPreferencesKeys.java",
    "content": "package com.lib.lock.gesture.content;\n\n/**\n * <p>\n * 邮箱：ittfxin@126.com\n * <p>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\n\npublic interface SharedPreferencesKeys {\n\n    String TAG = SharedPreferencesKeys.class.getSimpleName();\n\n    // 升级后channel id不变\n    String KEY_RECORDED_CHANNEL_ID = TAG + \".key.recorded.channel.id\";\n\n    String KEY_PHONE_DEVICE_ID = TAG + \"key.phone.device.id\";\n\n    String KEY_USER_ID = TAG + \"key.user.id\";\n\n    String KEY_USER_TOKEN = TAG + \"key.user.token\";\n\n\n    String KEY_GESTURE_PWD = \"gesture_pwd_key\";\n\n    String KEY_HAS_FINGERPRINT = \"KEY_HAS_FINGERPRINT\";\n    /**\n     * 根据使用情况配置文件名称\n     */\n    String spFileName = \"common_sharedPreferences\";\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/content/readme.txt",
    "content": "SharedPreferences"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/DefaultIndicatorHitCellView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.support.annotation.ColorInt;\nimport android.support.annotation.NonNull;\n\nimport com.lib.lock.gesture.bean.CellBean;\nimport com.lib.lock.gesture.config.Config;\n\n/**\n */\n\npublic class DefaultIndicatorHitCellView implements  IHitCellView {\n\n    private @ColorInt\n    int normalColor;\n    private @ColorInt\n    int errorColor;\n\n    private Paint paint;\n\n    public DefaultIndicatorHitCellView() {\n        this.paint = Config.createPaint();\n        this.paint.setStyle(Paint.Style.FILL);\n    }\n\n    public int getNormalColor() {\n        return normalColor;\n    }\n\n    public DefaultIndicatorHitCellView setNormalColor(int normalColor) {\n        this.normalColor = normalColor;\n        return this;\n    }\n\n    public int getErrorColor() {\n        return errorColor;\n    }\n\n    public DefaultIndicatorHitCellView setErrorColor(int errorColor) {\n        this.errorColor = errorColor;\n        return this;\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas, @NonNull CellBean cellBean, boolean isError) {\n        int saveCount = canvas.save();\n\n        this.paint.setColor(this.getColor(isError));\n        canvas.drawCircle(cellBean.x, cellBean.y, cellBean.radius, this.paint);\n\n        canvas.restoreToCount(saveCount);\n    }\n\n    private int getColor(boolean isError) {\n        return isError ? this.getErrorColor() : this.getNormalColor();\n    }\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/DefaultIndicatorLinkedLineView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.support.annotation.ColorInt;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.lib.lock.gesture.bean.CellBean;\nimport com.lib.lock.gesture.config.Config;\n\nimport java.util.List;\n\n/**\n */\n\npublic class DefaultIndicatorLinkedLineView implements IIndicatorLinkedLineView {\n    private @ColorInt\n    int normalColor;\n    private @ColorInt\n    int errorColor;\n    private float lineWidth;\n\n    private Paint paint;\n\n    public DefaultIndicatorLinkedLineView() {\n        this.paint = Config.createPaint();\n        this.paint.setStyle(Paint.Style.STROKE);\n    }\n\n    public int getNormalColor() {\n        return normalColor;\n    }\n\n    public DefaultIndicatorLinkedLineView setNormalColor(int normalColor) {\n        this.normalColor = normalColor;\n        return this;\n    }\n\n    public int getErrorColor() {\n        return errorColor;\n    }\n\n    public DefaultIndicatorLinkedLineView setErrorColor(int errorColor) {\n        this.errorColor = errorColor;\n        return this;\n    }\n\n    public float getLineWidth() {\n        return lineWidth;\n    }\n\n    public DefaultIndicatorLinkedLineView setLineWidth(float lineWidth) {\n        this.lineWidth = lineWidth;\n        return this;\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas, @Nullable List<Integer> hitList, @NonNull List<CellBean> cellBeanList, boolean isError) {\n        if (hitList == null || hitList.isEmpty() || cellBeanList.isEmpty()) {\n            return;\n        }\n\n        final int saveCount = canvas.save();\n\n        final CellBean first = cellBeanList.get(hitList.get(0));\n        final Path path = new Path();\n        path.moveTo(first.x, first.y);\n\n        for (int i = 1; i < hitList.size(); i++) {\n            CellBean c = cellBeanList.get(hitList.get(i));\n            path.lineTo(c.x, c.y);\n        }\n\n        this.paint.setColor(this.getColor(isError));\n        this.paint.setStrokeWidth(this.getLineWidth());\n        canvas.drawPath(path, this.paint);\n\n        canvas.restoreToCount(saveCount);\n    }\n\n    private int getColor(boolean isError) {\n        return isError ? this.getErrorColor() : this.getNormalColor();\n    }\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/DefaultIndicatorNormalCellView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.support.annotation.NonNull;\n\nimport com.lib.lock.gesture.bean.CellBean;\nimport com.lib.lock.gesture.config.Config;\n\n/**\n */\n\npublic class DefaultIndicatorNormalCellView implements INormalCellView {\n\n    private int normalColor;\n    private int fillColor;\n    private float lineWidth;\n\n    private Paint paint;\n\n    public DefaultIndicatorNormalCellView() {\n        this.paint = Config.createPaint();\n        this.paint.setStyle(Paint.Style.FILL);\n    }\n\n    public int getNormalColor() {\n        return normalColor;\n    }\n\n    public DefaultIndicatorNormalCellView setNormalColor(int normalColor) {\n        this.normalColor = normalColor;\n        return this;\n    }\n\n    public int getFillColor() {\n        return fillColor;\n    }\n\n    public DefaultIndicatorNormalCellView setFillColor(int fillColor) {\n        this.fillColor = fillColor;\n        return this;\n    }\n\n    public float getLineWidth() {\n        return lineWidth;\n    }\n\n    public DefaultIndicatorNormalCellView setLineWidth(float lineWidth) {\n        this.lineWidth = lineWidth;\n        return this;\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas, @NonNull CellBean cellBean) {\n        int saveCount = canvas.save();\n\n        //outer circle\n        this.paint.setColor(this.getNormalColor());\n        canvas.drawCircle(cellBean.x, cellBean.y, cellBean.radius, this.paint);\n\n        //inner circle\n        this.paint.setColor(this.getFillColor());\n        canvas.drawCircle(cellBean.x, cellBean.y, cellBean.radius - this.getLineWidth(), this.paint);\n\n        canvas.restoreToCount(saveCount);\n    }\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/DefaultLockerHitCellView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.Paint;\nimport android.graphics.RadialGradient;\nimport android.graphics.Shader;\nimport android.support.annotation.ColorInt;\nimport android.support.annotation.NonNull;\n\nimport com.lib.lock.gesture.bean.CellBean;\nimport com.lib.lock.gesture.config.Config;\n\n/**\n */\n\npublic class DefaultLockerHitCellView implements IHitCellView {\n\n    private RadialGradient mRadialGradient;\n    private @ColorInt\n    int hitColor;\n    private @ColorInt\n    int errorColor;\n    private @ColorInt\n    int fillColor;\n    private @ColorInt\n    int normalColor;\n\n    private float lineWidth;\n\n    private Paint paint;\n\n\n    public DefaultLockerHitCellView() {\n        this.paint = Config.createPaint();\n        this.paint.setStyle(Paint.Style.FILL);\n\n\n    }\n\n    public @ColorInt\n    int getHitColor() {\n        return hitColor;\n    }\n\n    public DefaultLockerHitCellView setHitColor(@ColorInt int hitColor) {\n        this.hitColor = hitColor;\n        return this;\n    }\n\n    public @ColorInt\n    int getErrorColor() {\n        return errorColor;\n    }\n\n    public DefaultLockerHitCellView setErrorColor(@ColorInt int errorColor) {\n        this.errorColor = errorColor;\n        return this;\n    }\n\n    public int getNormalColor() {\n        return normalColor;\n    }\n\n    public DefaultLockerHitCellView setNormalColor(int normalColor) {\n        this.normalColor = normalColor;\n        return this;\n    }\n\n    public @ColorInt\n    int getFillColor() {\n        return fillColor;\n    }\n\n    public DefaultLockerHitCellView setFillColor(@ColorInt int fillColor) {\n        this.fillColor = fillColor;\n        return this;\n    }\n\n    public float getLineWidth() {\n        return lineWidth;\n    }\n\n    public DefaultLockerHitCellView setLineWidth(float lineWidth) {\n        this.lineWidth = lineWidth;\n        return this;\n    }\n\n\n    @Override\n    public void draw(@NonNull Canvas canvas, @NonNull CellBean cellBean, boolean isError) {\n        final int saveCount = canvas.save();\n\n        // draw outer circle\n        this.paint.setColor(this.getOuterColor(isError));\n        canvas.drawCircle(cellBean.x, cellBean.y, cellBean.radius, this.paint);\n\n        // draw fill circle\n        Paint mPaint = new Paint();\n        mRadialGradient = new RadialGradient(cellBean.x, cellBean.y, cellBean.radius - this.getLineWidth() * 4,\n                getGradientColor(isError),Color.WHITE, Shader.TileMode.REPEAT );\n        mPaint.setShader(mRadialGradient);\n        canvas.drawCircle(cellBean.x, cellBean.y, cellBean.radius - this.getLineWidth() * 4, mPaint);\n\n        // draw inner circle\n        this.paint.setColor(this.getColor(isError));\n        canvas.drawCircle(cellBean.x, cellBean.y, cellBean.radius / 4f, this.paint);\n\n        canvas.restoreToCount(saveCount);\n    }\n\n    private @ColorInt\n    int getColor(boolean isError) {\n        return isError ? this.getErrorColor() : this.getHitColor();\n    }\n\n    private @ColorInt\n    int getOuterColor(boolean isError) {\n        return isError ? this.getErrorColor() : this.getNormalColor();\n    }\n\n\n    private @ColorInt\n    int getGradientColor(boolean isError) {\n        return isError ? this.getErrorColor() : Color.parseColor(\"#9AD7F7\");\n    }\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/DefaultLockerLinkedLineView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.support.annotation.ColorInt;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.lib.lock.gesture.bean.CellBean;\nimport com.lib.lock.gesture.config.Config;\n\nimport java.util.List;\n\n/**\n */\n\npublic class DefaultLockerLinkedLineView implements ILockerLinkedLineView {\n    private static final String TAG = \"LockerLinkedLineView\";\n\n    private @ColorInt\n    int normalColor;\n    private @ColorInt\n    int errorColor;\n    private @ColorInt\n    int hitColor;\n\n    private float lineWidth;\n\n\n    private Paint paint;\n\n\n    public DefaultLockerLinkedLineView() {\n        this.paint = Config.createPaint();\n        this.paint.setStyle(Paint.Style.STROKE);\n    }\n\n    public int getNormalColor() {\n        return normalColor;\n    }\n\n    public DefaultLockerLinkedLineView setNormalColor(int normalColor) {\n        this.normalColor = normalColor;\n        return this;\n    }\n\n    public int getErrorColor() {\n        return errorColor;\n    }\n\n    public ILockerLinkedLineView setHitColor(int hitColor) {\n        this.hitColor = hitColor;\n        return this;\n    }\n\n\n    public int getHitColor() {\n        return hitColor;\n    }\n\n    public DefaultLockerLinkedLineView setErrorColor(int errorColor) {\n        this.errorColor = errorColor;\n        return this;\n    }\n\n    public float getLineWidth() {\n        return lineWidth;\n    }\n\n    public DefaultLockerLinkedLineView setLineWidth(float lineWidth) {\n        this.lineWidth = lineWidth;\n        return this;\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas, @Nullable List<Integer> hitList, @NonNull List<CellBean> cellBeanList,\n                     float endX, float endY, boolean isError) {\n        if (hitList == null || hitList.isEmpty() || cellBeanList.isEmpty()) {\n            return;\n        }\n\n        final int saveCount = canvas.save();\n\n        final Path path = new Path();\n\n        CellBean first = cellBeanList.get(hitList.get(0));\n        path.moveTo(first.x, first.y);\n        for (int i = 1; i < hitList.size(); i++) {\n            CellBean c = cellBeanList.get(hitList.get(i));\n            path.lineTo(c.x, c.y);\n        }\n\n        if (((endX != 0) || (endY != 0)) && (hitList.size() < 9)) {\n            path.lineTo(endX, endY);\n        }\n\n        this.paint.setColor(this.getColor(isError));\n        this.paint.setStrokeWidth(this.getLineWidth());\n        canvas.drawPath(path, this.paint);\n\n        canvas.restoreToCount(saveCount);\n    }\n\n    private @ColorInt\n    int getColor(boolean isError) {\n        return isError ? this.getErrorColor() : this.getHitColor();\n    }\n\n\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/DefaultLockerNormalCellView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.support.annotation.ColorInt;\nimport android.support.annotation.NonNull;\n\nimport com.lib.lock.gesture.bean.CellBean;\nimport com.lib.lock.gesture.config.Config;\n\n/**\n */\n\npublic class DefaultLockerNormalCellView implements INormalCellView {\n\n    private @ColorInt\n    int normalColor;\n    private @ColorInt\n    int fillColor;\n\n    private float lineWidth;\n\n    private Paint paint;\n\n    public DefaultLockerNormalCellView() {\n        this.paint = Config.createPaint();\n        this.paint.setStyle(Paint.Style.FILL);\n    }\n\n    public int getNormalColor() {\n        return normalColor;\n    }\n\n    public DefaultLockerNormalCellView setNormalColor(int normalColor) {\n        this.normalColor = normalColor;\n        return this;\n    }\n\n    public int getFillColor() {\n        return fillColor;\n    }\n\n    public DefaultLockerNormalCellView setFillColor(int fillColor) {\n        this.fillColor = fillColor;\n        return this;\n    }\n\n    public float getLineWidth() {\n        return lineWidth;\n    }\n\n    public DefaultLockerNormalCellView setLineWidth(float lineWidth) {\n        this.lineWidth = lineWidth;\n        return this;\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas, @NonNull CellBean cellBean) {\n        final int saveCount = canvas.save();\n\n        // draw outer circle\n        this.paint.setColor(this.getNormalColor());\n        canvas.drawCircle(cellBean.x, cellBean.y, cellBean.radius, this.paint);\n\n        // draw fill circle\n        this.paint.setColor(this.getFillColor());\n        canvas.drawCircle(cellBean.x, cellBean.y, cellBean.radius - this.getLineWidth(), this.paint);\n\n        canvas.restoreToCount(saveCount);\n    }\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/DefaultLockerNormalCustomCellView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.support.annotation.ColorInt;\nimport android.support.annotation.NonNull;\n\nimport com.lib.lock.gesture.bean.CellBean;\nimport com.lib.lock.gesture.config.Config;\n\n/**\n */\n\npublic class DefaultLockerNormalCustomCellView implements INormalCellView {\n\n    private @ColorInt\n    int normalColor;\n    private @ColorInt\n    int fillColor;\n    private @ColorInt\n    int hitColor;\n\n    private float lineWidth;\n\n    private Paint paint;\n\n\n    public DefaultLockerNormalCustomCellView() {\n        this.paint = Config.createPaint();\n        this.paint.setStyle(Paint.Style.FILL);\n    }\n\n    public int getNormalColor() {\n        return normalColor;\n    }\n\n    public DefaultLockerNormalCustomCellView setNormalColor(int normalColor) {\n        this.normalColor = normalColor;\n        return this;\n    }\n\n    public int getFillColor() {\n        return fillColor;\n    }\n\n    public DefaultLockerNormalCustomCellView setFillColor(int fillColor) {\n        this.fillColor = fillColor;\n        return this;\n    }\n\n    public float getLineWidth() {\n        return lineWidth;\n    }\n\n    public DefaultLockerNormalCustomCellView setLineWidth(float lineWidth) {\n        this.lineWidth = lineWidth;\n        return this;\n    }\n\n\n    public DefaultLockerNormalCustomCellView setHitColor(@ColorInt int hitColor) {\n        this.hitColor = hitColor;\n        return this;\n    }\n\n    public @ColorInt\n    int getHitColor() {\n        return hitColor;\n    }\n\n    @Override\n    public void draw(@NonNull Canvas canvas, @NonNull CellBean cellBean) {\n        final int saveCount = canvas.save();\n\n        // draw inner circle\n        this.paint.setColor(getNormalColor());\n        canvas.drawCircle(cellBean.x, cellBean.y, cellBean.radius / 4f, this.paint);\n\n        canvas.restoreToCount(saveCount);\n    }\n\n\n\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/IHitCellView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.support.annotation.NonNull;\n\nimport com.lib.lock.gesture.bean.CellBean;\n\n/**\n */\n\npublic interface IHitCellView {\n    /**\n     * 绘制已设置的每个图案的样式\n     *\n     * @param canvas\n     * @param cellBean\n     * @param isError\n     */\n    void draw(@NonNull Canvas canvas, @NonNull CellBean cellBean, boolean isError);\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/IIndicatorLinkedLineView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.lib.lock.gesture.bean.CellBean;\n\nimport java.util.List;\n\n/**\n */\n\npublic interface IIndicatorLinkedLineView {\n    /**\n     * 绘制指示器连接线\n     *\n     * @param canvas\n     * @param hitList\n     * @param cellBeanList\n     * @param isError\n     */\n    void draw(@NonNull Canvas canvas,\n              @Nullable List<Integer> hitList,\n              @NonNull List<CellBean> cellBeanList,\n              boolean isError);\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/ILockerLinkedLineView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\n\nimport com.lib.lock.gesture.bean.CellBean;\n\nimport java.util.List;\n\n/**\n */\n\npublic interface ILockerLinkedLineView {\n    /**\n     * 绘制图案密码连接线\n     *\n     * @param canvas\n     * @param hitList\n     * @param cellBeanList\n     * @param endX\n     * @param endY\n     * @param isError\n     */\n    void draw(@NonNull Canvas canvas,\n              @Nullable List<Integer> hitList,\n              @NonNull List<CellBean> cellBeanList,\n              float endX,\n              float endY,\n              boolean isError);\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/INormalCellView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.graphics.Canvas;\nimport android.support.annotation.NonNull;\n\nimport com.lib.lock.gesture.bean.CellBean;\n\n/**\n */\n\npublic interface INormalCellView {\n    /**\n     * 绘制正常情况下（即未设置的）每个图案的样式\n     *\n     * @param canvas\n     * @param cellBean the target cell view\n     */\n    void draw(@NonNull Canvas canvas, @NonNull CellBean cellBean);\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/OnPatternChangeListener.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport java.util.List;\n\n/**\n */\n\npublic interface OnPatternChangeListener {\n    /**\n     * 开始绘制图案时（即手指按下触碰到绘画区域时）会调用该方法\n     *\n     * @param view\n     */\n    void onStart(PatternLockerView view);\n\n    /**\n     * 图案绘制改变时（即手指在绘画区域移动时）会调用该方法，请注意只有 @param hitList改变了才会触发此方法\n     *\n     * @param view\n     * @param hitList\n     */\n    void onChange(PatternLockerView view, List<Integer> hitList);\n\n    /**\n     * 图案绘制完成时（即手指抬起离开绘画区域时）会调用该方法\n     *\n     * @param view\n     * @param hitList\n     */\n    void onComplete(PatternLockerView view, List<Integer> hitList);\n\n    /**\n     * 已绘制的图案被清除时会调用该方法\n     *\n     * @param view\n     */\n    void onClear(PatternLockerView view);\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/PatternIndicatorView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.support.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.View;\n\nimport com.lib.lock.gesture.R;\nimport com.lib.lock.gesture.bean.CellBean;\nimport com.lib.lock.gesture.bean.CellFactory;\nimport com.lib.lock.gesture.config.Config;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n */\n\npublic class PatternIndicatorView extends View {\n    private static final String TAG = \"PatternIndicatorView\";\n\n    private int normalColor;\n    private int fillColor;\n    private int hitColor;\n    private int errorColor;\n    private float lineWidth;\n\n    private boolean isError;\n    private List<Integer> hitList;\n    private List<CellBean> cellBeanList;\n\n    private IIndicatorLinkedLineView linkedLineView;\n    private INormalCellView normalCellView;\n    private IHitCellView hitCellView;\n\n\n    public PatternIndicatorView(Context context) {\n        this(context, null);\n    }\n\n    public PatternIndicatorView(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public PatternIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init(context, attrs, defStyleAttr);\n    }\n\n    public int getNormalColor() {\n        return normalColor;\n    }\n\n    public PatternIndicatorView setNormalColor(int normalColor) {\n        this.normalColor = normalColor;\n        return this;\n    }\n\n    public int getFillColor() {\n        return fillColor;\n    }\n\n    public PatternIndicatorView setFillColor(int fillColor) {\n        this.fillColor = fillColor;\n        return this;\n    }\n\n    public int getHitColor() {\n        return hitColor;\n    }\n\n    public PatternIndicatorView setHitColor(int hitColor) {\n        this.hitColor = hitColor;\n        return this;\n    }\n\n    public int getErrorColor() {\n        return errorColor;\n    }\n\n    public PatternIndicatorView setErrorColor(int errorColor) {\n        this.errorColor = errorColor;\n        return this;\n    }\n\n    public float getLineWidth() {\n        return lineWidth;\n    }\n\n    public PatternIndicatorView setLineWidth(float lineWidth) {\n        this.lineWidth = lineWidth;\n        return this;\n    }\n\n    public IIndicatorLinkedLineView getLinkedLineView() {\n        return linkedLineView;\n    }\n\n    public PatternIndicatorView setLinkedLineView(IIndicatorLinkedLineView linkedLineView) {\n        this.linkedLineView = linkedLineView;\n        return this;\n    }\n\n    public INormalCellView getNormalCellView() {\n        return normalCellView;\n    }\n\n    public PatternIndicatorView setNormalCellView(INormalCellView normalCellView) {\n        this.normalCellView = normalCellView;\n        return this;\n    }\n\n    public IHitCellView getHitCellView() {\n        return hitCellView;\n    }\n\n    public PatternIndicatorView setHitCellView(IHitCellView hitCellView) {\n        this.hitCellView = hitCellView;\n        return this;\n    }\n\n    public void buildWithDefaultStyle() {\n        this.setNormalCellView(new DefaultIndicatorNormalCellView()\n                .setNormalColor(this.getNormalColor())\n                .setFillColor(this.getFillColor())\n                .setLineWidth(this.getLineWidth())\n        ).setHitCellView(new DefaultIndicatorHitCellView()\n                .setErrorColor(this.getErrorColor())\n                .setNormalColor(this.getHitColor())\n        ).setLinkedLineView(new DefaultIndicatorLinkedLineView()\n                .setNormalColor(this.getHitColor())\n                .setErrorColor(this.getErrorColor())\n                .setLineWidth(this.getLineWidth())\n        ).build();\n    }\n\n    public void build() {\n        if (getNormalCellView() == null) {\n            Log.e(TAG, \"build(), normalCellView is null\");\n            return;\n        }\n\n        if (getHitCellView() == null) {\n            Log.e(TAG, \"build(), hitCellView is null\");\n            return;\n        }\n\n        if (getLinkedLineView() == null) {\n            Log.w(TAG, \"build(), linkedLineView is null\");\n        }\n        postInvalidate();\n    }\n\n    public void updateState(List<Integer> hitList, boolean isError) {\n        //1. reset to default state\n        if (!this.hitList.isEmpty()) {\n            for (int i : this.hitList) {\n                this.cellBeanList.get(i).isHit = false;\n            }\n            this.hitList.clear();\n        }\n\n        //2. update hit state\n        if (hitList != null) {\n            this.hitList.addAll(hitList);\n        }\n        if (!this.hitList.isEmpty()) {\n            for (int i : this.hitList) {\n                this.cellBeanList.get(i).isHit = true;\n            }\n        }\n\n        //3. update result\n        this.isError = isError;\n\n        //4. update view\n        postInvalidate();\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int a = Math.min(widthMeasureSpec, heightMeasureSpec);\n        super.onMeasure(a, a);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        if (this.cellBeanList == null) {\n            this.cellBeanList = new CellFactory(getWidth() - getPaddingLeft() - getPaddingRight(),\n                    getHeight() - getPaddingTop() - getPaddingBottom())\n                    .getCellBeanList();\n        }\n\n        drawLinkedLine(canvas);\n        drawCells(canvas);\n    }\n\n    private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        this.initAttrs(context, attrs, defStyleAttr);\n        this.initData();\n    }\n\n    private void initAttrs(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PatternIndicatorView, defStyleAttr, 0);\n\n        this.normalColor = ta.getColor(R.styleable.PatternIndicatorView_piv_color, Config.getDefaultNormalColor());\n        this.fillColor = ta.getColor(R.styleable.PatternIndicatorView_piv_fillColor, Config.getDefaultFillColor());\n        this.hitColor = ta.getColor(R.styleable.PatternIndicatorView_piv_hitColor, Config.getDefaultHitColor());\n        this.errorColor = ta.getColor(R.styleable.PatternIndicatorView_piv_errorColor, Config.getDefaultErrorColor());\n        this.lineWidth = ta.getDimension(R.styleable.PatternIndicatorView_piv_lineWidth, Config.getDefaultLineWidth(getResources()));\n\n        ta.recycle();\n\n        this.setNormalColor(this.normalColor);\n        this.setFillColor(this.fillColor);\n        this.setHitColor(this.hitColor);\n        this.setErrorColor(this.errorColor);\n        this.setLineWidth(this.lineWidth);\n    }\n\n    private void initData() {\n        this.hitList = new ArrayList<>();\n        this.buildWithDefaultStyle();\n    }\n\n    private void drawLinkedLine(Canvas canvas) {\n        if (!this.hitList.isEmpty() && (this.getLinkedLineView() != null)) {\n            this.getLinkedLineView().draw(canvas,\n                    this.hitList,\n                    this.cellBeanList,\n                    this.isError);\n        }\n    }\n\n    private void drawCells(Canvas canvas) {\n        if (this.getHitCellView() == null) {\n            Log.e(TAG, \"drawCells(), hitCellView is null\");\n            return;\n        }\n\n        if (this.getNormalCellView() == null) {\n            Log.e(TAG, \"drawCells(), normalCellView is null\");\n            return;\n        }\n\n        for (int i = 0; i < this.cellBeanList.size(); i++) {\n            CellBean item = this.cellBeanList.get(i);\n            if (item.isHit) {\n                this.getHitCellView().draw(canvas, item, this.isError);\n            } else {\n                this.getNormalCellView().draw(canvas, item);\n            }\n        }\n    }\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/customView/PatternLockerView.java",
    "content": "package com.lib.lock.gesture.customView;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android.graphics.Canvas;\nimport android.support.annotation.ColorInt;\nimport android.support.annotation.Nullable;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.View;\n\nimport com.lib.lock.gesture.R;\nimport com.lib.lock.gesture.bean.CellBean;\nimport com.lib.lock.gesture.bean.CellFactory;\nimport com.lib.lock.gesture.config.Config;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n */\n\npublic class PatternLockerView extends View {\n    private static final String TAG = \"PatternLockerView\";\n\n    private @ColorInt\n    int normalColor;\n    private @ColorInt\n    int hitColor;\n    private @ColorInt\n    int errorColor;\n    private @ColorInt\n    int fillColor;\n    private float lineWidth;\n    private boolean enableAutoClean;\n\n    private float endX;\n    private float endY;\n\n    private int hitSize;\n    private boolean isError;\n    private List<CellBean> cellBeanList;\n    private List<Integer> hitList;\n    private OnPatternChangeListener listener;\n\n    private ILockerLinkedLineView linkedLineView;\n    private INormalCellView normalCellView;\n    private IHitCellView hitCellView;\n\n    public PatternLockerView(Context context) {\n        this(context, null);\n    }\n\n    public PatternLockerView(Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, 0);\n    }\n\n    public PatternLockerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        this.init(context, attrs, defStyleAttr);\n    }\n\n    public void setOnPatternChangedListener(OnPatternChangeListener listener) {\n        this.listener = listener;\n    }\n\n    public void updateStatus(boolean isError) {\n        this.isError = isError;\n        postInvalidate();\n    }\n\n    public void clearHitState() {\n        clearHitData();\n        this.isError = false;\n        if (this.listener != null) {\n            this.listener.onClear(this);\n        }\n\n        postInvalidate();\n    }\n\n    public int getNormalColor() {\n        return normalColor;\n    }\n\n    public PatternLockerView setNormalColor(int normalColor) {\n        this.normalColor = normalColor;\n        return this;\n    }\n\n    public int getHitColor() {\n        return hitColor;\n    }\n\n    public PatternLockerView setHitColor(int hitColor) {\n        this.hitColor = hitColor;\n        return this;\n    }\n\n    public int getErrorColor() {\n        return errorColor;\n    }\n\n    public PatternLockerView setErrorColor(int errorColor) {\n        this.errorColor = errorColor;\n        return this;\n    }\n\n    public int getFillColor() {\n        return fillColor;\n    }\n\n    public PatternLockerView setFillColor(int fillColor) {\n        this.fillColor = fillColor;\n        return this;\n    }\n\n    public float getLineWidth() {\n        return lineWidth;\n    }\n\n    public PatternLockerView setLineWidth(float lineWidth) {\n        this.lineWidth = lineWidth;\n        return this;\n    }\n\n    public ILockerLinkedLineView getLinkedLineView() {\n        return linkedLineView;\n    }\n\n    public PatternLockerView setLinkedLineView(ILockerLinkedLineView linkedLineView) {\n        this.linkedLineView = linkedLineView;\n        return this;\n    }\n\n    public INormalCellView getNormalCellView() {\n        return normalCellView;\n    }\n\n    public PatternLockerView setNormalCellView(INormalCellView normalCellView) {\n        this.normalCellView = normalCellView;\n        return this;\n    }\n\n    public IHitCellView getHitCellView() {\n        return hitCellView;\n    }\n\n    public PatternLockerView setHitCellView(IHitCellView hitCellView) {\n        this.hitCellView = hitCellView;\n        return this;\n    }\n\n    public void buildWithDefaultStyle() {\n        this.setNormalCellView(new DefaultLockerNormalCustomCellView()\n                .setNormalColor(this.getNormalColor())\n                .setFillColor(this.getFillColor())\n                .setLineWidth(this.getLineWidth())\n        ).setHitCellView(new DefaultLockerHitCellView()\n                .setHitColor(this.getHitColor())\n                .setErrorColor(this.getErrorColor())\n                .setFillColor(this.getFillColor())\n                .setLineWidth(this.getLineWidth())\n                .setNormalColor(this.getNormalColor())\n        ).setLinkedLineView(new DefaultLockerLinkedLineView()\n                .setNormalColor(this.getHitColor())\n                .setErrorColor(this.getErrorColor())\n                .setLineWidth(this.getLineWidth())\n                .setHitColor(this.getHitColor())\n        ).build();\n    }\n\n    public void build() {\n        if (getNormalCellView() == null) {\n            Log.e(TAG, \"build(), normalCellView is null\");\n            return;\n        }\n\n        if (getHitCellView() == null) {\n            Log.e(TAG, \"build(), hitCellView is null\");\n            return;\n        }\n\n        if (getLinkedLineView() == null) {\n            Log.w(TAG, \"build(), linkedLineView is null\");\n        }\n\n        postInvalidate();\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        int a = Math.min(widthMeasureSpec, heightMeasureSpec);\n        super.onMeasure(a, a);\n    }\n\n    @Override\n    protected void onDraw(Canvas canvas) {\n        super.onDraw(canvas);\n        if (this.cellBeanList == null) {\n            this.cellBeanList = new CellFactory(getWidth(), getHeight())\n                    .getCellBeanList();\n        }\n        drawLinkedLine(canvas);\n        drawCells(canvas);\n    }\n\n    @Override\n    public boolean onTouchEvent(MotionEvent event) {\n        if (!isEnabled()) {\n            return super.onTouchEvent(event);\n        }\n\n        boolean isHandle = false;\n        switch (event.getAction()) {\n            case MotionEvent.ACTION_DOWN:\n                handleActionDown(event);\n                isHandle = true;\n                break;\n            case MotionEvent.ACTION_MOVE:\n                handleActionMove(event);\n                isHandle = true;\n                break;\n            case MotionEvent.ACTION_UP:\n                handleActionUp(event);\n                isHandle = true;\n                break;\n            default:\n                break;\n        }\n        postInvalidate();\n        return isHandle ? true : super.onTouchEvent(event);\n    }\n\n    private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        this.initAttrs(context, attrs, defStyleAttr);\n        this.initData();\n    }\n\n    private void initAttrs(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PatternLockerView, defStyleAttr, 0);\n\n        this.normalColor = ta.getColor(R.styleable.PatternLockerView_plv_color, Config.getDefaultNormalColor());\n        this.hitColor = ta.getColor(R.styleable.PatternLockerView_plv_hitColor, Config.getDefaultHitColor());\n        this.errorColor = ta.getColor(R.styleable.PatternLockerView_plv_errorColor, Config.getDefaultErrorColor());\n        this.fillColor = ta.getColor(R.styleable.PatternLockerView_plv_fillColor, Config.getDefaultFillColor());\n        this.lineWidth = ta.getDimension(R.styleable.PatternLockerView_plv_lineWidth, Config.getDefaultLineWidth(getResources()));\n        this.enableAutoClean = ta.getBoolean(R.styleable.PatternLockerView_plv_enableAutoClean, Config.getDefaultEnableAutoClean());\n\n        ta.recycle();\n\n        this.setNormalColor(this.normalColor);\n        this.setHitColor(this.hitColor);\n        this.setErrorColor(this.errorColor);\n        this.setFillColor(this.fillColor);\n        this.setLineWidth(this.lineWidth);\n    }\n\n    private void initData() {\n        this.hitList = new ArrayList<>();\n        this.buildWithDefaultStyle();\n    }\n\n    private void drawLinkedLine(Canvas canvas) {\n        if ((this.hitList != null) && !this.hitList.isEmpty() && (getLinkedLineView() != null)) {\n            getLinkedLineView().draw(canvas,\n                    this.hitList,\n                    this.cellBeanList,\n                    this.endX,\n                    this.endY,\n                    this.isError);\n        }\n    }\n\n    private void drawCells(Canvas canvas) {\n        if (getHitCellView() == null) {\n            Log.e(TAG, \"drawCells(), hitCellView is null\");\n            return;\n        }\n\n        if (getNormalCellView() == null) {\n            Log.e(TAG, \"drawCells(), normalCellView is null\");\n            return;\n        }\n\n        for (int i = 0; i < this.cellBeanList.size(); i++) {\n            final CellBean item = this.cellBeanList.get(i);\n            if (item.isHit) {\n                getHitCellView().draw(canvas, item, this.isError);\n            } else {\n                getNormalCellView().draw(canvas, item);\n            }\n        }\n    }\n\n    private void handleActionDown(MotionEvent event) {\n        //1. reset to default state\n        clearHitData();\n\n        //2. update hit state\n        updateHitState(event);\n\n        //3. notify listener\n        if (this.listener != null) {\n            this.listener.onStart(this);\n        }\n    }\n\n    private void handleActionMove(MotionEvent event) {\n        //1. update hit state\n        updateHitState(event);\n\n        //2. update end point\n        this.endX = event.getX();\n        this.endY = event.getY();\n\n        //3. notify listener if needed\n        final int size = this.hitList.size();\n        if ((this.listener != null) && (this.hitSize != size)) {\n            this.hitSize = size;\n            this.listener.onChange(this, this.hitList);\n        }\n    }\n\n    private void handleActionUp(MotionEvent event) {\n        //1. update hit state\n        updateHitState(event);\n        this.endX = 0;\n        this.endY = 0;\n\n        //2. notify listener\n        if (this.listener != null) {\n            this.listener.onComplete(this, this.hitList);\n        }\n\n        //3. startTimer if needed\n        if (this.enableAutoClean && this.hitList.size() > 0) {\n            startTimer();\n        }\n    }\n\n    private void updateHitState(MotionEvent event) {\n        final float x = event.getX();\n        final float y = event.getY();\n        for (CellBean c : this.cellBeanList) {\n            if (!c.isHit && c.of(x, y)) {\n                c.isHit = true;\n                this.hitList.add(c.id);\n            }\n        }\n    }\n\n    private void clearHitData() {\n        for (int i = 0; i < this.hitList.size(); i++) {\n            this.cellBeanList.get(hitList.get(i)).isHit = false;\n        }\n        this.hitList.clear();\n        this.hitSize = 0;\n    }\n\n    private final Runnable action = new Runnable() {\n        @Override\n        public void run() {\n            setEnabled(true);\n            clearHitState();\n        }\n    };\n\n    @Override\n    protected void onDetachedFromWindow() {\n        this.setOnPatternChangedListener(null);\n        this.removeCallbacks(this.action);\n        super.onDetachedFromWindow();\n    }\n\n    private void startTimer() {\n        setEnabled(false);\n        this.postDelayed(this.action, Config.getDefaultDelayTime());\n    }\n}"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/utils/ContextUtils.java",
    "content": "package com.lib.lock.gesture.utils;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.support.annotation.NonNull;\n\n/**\n * 作者：xin on 2018/6/7 0007 14:17\n * <p>\n * 邮箱：ittfxin@126.com\n *\n * <P>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\n\n\npublic class ContextUtils {\n\n\n\n    @SuppressLint(\"StaticFieldLeak\")\n    private static Context context;\n\n    private ContextUtils() {\n        throw new UnsupportedOperationException(\"u can't instantiate me...\");\n    }\n\n    /**\n     * 初始化工具类\n     *\n     * @param context 上下文\n     */\n    public static void init(@NonNull Context context) {\n        ContextUtils.context = context.getApplicationContext();\n    }\n\n    /**\n     * 获取ApplicationContext\n     *\n     * @return ApplicationContext\n     */\n    public static Context getContext() {\n        if (context != null) return context;\n        throw new NullPointerException(\"u should init first\");\n    }\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/utils/PatternHelper.java",
    "content": "package com.lib.lock.gesture.utils;\n\nimport android.text.TextUtils;\n\nimport com.lib.lock.gesture.content.SPManager;\n\nimport java.util.List;\n\n\n\n\n\npublic class PatternHelper {\n    public static final int MAX_SIZE = 4;\n    public static final int MAX_TIMES = 5;\n   ;\n\n    private String message;\n    private String storagePwd;\n    private String tmpPwd;\n    private int times;\n    private boolean isFinish;\n    private boolean isOk;\n\n    public void validateForSetting(List<Integer> hitList) {\n        this.isFinish = false;\n        this.isOk = false;\n\n        if ((hitList == null) || (hitList.size() < MAX_SIZE)) {\n            this.tmpPwd = null;\n            this.message = getSizeErrorMsg();\n            return;\n        }\n\n        //1. draw first time\n        if (TextUtils.isEmpty(this.tmpPwd)) {\n            this.tmpPwd = convert2String(hitList);\n            this.message = getReDrawMsg();\n            this.isOk = true;\n            return;\n        }\n\n        //2. draw second times\n        if (this.tmpPwd.equals(convert2String(hitList))) {\n            this.message = getSettingSuccessMsg();\n            saveToStorage(this.tmpPwd);\n            this.isOk = true;\n            this.isFinish = true;\n        } else {\n            this.tmpPwd = null;\n            this.message = getDiffPreErrorMsg();\n        }\n    }\n\n    public void validateForChecking(List<Integer> hitList) {\n        this.isOk = false;\n\n        if ((hitList == null) || (hitList.size() < MAX_SIZE)) {\n            this.times++;\n            this.isFinish = this.times >= MAX_SIZE;\n            this.message = getPwdErrorMsg();\n            return;\n        }\n\n        this.storagePwd = getFromStorage();\n        if (!TextUtils.isEmpty(this.storagePwd) && this.storagePwd.equals(convert2String(hitList))) {\n            this.message = getCheckingSuccessMsg();\n            this.isOk = true;\n            this.isFinish = true;\n        } else {\n            this.times++;\n            this.isFinish = this.times >= MAX_SIZE;\n            this.message = getPwdErrorMsg();\n        }\n    }\n\n    public String getMessage() {\n        return this.message;\n    }\n\n    public boolean isFinish() {\n        return isFinish;\n    }\n\n    public boolean isOk() {\n        return isOk;\n    }\n\n    private String getReDrawMsg() {\n        return \"请再次绘制解锁图案\";\n    }\n\n    private String getSettingSuccessMsg() {\n        return \"手势解锁图案设置成功！\";\n    }\n\n    private String getCheckingSuccessMsg() {\n        return \"解锁成功！\";\n    }\n\n    private String getSizeErrorMsg() {\n        return String.format(\"至少连接个%d点，请重新绘制\", MAX_SIZE);\n    }\n\n    private String getDiffPreErrorMsg() {\n        return \"与上次绘制不一致，请重新绘制\";\n    }\n\n    private String getPwdErrorMsg() {\n        return String.format(\"密码错误，还剩%d次机会\", getRemainTimes());\n    }\n\n    private String convert2String(List<Integer> hitList) {\n        return hitList.toString();\n    }\n\n    private void saveToStorage(String gesturePwd) {\n        final String encryptPwd = SecurityUtil.encrypt(gesturePwd);\n        SPManager.getInstance().putPatternPSW( encryptPwd);\n    }\n\n    private String getFromStorage() {\n        final String result = SPManager.getInstance().getPatternPSW();\n        return SecurityUtil.decrypt(result);\n    }\n\n    private int getRemainTimes() {\n        return (times < 5) ? (MAX_TIMES - times) : 0;\n    }\n\n\n\n\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/java/com/lib/lock/gesture/utils/SecurityUtil.java",
    "content": "package com.lib.lock.gesture.utils;\n\nimport java.io.UnsupportedEncodingException;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.spec.SecretKeySpec;\n\n/**\n * <p>\n * 邮箱：ittfxin@126.com\n * <P>\n * https://github.com/wzx54321/XinFrameworkLib\n */\n\n\npublic class SecurityUtil {\n    private static final String CIPHER_MODE = \"AES/ECB/PKCS5Padding\";\n    private static final String MASTER_PASSWORD = \"Test123454321\";\n\n    private static SecretKeySpec createKey(String password) {\n        byte[] data = null;\n\n        if (password == null) {\n            password = \"\";\n        }\n\n        StringBuffer sb = new StringBuffer(32);\n\n        sb.append(password);\n\n        while (sb.length() < 32) {\n            sb.append(\"0\");\n        }\n\n        if (sb.length() > 32) {\n            sb.setLength(32);\n        }\n\n        try {\n            data = sb.toString().getBytes(\"UTF-8\");\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n        }\n\n        return new SecretKeySpec(data, \"AES\");\n    }\n\n    public static String encrypt(final String content) {\n        return encrypt(content, MASTER_PASSWORD);\n    }\n\n    public static byte[] encrypt(byte[] content, String password) {\n        try {\n            SecretKeySpec key = createKey(password);\n            Cipher cipher = Cipher.getInstance(CIPHER_MODE);\n            cipher.init(Cipher.ENCRYPT_MODE, key);\n            byte[] result = cipher.doFinal(content);\n            return result;\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    public static String encrypt(String content, String password) {\n        byte[] data = null;\n\n        try {\n            data = content.getBytes(\"UTF-8\");\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        data = encrypt(data, password);\n\n        return byte2hex(data != null ? data : new byte[0]);\n    }\n\n    public static String decrypt(final String content) {\n        return decrypt(content, MASTER_PASSWORD);\n    }\n\n    public static byte[] decrypt(byte[] content, String password) {\n        try {\n            SecretKeySpec key = createKey(password);\n            Cipher cipher = Cipher.getInstance(CIPHER_MODE);\n            cipher.init(Cipher.DECRYPT_MODE, key);\n            byte[] result = cipher.doFinal(content);\n            return result;\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    public static String decrypt(String content, String password) {\n        byte[] data = null;\n\n        try {\n            data = hex2byte(content);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n\n        data = decrypt(data, password);\n\n        if (data == null) {\n            return null;\n        }\n\n        String result = null;\n\n        try {\n            result = new String(data, \"UTF-8\");\n        } catch (UnsupportedEncodingException e) {\n            e.printStackTrace();\n        }\n\n        return result;\n    }\n\n    public static String byte2hex(byte[] b) { // 一个字节的数，\n        StringBuffer sb = new StringBuffer(b.length * 2);\n        String tmp;\n\n        for (int n = 0; n < b.length; n++) {\n            // 整数转成十六进制表示\n            tmp = (Integer.toHexString(b[n] & 0XFF));\n            if (tmp.length() == 1) {\n                sb.append(\"0\");\n            }\n            sb.append(tmp);\n        }\n        return sb.toString().toUpperCase(); // 转成大写\n    }\n\n    private static byte[] hex2byte(String inputString) {\n        if (inputString == null || inputString.length() < 2) {\n            return new byte[0];\n        }\n\n        inputString = inputString.toLowerCase();\n\n        int l = inputString.length() / 2;\n        byte[] result = new byte[l];\n\n        for (int i = 0; i < l; ++i) {\n            String tmp = inputString.substring(2 * i, 2 * i + 2);\n            result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF);\n        }\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "gesturepswlib/src/main/res/layout/activity_gesture_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n                xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n                xmlns:tools=\"http://schemas.android.com/tools\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"@android:color/white\"\n                android:orientation=\"vertical\">\n\n    <ImageView\n        android:id=\"@+id/iv_close\"\n        android:layout_width=\"20dp\"\n        android:layout_height=\"20dp\"\n        android:layout_marginLeft=\"20dp\"\n        android:layout_marginTop=\"30dp\"\n        android:src=\"@drawable/icon_close_x\"/>\n\n    <ImageView\n        android:id=\"@+id/iv_avatar\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\"\n        android:layout_below=\"@+id/iv_close\"\n        android:layout_centerHorizontal=\"true\"\n        android:layout_marginTop=\"36dp\"\n        android:src=\"@drawable/icon_avatar_\"/>\n\n    <TextView\n        android:id=\"@+id/text_phone_number\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/iv_avatar\"\n        android:layout_marginTop=\"10dp\"\n        android:gravity=\"center\"\n        android:text=\"185******09\"\n        android:textColor=\"#636B73\"\n        android:textSize=\"14sp\"/>\n\n    <TextView\n        android:id=\"@+id/text_msg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_below=\"@+id/text_phone_number\"\n        android:layout_marginTop=\"33dp\"\n        android:gravity=\"center\"\n        android:textColor=\"#636B73\"\n        android:textSize=\"14sp\"\n        tools:text=\"msg\"/>\n\n\n    <com.lib.lock.gesture.customView.PatternLockerView\n        android:id=\"@+id/pattern_lock_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_below=\"@+id/text_msg\"\n        android:layout_marginLeft=\"70dp\"\n        android:layout_marginRight=\"70dp\"\n        android:layout_marginTop=\"22dp\"\n        android:paddingBottom=\"50dp\"\n        app:plv_color=\"#9AD7F7\"\n        app:plv_hitColor=\"#08A3EE\"/>\n</RelativeLayout>\n"
  },
  {
    "path": "gesturepswlib/src/main/res/layout/activity_gesture_verify.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n              android:layout_width=\"match_parent\"\n              android:layout_height=\"match_parent\"\n              xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n              xmlns:tools=\"http://schemas.android.com/tools\"\n              android:background=\"@android:color/white\"\n              android:orientation=\"vertical\">\n\n\n\n    <TextView\n        android:id=\"@+id/text_msg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"20dp\"\n        android:gravity=\"center\"\n        android:padding=\"20dp\"\n        tools:text=\"msg\"\n        android:textColor=\"#636B73\"\n        android:textSize=\"14sp\"/>\n\n\n\n    <com.lib.lock.gesture.customView.PatternLockerView\n        android:id=\"@+id/pattern_lock_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginLeft=\"70dp\"\n        android:layout_marginRight=\"70dp\"\n        android:layout_marginTop=\"22dp\"\n        android:paddingBottom=\"50dp\"\n        app:plv_color=\"#9AD7F7\"\n        app:plv_hitColor=\"#08A3EE\" />\n</LinearLayout>"
  },
  {
    "path": "gesturepswlib/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"PatternIndicatorView\">\n        <attr name=\"piv_color\" format=\"color|reference\" />\n        <attr name=\"piv_hitColor\" format=\"color|reference\" />\n        <attr name=\"piv_errorColor\" format=\"color|reference\" />\n        <attr name=\"piv_fillColor\" format=\"color|reference\" />\n        <attr name=\"piv_lineWidth\" format=\"dimension|reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"PatternLockerView\">\n        <attr name=\"plv_color\" format=\"color|reference\" />\n        <attr name=\"plv_hitColor\" format=\"color|reference\" />\n        <attr name=\"plv_errorColor\" format=\"color|reference\" />\n        <attr name=\"plv_fillColor\" format=\"color|reference\" />\n        <attr name=\"plv_lineWidth\" format=\"dimension|reference\" />\n        <attr name=\"plv_enableAutoClean\" format=\"boolean|reference\" />\n\n\n    </declare-styleable>\n</resources>"
  },
  {
    "path": "gesturepswlib/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Library</string>\n</resources>\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Jul 16 11:27:18 CST 2018\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.4-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windowz variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\ngoto execute\n\n:4NT_args\n@rem Get arguments from the 4NT Shell from JP Software\nset CMD_LINE_ARGS=%$\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':gesturepswlib', ':aliocrlib',\n        ':fingerprintlib'"
  }
]