[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <a href=\"https://join.slack.com/t/faceonlive/shared_invite/zt-2drx19c5t-vQsR4TUGPD8oL7i7BXdKZA\">Slack</a>\n    ·\n   <a href=\"https://www.faceonlive.com/\">Website</a>\n    ·\n   <a href=\"https://portfolio.faceonlive.com\">Portfolio</a>  \n    ·\n    <a href=\"https://www.huggingface.co/FaceOnLive\">Hugging Face</a>\n    ·\n    <a href=\"https://getapi.faceonlive.com\">Free APIs</a>\n    ·\n    <a href=\"https://github.com/FaceOnLive/OpenKYC\">OpenKYC</a>  \n    ·\n    <a href=\"https://github.com/FaceOnLive/Mask-Face-Attendance-App-Flutter\">Face Attendance</a>  \n    ·\n    <a href=\"mailto:contact@faceonlive.com\">Contact</a>\n</div>\n<h1 align=\"center\">Robust, Realtime, On-Device License Plate Recognition SDK For Android</h1>\n\nIt can not only recognize number plate, but also detect vehicle model, color and country.\n\n## :tada:  Try It Yourself\n<a href=\"https://drive.google.com/file/d/1RRLOMN0v9xm_uGPk5pDSryQgMtcrxTog/view?usp=sharing\" target=\"_blank\">\n  <img alt=\"Get it on Google Play\" src=\"https://goo.gl/cR2qQH\" height=\"100\"/>\n</a>\n</br>\n</br>\n\nhttps://user-images.githubusercontent.com/91896009/186433213-6bb1bda3-6b1b-4f71-b950-85e7d233ddff.mp4\n\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    repositories {\n        google()\n        jcenter()\n        \n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.5.1'\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}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "demo/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "demo/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 28\n\n\n    defaultConfig {\n        applicationId \"org.buyun.alpr\"\n        minSdkVersion 21\n        targetSdkVersion 28\n        versionCode 1\n        versionName \"1.0\"\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled true\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    sourceSets {\n        main {\n            java.srcDirs += ['../common/src/main/java']\n            res.srcDirs += ['../common/src/main/res']\n        }\n    }\n\n}\n\ndependencies {\n    implementation fileTree(dir: 'libs', include: ['*.jar'])\n\n    implementation 'androidx.appcompat:appcompat:1.1.0'\n    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'\n\n    implementation project(\":sdk\")\n}\n"
  },
  {
    "path": "demo/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-keep class org.buyun.alpr.sdk.** {*;}\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n"
  },
  {
    "path": "demo/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"org.buyun.alpr\">\n\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n\n    <uses-feature android:name=\"android.hardware.camera\" />\n    <uses-feature android:name=\"android.hardware.camera.autofocus\" />\n\n    <application android:allowBackup=\"true\"\n        android:label=\"@string/app_name\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:theme=\"@style/MaterialTheme\">\n        <activity android:name=\".AlprVideoSequentialActivity\"\n            android:label=\"@string/app_name\"\n            android:screenOrientation=\"landscape\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "demo/src/main/java/org/buyun/alpr/AlprVideoSequentialActivity.java",
    "content": "package org.buyun.alpr;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.util.Size;\n\nimport org.buyun.alpr.common.AlprActivity;\nimport org.buyun.alpr.common.AlprCameraFragment;\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Main activity\n */\npublic class AlprVideoSequentialActivity extends AlprActivity {\n\n    static final String TAG = AlprVideoSequentialActivity.class.getCanonicalName();\n    static final Size PREFERRED_SIZE = new Size(1280, 720);\n    static final String CONFIG_DEBUG_LEVEL = \"info\";\n    static final boolean CONFIG_DEBUG_WRITE_INPUT_IMAGE = false; // must be false unless you're debugging the code\n    static final int CONFIG_NUM_THREADS = -1;\n    static final boolean CONFIG_GPGPU_ENABLED = true;\n    static final int CONFIG_MAX_LATENCY = -1;\n    static final String CONFIG_CHARSET = \"latin\";\n    static final boolean CONFIG_IENV_ENABLED = false;\n    static final boolean CONFIG_OPENVINO_ENABLED = true;\n    static final String CONFIG_OPENVINO_DEVICE = \"CPU\";\n    static final double CONFIG_DETECT_MINSCORE = 0.1; // 10%\n    static final boolean CONFIG_CAR_NOPLATE_DETECT_ENABLED = false;\n    static final double CONFIG_CAR_NOPLATE_DETECT_MINSCORE = 0.8; // 80%\n    static final List<Float> CONFIG_DETECT_ROI = Arrays.asList(0.f, 0.f, 0.f, 0.f);\n    static final boolean CONFIG_PYRAMIDAL_SEARCH_ENABLED = true;\n    static final double CONFIG_PYRAMIDAL_SEARCH_SENSITIVITY= 0.28; // 28%\n    static final double CONFIG_PYRAMIDAL_SEARCH_MINSCORE = 0.5; // 50%\n    static final int CONFIG_PYRAMIDAL_SEARCH_MIN_IMAGE_SIZE_INPIXELS = 800; // pixels\n    static final boolean CONFIG_KLASS_LPCI_ENABLED = true;\n    static final boolean CONFIG_KLASS_VCR_ENABLED = true;\n    static final boolean CONFIG_KLASS_VMMR_ENABLED = true;\n    static final boolean CONFIG_KLASS_VBSR_ENABLED = false;\n    static final double CONFIG_KLASS_VCR_GAMMA = 1.5;\n    static final double CONFIG_RECOGN_MINSCORE = 0.4; // 40%\n    static final String CONFIG_RECOGN_SCORE_TYPE = \"min\";\n    static final boolean CONFIG_RECOGN_RECTIFY_ENABLED = false;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        Log.i(TAG, \"onCreate \" + this);\n        super.onCreate(savedInstanceState);\n\n        getSupportFragmentManager().beginTransaction()\n                .replace(R.id.container, AlprCameraFragment.newInstance(PREFERRED_SIZE, this))\n                .commit();\n    }\n\n    @Override\n    public void onResume() {\n        super.onResume();\n\n    }\n\n    @Override\n    public void onDestroy() {\n        Log.i(TAG, \"onDestroy \" + this);\n        super.onDestroy();\n    }\n\n    @Override\n    protected int getLayoutResId() {\n        return R.layout.activity_main;\n    }\n\n    @Override\n    protected JSONObject getJsonConfig() {\n\n        JSONObject config = new JSONObject();\n        try {\n            config.put(\"debug_level\", CONFIG_DEBUG_LEVEL);\n            config.put(\"debug_write_input_image_enabled\", CONFIG_DEBUG_WRITE_INPUT_IMAGE);\n            config.put(\"debug_internal_data_path\", getDebugInternalDataPath());\n\n            config.put(\"num_threads\", CONFIG_NUM_THREADS);\n            config.put(\"gpgpu_enabled\", CONFIG_GPGPU_ENABLED);\n            config.put(\"charset\", CONFIG_CHARSET);\n            config.put(\"max_latency\", CONFIG_MAX_LATENCY);\n            config.put(\"ienv_enabled\", CONFIG_IENV_ENABLED);\n            config.put(\"openvino_enabled\", CONFIG_OPENVINO_ENABLED);\n            config.put(\"openvino_device\", CONFIG_OPENVINO_DEVICE);\n\n            config.put(\"detect_minscore\", CONFIG_DETECT_MINSCORE);\n            config.put(\"detect_roi\", new JSONArray(getDetectROI()));\n\n            config.put(\"car_noplate_detect_enabled\", CONFIG_CAR_NOPLATE_DETECT_ENABLED);\n            config.put(\"car_noplate_detect_min_score\", CONFIG_CAR_NOPLATE_DETECT_MINSCORE);\n\n            config.put(\"pyramidal_search_enabled\", CONFIG_PYRAMIDAL_SEARCH_ENABLED);\n            config.put(\"pyramidal_search_sensitivity\", CONFIG_PYRAMIDAL_SEARCH_SENSITIVITY);\n            config.put(\"pyramidal_search_minscore\", CONFIG_PYRAMIDAL_SEARCH_MINSCORE);\n            config.put(\"pyramidal_search_min_image_size_inpixels\", CONFIG_PYRAMIDAL_SEARCH_MIN_IMAGE_SIZE_INPIXELS);\n\n            config.put(\"klass_lpci_enabled\", CONFIG_KLASS_LPCI_ENABLED);\n            config.put(\"klass_vcr_enabled\", CONFIG_KLASS_VCR_ENABLED);\n            config.put(\"klass_vmmr_enabled\", CONFIG_KLASS_VMMR_ENABLED);\n            config.put(\"klass_vbsr_enabled\", CONFIG_KLASS_VBSR_ENABLED);\n            config.put(\"klass_vcr_gamma\", CONFIG_KLASS_VCR_GAMMA);\n\n            config.put(\"recogn_minscore\", CONFIG_RECOGN_MINSCORE);\n            config.put(\"recogn_score_type\", CONFIG_RECOGN_SCORE_TYPE);\n            config.put(\"recogn_rectify_enabled\", CONFIG_RECOGN_RECTIFY_ENABLED);\n        }\n        catch (JSONException e) {\n            e.printStackTrace();\n        }\n        return config;\n    }\n\n\n    @Override\n    protected boolean isParallelDeliveryEnabled() { return false; /* we want to deactivated parallel and use sequential delivery*/ }\n\n    @Override\n    protected List<Float> getDetectROI() { return CONFIG_DETECT_ROI; }\n}\n"
  },
  {
    "path": "demo/src/main/java/org/buyun/alpr/common/AlprActivity.java",
    "content": "package org.buyun.alpr.common;\n\nimport android.graphics.RectF;\nimport android.media.ExifInterface;\nimport android.media.Image;\nimport android.os.Bundle;\nimport android.os.Environment;\nimport android.os.SystemClock;\nimport android.util.Log;\nimport android.util.Size;\nimport android.view.Window;\nimport android.view.WindowManager;\n\nimport androidx.annotation.NonNull;\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport org.buyun.alpr.sdk.SDK_IMAGE_TYPE;\nimport org.buyun.alpr.sdk.AlprSdk;\nimport org.buyun.alpr.sdk.AlprCallback;\nimport org.buyun.alpr.sdk.AlprResult;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.util.List;\n\n/**\n * Base activity to subclass to make our life easier\n */\npublic abstract class AlprActivity extends AppCompatActivity implements AlprCameraFragment.AlprCameraFragmentSink {\n\n    static final String TAG = AlprActivity.class.getCanonicalName();\n\n    private String mDebugInternalDataPath = null;\n\n    private boolean mIsProcessing = false;\n    private boolean mIsPaused = true;\n\n    /**\n     * Parallel callback delivery function used by the engine to notify for new deferred results\n     */\n    static class MyUltAlprSdkParallelDeliveryCallback extends AlprCallback {\n        static final String TAG = MyUltAlprSdkParallelDeliveryCallback.class.getCanonicalName();\n\n        AlprPlateView mAlprPlateView;\n        Size mImageSize;\n        long mTotalDuration = 0;\n        int mOrientation = 0;\n\n        void setAlprPlateView(@NonNull final AlprPlateView view) {\n            mAlprPlateView = view;\n        }\n\n        void setImageSize(@NonNull final Size imageSize, @NonNull final int orientation) {\n            mImageSize = imageSize;\n            mOrientation = orientation;\n        }\n\n        void setDurationTime(final long totalDuration) {\n            mTotalDuration = totalDuration;\n        }\n\n        @Override\n        public void onNewResult(AlprResult result) {\n            Log.d(TAG, AlprUtils.resultToString(result));\n            if (mAlprPlateView!= null) {\n                mAlprPlateView.setResult(result, mImageSize, mTotalDuration, mOrientation);\n            }\n        }\n        static MyUltAlprSdkParallelDeliveryCallback newInstance() {\n            return new MyUltAlprSdkParallelDeliveryCallback();\n        }\n    }\n\n    /**\n     * The parallel delivery callback. Set to null to disable parallel mode\n     * and enforce sequential mode.\n     */\n    private MyUltAlprSdkParallelDeliveryCallback mParallelDeliveryCallback;\n\n    private AlprPlateView mAlprPlateView;\n\n    @Override\n    protected void onCreate(final Bundle savedInstanceState) {\n        Log.i(TAG, \"onCreate \" + this);\n        super.onCreate(savedInstanceState);\n        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);\n        requestWindowFeature(Window.FEATURE_NO_TITLE);\n        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);\n        setContentView(getLayoutResId());\n\n        // Create folder to dump input images for debugging\n        File dummyFile = new File(getExternalFilesDir(null), \"dummyFile\");\n        if (!dummyFile.getParentFile().exists() && !dummyFile.getParentFile().mkdirs()) {\n            Log.e(TAG,\"mkdir failed: \" + dummyFile.getParentFile().getAbsolutePath());\n        }\n        mDebugInternalDataPath = dummyFile.getParentFile().exists() ? dummyFile.getParent() : Environment.getExternalStorageDirectory().getAbsolutePath();\n        dummyFile.delete();\n\n        // Create parallel delivery callback is enabled\n        mParallelDeliveryCallback = isParallelDeliveryEnabled() ? MyUltAlprSdkParallelDeliveryCallback.newInstance() : null;\n\n        // Init the engine\n        final JSONObject config = getJsonConfig();\n        // Retrieve previously stored key from internal storage\n        final AlprResult alprResult = AlprUtils.assertIsOk(AlprSdk.init(\n                getAssets(),\n                config.toString(),\n                mParallelDeliveryCallback\n        ));\n        Log.i(TAG,\"ALPR engine initialized: \" + AlprUtils.resultToString(alprResult));\n    }\n\n    @Override\n    public void onDestroy() {\n        Log.i(TAG, \"onDestroy \" + this);\n        final AlprResult result = AlprUtils.assertIsOk(AlprSdk.deInit());\n        Log.i(TAG,\"ALPR engine deInitialized: \" + AlprUtils.resultToString(result));\n\n        super.onDestroy();\n    }\n\n    @Override\n    public synchronized void onResume() {\n        super.onResume();\n\n        mIsPaused = false;\n    }\n\n    @Override\n    public synchronized void onPause() {\n        mIsPaused = true;\n\n        super.onPause();\n    }\n\n    @Override\n    public void setAlprPlateView(@NonNull final AlprPlateView view) {\n        mAlprPlateView = view;\n        if (mParallelDeliveryCallback != null) {\n            mParallelDeliveryCallback.setAlprPlateView(view);\n        }\n        final List<Float> roi = getDetectROI();\n        assert(roi.size() == 4);\n        mAlprPlateView.setDetectROI(\n                new RectF(\n                        roi.get(0).floatValue(),\n                        roi.get(2).floatValue(),\n                        roi.get(1).floatValue(),\n                        roi.get(3).floatValue()\n                )\n        );\n    }\n\n    @Override\n    public void setImage(@NonNull final Image image, final int jpegOrientation) {\n\n        // On sequential mode we just ignore the processing\n        if (mIsProcessing || mIsPaused) {\n            Log.d(TAG, \"Inference function not returned yet: Processing or paused\");\n            image.close();\n            return;\n        }\n\n        mIsProcessing = true;\n\n        final Size imageSize = new Size(image.getWidth(), image.getHeight());\n\n        // Orientation\n        // Convert from degree to real EXIF orientation\n        int exifOrientation;\n        switch (jpegOrientation) {\n            case 90: exifOrientation = ExifInterface.ORIENTATION_ROTATE_90; break;\n            case 180: exifOrientation = ExifInterface.ORIENTATION_ROTATE_180; break;\n            case 270: exifOrientation = ExifInterface.ORIENTATION_ROTATE_270; break;\n            case 0: default: exifOrientation = ExifInterface.ORIENTATION_NORMAL; break;\n        }\n\n        // Update image for the async callback\n        if (mParallelDeliveryCallback != null) {\n            mParallelDeliveryCallback.setImageSize((jpegOrientation % 180) == 0 ? imageSize : new Size(imageSize.getHeight(), imageSize.getWidth()), jpegOrientation);\n        }\n\n        // The actual ALPR inference is done here\n        // Do not worry about the time taken to perform the inference, the caller\n        // (most likely the camera fragment) set the current image using a background thread.\n        final Image.Plane[] planes = image.getPlanes();\n        final long startTimeInMillis = SystemClock.uptimeMillis();\n        final AlprResult result = /*AlprUtils.assertIsOk*/(AlprSdk.process(\n                SDK_IMAGE_TYPE.ULTALPR_SDK_IMAGE_TYPE_YUV420P,\n                planes[0].getBuffer(),\n                planes[1].getBuffer(),\n                planes[2].getBuffer(),\n                imageSize.getWidth(),\n                imageSize.getHeight(),\n                planes[0].getRowStride(),\n                planes[1].getRowStride(),\n                planes[2].getRowStride(),\n                planes[1].getPixelStride(),\n                exifOrientation\n        ));\n        final long durationInMillis = SystemClock.uptimeMillis() - startTimeInMillis; // Total time: Inference + image processing (chroma conversion, rotation...)\n\n        if (mParallelDeliveryCallback != null) {\n            mParallelDeliveryCallback.setDurationTime(durationInMillis);\n        }\n\n        // Release the image and signal the inference process is finished\n        image.close();\n\n        mIsProcessing = false;\n\n        if (result.isOK()) {\n            Log.d(TAG, AlprUtils.resultToString(result));\n        } else {\n            Log.e(TAG, AlprUtils.resultToString(result));\n        }\n\n        // Display the result if sequential mode. Otherwise, let the parallel callback\n        // display the result when provided.\n        // Starting version 3.2 the callback will be called even if the result is empty\n        if (mAlprPlateView != null && (mParallelDeliveryCallback == null || (result.numPlates() == 0 && result.numCars() == 0))) { // means sequential call or no plate/car to expect from the parallel delivery callback\n            mAlprPlateView.setResult(result, (jpegOrientation % 180) == 0 ? imageSize : new Size(imageSize.getHeight(), imageSize.getWidth()), durationInMillis, jpegOrientation);\n        }\n    }\n\n    /**\n     * Gets the base folder defining a path where the application can write private\n     * data.\n     * @return The path\n     */\n    protected String getDebugInternalDataPath() {\n        return mDebugInternalDataPath;\n    }\n\n    /**\n     * Gets the server url used to activate the license. Please contact us to get the correct URL.\n     * e.g. https://localhost:3600\n     * @return The URL\n     */\n    protected String getActivationServerUrl() {\n        return \"\";\n    }\n\n    protected String getActivationMasterOrSlaveKey() {\n        return \"\";\n    }\n\n    /**\n     * Returns the layout Id for the activity\n     * @return\n     */\n    protected abstract int getLayoutResId();\n\n    /**\n     * Returns JSON config to be used to initialize the ALPR/ANPR SDK.\n     * @return The JSON config\n     */\n    protected abstract JSONObject getJsonConfig();\n\n    /**\n     */\n    protected abstract boolean isParallelDeliveryEnabled();\n\n    protected abstract List<Float> getDetectROI();\n}"
  },
  {
    "path": "demo/src/main/java/org/buyun/alpr/common/AlprBackgroundTask.java",
    "content": "package org.buyun.alpr.common;\n\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\n\npublic class AlprBackgroundTask {\n\n    private Handler mHandler;\n    private HandlerThread mThread;\n\n    public synchronized final Handler getHandler() {\n        return mHandler;\n    }\n    public synchronized final boolean isRunning() { return mHandler != null; }\n\n    public synchronized void start(final String threadName) {\n        if (mThread != null) {\n            return;\n        }\n        mThread = new HandlerThread(threadName);\n        mThread.start();\n        mHandler = new Handler(mThread.getLooper());\n    }\n\n    public synchronized void stop() {\n        if (mThread == null) {\n            return;\n        }\n        mThread.quitSafely();\n        try {\n            mThread.join();\n            mThread = null;\n            mHandler = null;\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public synchronized void post(final Runnable r) {\n        if (mHandler != null) {\n            mHandler.post(r);\n        }\n    }\n}"
  },
  {
    "path": "demo/src/main/java/org/buyun/alpr/common/AlprCameraFragment.java",
    "content": "package org.buyun.alpr.common;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.content.pm.PackageManager;\nimport android.content.res.Configuration;\nimport android.graphics.Color;\nimport android.graphics.ImageFormat;\nimport android.graphics.SurfaceTexture;\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.params.StreamConfigurationMap;\nimport android.media.Image;\nimport android.media.ImageReader;\nimport android.os.Bundle;\n\nimport androidx.annotation.NonNull;\nimport androidx.core.app.ActivityCompat;\nimport androidx.fragment.app.DialogFragment;\nimport androidx.fragment.app.Fragment;\nimport androidx.core.content.ContextCompat;\n\nimport android.text.TextUtils;\nimport android.util.Log;\nimport android.util.Size;\nimport android.util.SparseIntArray;\nimport android.view.LayoutInflater;\nimport android.view.Surface;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.concurrent.Semaphore;\nimport java.util.concurrent.TimeUnit;\n\nimport org.buyun.alpr.R; // FIXME(dmi): must remove\n\n\npublic class AlprCameraFragment extends Fragment\n        implements ActivityCompat.OnRequestPermissionsResultCallback {\n\n    static final int REQUEST_CAMERA_PERMISSION = 1;\n\n    static final String FRAGMENT_DIALOG = \"dialog\";\n\n    static final String TAG = AlprCameraFragment.class.getCanonicalName();\n\n    static final int VIDEO_FORMAT = ImageFormat.YUV_420_888; // All Android devices are required to support this format\n\n    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();\n    static {\n        ORIENTATIONS.append(Surface.ROTATION_0, 90);\n        ORIENTATIONS.append(Surface.ROTATION_90, 0);\n        ORIENTATIONS.append(Surface.ROTATION_180, 270);\n        ORIENTATIONS.append(Surface.ROTATION_270, 180);\n    }\n\n    /**\n     * Using #2: processing and pending.\n     */\n    static final int MAX_IMAGES = 2;\n\n    /**\n     * The camera preview size will be chosen to be the smallest frame by pixel size capable of\n     * containing a DESIRED_SIZE x DESIRED_SIZE square.\n     */\n    static final int MINIMUM_PREVIEW_SIZE = 320;\n\n    private Size mPreferredSize = null;\n\n    /**\n     * ID of the current {@link CameraDevice}.\n     */\n    private String mCameraId;\n\n    private int mJpegOrientation = 1;\n\n    /**\n     * An {@link AlprGLSurfaceView} for camera preview.\n     */\n    private AlprGLSurfaceView mGLSurfaceView;\n\n    private AlprPlateView mPlateView;\n\n    /**\n     * A {@link CameraCaptureSession } for camera preview.\n     */\n    private CameraCaptureSession mCaptureSession;\n\n    /**\n     * A reference to the opened {@link CameraDevice}.\n     */\n    private CameraDevice mCameraDevice;\n\n    /**\n     * The {@link android.util.Size} of camera preview.\n     */\n    private Size mPreviewSize;\n\n    private AlprCameraFragmentSink mSink;\n\n    private final AlprBackgroundTask mBackgroundTaskCamera = new AlprBackgroundTask();\n    private final AlprBackgroundTask mBackgroundTaskDrawing = new AlprBackgroundTask();\n    private final AlprBackgroundTask mBackgroundTaskInference = new AlprBackgroundTask();\n\n    /**\n     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.\n     */\n    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {\n\n        @Override\n        public void onOpened(@NonNull CameraDevice cameraDevice) {\n            // This method is called when the camera is opened.  We start camera preview here.\n            mCameraOpenCloseLock.release();\n            mCameraDevice = cameraDevice;\n            createCameraCaptureSession();\n        }\n\n        @Override\n        public void onDisconnected(@NonNull CameraDevice cameraDevice) {\n            mCameraOpenCloseLock.release();\n            cameraDevice.close();\n            mCameraDevice = null;\n        }\n\n        @Override\n        public void onError(@NonNull CameraDevice cameraDevice, int error) {\n            mCameraOpenCloseLock.release();\n            cameraDevice.close();\n            mCameraDevice = null;\n            Activity activity = getActivity();\n            if (null != activity) {\n                activity.finish();\n            }\n        }\n    };\n\n    private boolean mClosingCamera = false;\n\n    /**\n     * An {@link ImageReader} that handles still image capture.\n     */\n    private ImageReader mImageReaderInference;\n\n    private ImageReader mImageReaderDrawing;\n\n\n    /**\n     * This a callback object for the {@link ImageReader}. \"onImageAvailable\" will be called when a\n     * still image is ready to be saved.\n     */\n    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener\n            = new ImageReader.OnImageAvailableListener() {\n\n        @Override\n        public void onImageAvailable(ImageReader reader) {\n            if (mClosingCamera) {\n                Log.d(TAG, \"Closing camera\");\n                return;\n            }\n            try {\n                final Image image = reader.acquireLatestImage();\n                if (image == null) {\n                    return;\n                }\n\n                final boolean isForDrawing = (reader.getSurface() == mImageReaderDrawing.getSurface());\n                if (isForDrawing) {\n                    /*mBackgroundTaskDrawing.post(() ->*/ mGLSurfaceView.setImage(image, mJpegOrientation)/*)*/;\n                }\n                else {\n                    /*mBackgroundTaskInference.post(() ->*/ mSink.setImage(image, mJpegOrientation)/*)*/;\n                }\n\n            } catch (final Exception e) {\n                e.printStackTrace();\n                Log.e(TAG, e.toString());\n            }\n        }\n    };\n\n    private CaptureRequest.Builder mCaptureRequestBuilder;\n\n    /**\n     * {@link CaptureRequest} generated by {@link #mCaptureRequestBuilder}\n     */\n    private CaptureRequest mCaptureRequest;\n\n    /**\n     * A {@link Semaphore} to prevent the app from exiting before closing the camera.\n     */\n    private Semaphore mCameraOpenCloseLock = new Semaphore(1);\n\n    /**\n     * Orientation of the camera sensor\n     */\n    private int mSensorOrientation;\n\n    /**\n     * Default constructor automatically called when the fragment is recreated. Required.\n     * https://stackoverflow.com/questions/51831053/could-not-find-fragment-constructor\n     */\n    public AlprCameraFragment() {\n        // nothing special here\n    }\n\n    private AlprCameraFragment(final Size preferredSize, final AlprCameraFragmentSink sink) {\n        mPreferredSize = preferredSize;\n        mSink = sink;\n    }\n\n    /**\n     * Public function to be called to create the fragment.\n     * @param preferredSize\n     * @return\n     */\n    public static AlprCameraFragment newInstance(final Size preferredSize, final AlprCameraFragmentSink sink) {\n        return new AlprCameraFragment(preferredSize, sink);\n    }\n\n    @Override\n    public View onCreateView(LayoutInflater inflater, ViewGroup container,\n                             Bundle savedInstanceState) {\n        return inflater.inflate(R.layout.fragment_camera, container, false);\n    }\n\n    @Override\n    public void onViewCreated(final View view, Bundle savedInstanceState) {\n        mGLSurfaceView = (AlprGLSurfaceView) view.findViewById(R.id.glSurfaceView);\n        mPlateView = (AlprPlateView) view.findViewById(R.id.plateView);\n        //mPlateView.setBackgroundColor(Color.RED);\n    }\n\n    @Override\n    public void onActivityCreated(Bundle savedInstanceState) {\n        super.onActivityCreated(savedInstanceState);\n    }\n\n    @Override\n    public synchronized void onResume() {\n        super.onResume();\n        startBackgroundThreads();\n\n        // Forward the plateView to the sink\n        if (mSink != null && mPlateView != null) {\n            mSink.setAlprPlateView(mPlateView);\n        }\n\n        // Open the camera\n        openCamera(mGLSurfaceView.getWidth(), mGLSurfaceView.getHeight());\n    }\n\n    @Override\n    public synchronized void onPause() {\n        closeCamera();\n        stopBackgroundThreads();\n        super.onPause();\n    }\n\n    private void requestCameraPermission() {\n        if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {\n            new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);\n        } else {\n            requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);\n        }\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,\n                                           @NonNull int[] grantResults) {\n        if (requestCode == REQUEST_CAMERA_PERMISSION) {\n            if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {\n                ErrorDialog.newInstance(getString(R.string.request_permission))\n                        .show(getChildFragmentManager(), FRAGMENT_DIALOG);\n            }\n        } else {\n            super.onRequestPermissionsResult(requestCode, permissions, grantResults);\n        }\n    }\n\n    /**\n     * Shows a {@link Toast} on the UI thread.\n     *\n     * @param text The message to show\n     */\n    private void showToast(final String text) {\n        final Activity activity = getActivity();\n        if (activity != null) {\n            activity.runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                    Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();\n                }\n            });\n        }\n    }\n\n    /**\n     * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose\n     * width and height are at least as large as the minimum of both, or an exact match if possible.\n     *\n     * @param choices The list of sizes that the camera supports for the intended output class\n     * @param width The minimum desired width\n     * @param height The minimum desired height\n     * @return The optimal {@code Size}, or an arbitrary one if none were big enough\n     */\n    private static Size chooseOptimalSize(final Size[] choices, final int width, final int height) {\n        final int minSize = Math.max(Math.min(width, height), MINIMUM_PREVIEW_SIZE);\n        final Size desiredSize = new Size(width, height);\n\n        // Collect the supported resolutions that are at least as big as the preview Surface\n        boolean exactSizeFound = false;\n        final List<Size> bigEnough = new ArrayList<Size>();\n        final List<Size> tooSmall = new ArrayList<Size>();\n        for (final Size option : choices) {\n            if (option.equals(desiredSize)) {\n                // Set the size but don't return yet so that remaining sizes will still be logged.\n                exactSizeFound = true;\n            }\n\n            if (option.getHeight() >= minSize && option.getWidth() >= minSize) {\n                bigEnough.add(option);\n            } else {\n                tooSmall.add(option);\n            }\n        }\n\n        Log.i(TAG, \"Desired size: \" + desiredSize + \", min size: \" + minSize + \"x\" + minSize);\n        Log.i(TAG, \"Valid preview sizes: [\" + TextUtils.join(\", \", bigEnough) + \"]\");\n        Log.i(TAG, \"Rejected preview sizes: [\" + TextUtils.join(\", \", tooSmall) + \"]\");\n\n        if (exactSizeFound) {\n            Log.i(TAG, \"Exact size match found.\");\n            return desiredSize;\n        }\n\n        // Pick the smallest of those, assuming we found any\n        if (bigEnough.size() > 0) {\n            final Size chosenSize = Collections.min(bigEnough, new CompareSizesByArea());\n            Log.i(TAG, \"Chosen size: \" + chosenSize.getWidth() + \"x\" + chosenSize.getHeight());\n            return chosenSize;\n        } else {\n            Log.e(TAG, \"Couldn't find any suitable preview size\");\n            return choices[0];\n        }\n    }\n\n    /**\n     * Sets up member variables related to camera.\n     *\n     */\n    @SuppressWarnings(\"SuspiciousNameCombination\")\n    private void setUpCameraOutputs() {\n        Activity activity = getActivity();\n        CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);\n        try {\n            for (String cameraId : manager.getCameraIdList()) {\n                CameraCharacteristics characteristics\n                        = manager.getCameraCharacteristics(cameraId);\n\n                // We don't use a front facing camera in this sample.\n                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);\n                if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {\n                    continue;\n                }\n\n                StreamConfigurationMap map = characteristics.get(\n                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);\n                if (map == null) {\n                    continue;\n                }\n\n                mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);\n\n                // JPEG orientation\n                // https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#JPEG_ORIENTATION\n                int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();\n                mJpegOrientation = (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;\n\n                // Danger, W.R.! Attempting to use too large a preview size could  exceed the camera\n                // bus' bandwidth limitation, resulting in gorgeous previews but the storage of\n                // garbage capture data.\n                mPreviewSize =\n                        chooseOptimalSize(\n                                map.getOutputSizes(SurfaceTexture.class),\n                                mPreferredSize.getWidth(),\n                                mPreferredSize.getHeight());\n\n                // We fit the aspect ratio of TextureView to the size of preview we picked.\n                final int orientation = getResources().getConfiguration().orientation;\n                if (orientation == Configuration.ORIENTATION_LANDSCAPE) {\n                    mGLSurfaceView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());\n                    mPlateView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());\n                } else {\n                    mGLSurfaceView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());\n                    mPlateView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());\n                }\n\n                mCameraId = cameraId;\n                return;\n            }\n        } catch (CameraAccessException e) {\n            e.printStackTrace();\n        } catch (NullPointerException e) {\n            // Currently an NPE is thrown when the Camera2API is used but not supported on the\n            // device this code runs.\n            ErrorDialog.newInstance(getString(R.string.camera_error))\n                    .show(getChildFragmentManager(), FRAGMENT_DIALOG);\n        }\n    }\n\n    /**\n     * Opens the camera specified by {@link AlprCameraFragment#mCameraId}.\n     */\n    private void openCamera(int width, int height) {\n        if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)\n                != PackageManager.PERMISSION_GRANTED) {\n            requestCameraPermission();\n            return;\n        }\n        setUpCameraOutputs();\n        Activity activity = getActivity();\n        CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);\n        try {\n            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {\n                throw new RuntimeException(\"Time out waiting to lock camera opening.\");\n            }\n            manager.openCamera(mCameraId, mStateCallback, mBackgroundTaskCamera.getHandler());\n        } catch (CameraAccessException e) {\n            e.printStackTrace();\n        } catch (InterruptedException e) {\n            throw new RuntimeException(\"Interrupted while trying to lock camera opening.\", e);\n        }\n    }\n\n    /**\n     * Closes the current {@link CameraDevice}.\n     */\n    private void closeCamera() {\n        try {\n            mClosingCamera = true;\n            mCameraOpenCloseLock.acquire();\n\n            if (null != mCaptureSession) {\n                mCaptureSession.close();\n                mCaptureSession = null;\n            }\n            if (null != mCameraDevice) {\n                mCameraDevice.close();\n                mCameraDevice = null;\n            }\n            if (null != mImageReaderInference) {\n                mImageReaderInference.close();\n                mImageReaderInference = null;\n            }\n            if (null != mImageReaderDrawing) {\n                mImageReaderDrawing.close();\n                mImageReaderDrawing = null;\n            }\n        } catch (InterruptedException e) {\n            throw new RuntimeException(\"Interrupted while trying to lock camera closing.\", e);\n        } finally {\n            mCameraOpenCloseLock.release();\n            mClosingCamera = false;\n        }\n    }\n\n    /**\n     * Starts a background threads\n     */\n    private void startBackgroundThreads() {\n        mBackgroundTaskInference.start(\"InferenceBackgroundThread\");\n        mBackgroundTaskDrawing.start(\"DrawingBackgroundThread\");\n        mBackgroundTaskCamera.start(\"CameraBackgroundThread\");\n    }\n\n    /**\n     * Stops the background threads\n     */\n    private void stopBackgroundThreads() {\n        mBackgroundTaskInference.stop();\n        mBackgroundTaskDrawing.stop();\n        mBackgroundTaskCamera.stop();\n    }\n\n    /**\n     * Creates a new {@link CameraCaptureSession} for camera preview.\n     */\n    private void createCameraCaptureSession() {\n        try {\n            // Create Image readers\n            mImageReaderInference = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),\n                    VIDEO_FORMAT, MAX_IMAGES);\n            mImageReaderInference.setOnImageAvailableListener(\n                    mOnImageAvailableListener, mBackgroundTaskCamera.getHandler());\n\n            mImageReaderDrawing = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),\n                    VIDEO_FORMAT, MAX_IMAGES);\n            mImageReaderDrawing.setOnImageAvailableListener(\n                    mOnImageAvailableListener, mBackgroundTaskCamera.getHandler());\n\n            // We set up a CaptureRequest.Builder with the output Surface to the image reader\n            mCaptureRequestBuilder\n                    = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);\n            //mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(1, 25));\n            //mCaptureRequestBuilder.set(CaptureRequest.CONTROL_MODE,\n            //        CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);\n            //mCaptureRequestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE,\n            //        CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO);\n            mCaptureRequestBuilder.addTarget(mImageReaderInference.getSurface());\n            mCaptureRequestBuilder.addTarget(mImageReaderDrawing.getSurface());\n\n            // Here, we create a CameraCaptureSession\n            mCameraDevice.createCaptureSession(Arrays.asList(mImageReaderInference.getSurface(), mImageReaderDrawing.getSurface()),\n                    new CameraCaptureSession.StateCallback() {\n\n                        @Override\n                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {\n                            // The camera is already closed\n                            if (null == mCameraDevice) {\n                                return;\n                            }\n\n                            // When the session is ready, we start displaying the preview.\n                            mCaptureSession = cameraCaptureSession;\n                            try {\n                                // Auto focus should be continuous\n                                mCaptureRequestBuilder.set(\n                                        CaptureRequest.CONTROL_AF_MODE,\n                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);\n                                // Flash is automatically enabled when necessary.\n                                mCaptureRequestBuilder.set(\n                                        CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);\n\n                                // Finally, we start grabbing the frames\n                                mCaptureRequest = mCaptureRequestBuilder.build();\n                                mCaptureSession.setRepeatingRequest(mCaptureRequest,\n                                        null, mBackgroundTaskCamera.getHandler());\n\n                            } catch (CameraAccessException e) {\n                                e.printStackTrace();\n                            }\n                        }\n\n                        @Override\n                        public void onConfigureFailed(\n                                @NonNull CameraCaptureSession cameraCaptureSession) {\n                            showToast(\"Failed\");\n                        }\n                    }, mBackgroundTaskCamera.getHandler()\n            );\n        } catch (CameraAccessException e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     *\n     */\n    public static interface AlprCameraFragmentSink {\n\n        /**\n         *\n         * @param view\n         */\n        public void setAlprPlateView(@NonNull final AlprPlateView view);\n\n        /**\n         *\n         * @param image\n         * @param jpegOrientation\n         */\n        public void setImage(@NonNull final Image image, final int jpegOrientation);\n    }\n\n    /**\n     * Compares two {@code Size}s based on their areas.\n     */\n    static class CompareSizesByArea implements Comparator<Size> {\n\n        @Override\n        public int compare(Size lhs, Size rhs) {\n            // We cast here to ensure the multiplications won't overflow\n            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -\n                    (long) rhs.getWidth() * rhs.getHeight());\n        }\n\n    }\n\n    /**\n     * Shows an error message dialog.\n     */\n    public static class ErrorDialog extends DialogFragment {\n\n        private static final String ARG_MESSAGE = \"message\";\n\n        public static ErrorDialog newInstance(String message) {\n            ErrorDialog dialog = new ErrorDialog();\n            Bundle args = new Bundle();\n            args.putString(ARG_MESSAGE, message);\n            dialog.setArguments(args);\n            return dialog;\n        }\n\n        @NonNull\n        @Override\n        public Dialog onCreateDialog(Bundle savedInstanceState) {\n            final Activity activity = getActivity();\n            return new AlertDialog.Builder(activity)\n                    .setMessage(getArguments().getString(ARG_MESSAGE))\n                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {\n                        @Override\n                        public void onClick(DialogInterface dialogInterface, int i) {\n                            activity.finish();\n                        }\n                    })\n                    .create();\n        }\n\n    }\n\n    /**\n     * Shows OK/Cancel confirmation dialog about camera permission.\n     */\n    public static class ConfirmationDialog extends DialogFragment {\n\n        @NonNull\n        @Override\n        public Dialog onCreateDialog(Bundle savedInstanceState) {\n            final Fragment parent = getParentFragment();\n            return new AlertDialog.Builder(getActivity())\n                    .setMessage(R.string.request_permission)\n                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {\n                        @Override\n                        public void onClick(DialogInterface dialog, int which) {\n                            parent.requestPermissions(new String[]{Manifest.permission.CAMERA},\n                                    REQUEST_CAMERA_PERMISSION);\n                        }\n                    })\n                    .setNegativeButton(android.R.string.cancel,\n                            new DialogInterface.OnClickListener() {\n                                @Override\n                                public void onClick(DialogInterface dialog, int which) {\n                                    Activity activity = parent.getActivity();\n                                    if (activity != null) {\n                                        activity.finish();\n                                    }\n                                }\n                            })\n                    .create();\n        }\n    }\n\n}"
  },
  {
    "path": "demo/src/main/java/org/buyun/alpr/common/AlprGLSurfaceView.java",
    "content": "package org.buyun.alpr.common;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.FloatBuffer;\nimport java.nio.ShortBuffer;\n\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.opengles.GL10;\n\nimport android.graphics.PixelFormat;\nimport android.media.Image;\nimport android.opengl.GLES20;\nimport android.opengl.GLSurfaceView;\nimport android.util.Log;\nimport android.view.SurfaceHolder;\n\n\n/**\n * GL surface view\n */\npublic class AlprGLSurfaceView extends GLSurfaceView implements GLSurfaceView.Renderer {\n    private static final String TAG = AlprGLSurfaceView.class.getCanonicalName();\n\n    private static final int FLOAT_SIZE_BYTES = 4;\n    private static final int SHORT_SIZE_BYTES = 2;\n    private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;\n    private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;\n    private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;\n\n    private static final float[] TRIANGLE_VERTICES_DATA_0 = {\n            1, -1, 0, 1, 1,     // 0: bottom/right\n            1, 1, 0, 1, 0,      // 1: top/right\n            -1, 1, 0, 0, 0,     // 2: top/left\n            -1, -1, 0, 0, 1     // 3: bottom/left\n    };\n    private static final short[] INDICES_DATA_0 = {\n            0, 1, 2,    // triangle #1: bottom/right, top/right, top/left\n            2, 3, 0   // triangle #2: top/left, bottom/left, bottom/right\n    };\n\n    private static final float[] TRIANGLE_VERTICES_DATA_90 = {\n            1, -1, 0, 1, 0,\n            1, 1, 0, 0, 0,\n            -1, 1, 0, 0, 1,\n            -1, -1, 0, 1, 1,\n    };\n    private static final short[] INDICES_DATA_90 = {\n            3, 0, 1,\n            1, 2, 3\n    };\n\n    private static final float[] TRIANGLE_VERTICES_DATA_180 = {\n            1, -1, 0, 0, 0,\n            1, 1, 0, 0, 1,\n            -1, 1, 0, 1, 1,\n            -1, -1, 0, 1, 0,\n    };\n    private static final short[] INDICES_DATA_180 = {\n            2, 3, 0,\n            0, 1, 2\n    };\n\n    private static final float[] TRIANGLE_VERTICES_DATA_270 = {\n            1, -1, 0, 0, 1,\n            1, 1, 0, 1, 1,\n            -1, 1, 0, 1, 0,\n            -1, -1, 0, 0, 0,\n    };\n    private static final short[] INDICES_DATA_270 = {\n            1, 2, 3,\n            3, 0, 1\n    };\n\n    private FloatBuffer mTriangleVertices;\n    private ShortBuffer mIndices;\n    private int mJpegOrientation = 0;\n    private boolean mJpegOrientationChanged = false;\n\n    private static final String VERTEX_SHADER_SOURCE = \"precision mediump float;\" +\n            \"attribute vec4 aPosition;\\n\" +\n            \"attribute vec2 aTextureCoord;\\n\" +\n            \"varying vec2 vTextureCoord;\\n\" +\n            \"void main() {\\n\" +\n            \"  gl_Position = aPosition;\\n\" +\n            \"  vTextureCoord = aTextureCoord;\\n\" +\n            \"}\\n\";\n\n    private static final String FRAGMENT_SHADER_SOURCE = \"precision mediump float;\" +\n            \"varying vec2 vTextureCoord;\" +\n            \"\" +\n            \"uniform sampler2D SamplerY; \" +\n            \"uniform sampler2D SamplerU;\" +\n            \"uniform sampler2D SamplerV;\" +\n            \"\" +\n            \"const mat3 yuv2rgb = mat3(1.164, 0, 1.596, 1.164, -0.391, -0.813, 1.164, 2.018, 0);\" +\n            \"\" +\n            \"void main() {    \" +\n            \"    vec3 yuv = vec3(1.1643 * (texture2D(SamplerY, vTextureCoord).r - 0.06274),\" +\n            \"                    texture2D(SamplerU, vTextureCoord).r - 0.5019,\" +\n            \"                    texture2D(SamplerV, vTextureCoord).r - 0.5019);\" +\n            \"    vec3 rgb = yuv * yuv2rgb;    \" +\n            \"    gl_FragColor = vec4(rgb, 1.0);\" +\n            \"} \";\n\n    private int mProgram;\n    private int maPositionHandle;\n    private int maTextureHandle;\n    private int muSamplerYHandle;\n    private int muSamplerUHandle;\n    private int muSamplerVHandle;\n    private int[] mTextureY = new int[1];\n    private int[] mTextureU = new int[1];\n    private int[] mTextureV = new int[1];\n\n    private boolean mSurfaceCreated;\n\n    private Image mImage = null;\n    private int mRatioWidth = 0;\n    private int mRatioHeight = 0;\n\n    public AlprGLSurfaceView(android.content.Context context) {\n        super(context);\n        initGL();\n    }\n\n    public AlprGLSurfaceView(android.content.Context context, android.util.AttributeSet attrs) {\n        super(context, attrs);\n        initGL();\n    }\n\n    private void initGL() {\n        setEGLContextClientVersion(2);\n        setEGLConfigChooser(8, 8, 8, 8, 16, 0);\n        setRenderer(this);\n        getHolder().setFormat(PixelFormat.TRANSLUCENT);\n        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);\n\n        mTriangleVertices = ByteBuffer.allocateDirect(TRIANGLE_VERTICES_DATA_0.length\n                * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();\n        mTriangleVertices.put(TRIANGLE_VERTICES_DATA_0).position(0);\n\n        mIndices = ByteBuffer.allocateDirect(INDICES_DATA_0.length\n                * SHORT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asShortBuffer();\n        mIndices.put(INDICES_DATA_0).position(0);\n    }\n\n    /**\n     * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio\n     * calculated from the parameters. Note that the actual sizes of parameters don't matter, that\n     * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.\n     *\n     * @param width  Relative horizontal size\n     * @param height Relative vertical size\n     */\n    public void setAspectRatio(int width, int height) {\n        if (width < 0 || height < 0) {\n            throw new IllegalArgumentException(\"Size cannot be negative.\");\n        }\n        mRatioWidth = width;\n        mRatioHeight = height;\n        requestLayout();\n    }\n\n    /**\n     *\n     * @param\n     */\n    public void setImage(final Image image, final int jpegOrientation){\n        if (!isReady()) {\n            Log.i(TAG, \"Not ready\");\n            image.close();\n            return;\n        }\n        if (mImage != null) {\n            Log.i(TAG, \"Already rendering previous image\");\n            image.close();\n            return;\n        }\n\n        // We need to save the image as the rendering is asynchronous\n        mImage = image;\n\n        if (mJpegOrientation != jpegOrientation) {\n            Log.i(TAG, \"Orientation changed: \" + mJpegOrientation + \" -> \" + jpegOrientation);\n            mJpegOrientation = jpegOrientation;\n            mJpegOrientationChanged = true;\n        }\n\n        // Signal the surface as dirty to force redrawing\n        requestRender();\n    }\n\n    public boolean isReady(){\n        return mSurfaceCreated;\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        int width = MeasureSpec.getSize(widthMeasureSpec);\n        int height = MeasureSpec.getSize(heightMeasureSpec);\n        if (0 == mRatioWidth || 0 == mRatioHeight) {\n            setMeasuredDimension(width, height);\n        } else {\n            if (width < height * mRatioWidth / mRatioHeight) {\n                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);\n            } else {\n                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);\n            }\n        }\n    }\n\n    @Override\n    public void surfaceCreated(SurfaceHolder holder) {\n        super.surfaceCreated(holder);\n\n        mSurfaceCreated = true;\n    }\n\n    @Override\n    public void surfaceDestroyed(SurfaceHolder holder) {\n        mSurfaceCreated = false;\n        if (mImage != null) {\n            mImage.close();\n            mImage = null;\n        }\n        super.surfaceDestroyed(holder);\n    }\n\n    @Override\n    public void onDrawFrame(GL10 glUnused) {\n        if (mImage == null) {\n            return;\n        }\n\n        if (mJpegOrientationChanged) {\n            updateVertices();\n            mJpegOrientationChanged = false;\n        }\n\n        final boolean swapSize = (mJpegOrientation % 180) != 0;\n        final int imageWidth = mImage.getWidth();\n        final int imageHeight = mImage.getHeight();\n\n        final AlprUtils.AlprTransformationInfo tInfo = new AlprUtils.AlprTransformationInfo(swapSize ? imageHeight : imageWidth, swapSize ? imageWidth : imageHeight, getWidth(), getHeight());\n        GLES20.glViewport(tInfo.getXOffset(), tInfo.getYOffset(), tInfo.getWidth(), tInfo.getHeight());\n        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT /*| GLES20.GL_DEPTH_BUFFER_BIT*/);\n        GLES20.glUseProgram(mProgram);\n        checkGlError(\"glUseProgram\");\n\n        final Image.Plane[] planes = mImage.getPlanes();\n\n        final ByteBuffer bufferY = planes[0].getBuffer();\n        final ByteBuffer bufferU = planes[1].getBuffer();\n        final ByteBuffer bufferV = planes[2].getBuffer();\n\n        final int uvPixelStride = planes[1].getPixelStride();\n\n        final int bufferWidthY = planes[0].getRowStride();\n        final int bufferHeightY = imageHeight;\n        final int bufferWidthUV = (planes[1].getRowStride() >> (uvPixelStride - 1));\n        final int bufferHeightUV = (bufferHeightY >> 1); // Always YUV420_888 -> half-height\n\n        final int uvFormat = uvPixelStride == 1 ? GLES20.GL_LUMINANCE : GLES20.GL_LUMINANCE_ALPHA; // Interleaved UV\n\n        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);\n        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureY[0]);\n        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, bufferWidthY, bufferHeightY, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, bufferY);\n        GLES20.glUniform1i(muSamplerYHandle, 0);\n\n        GLES20.glActiveTexture(GLES20.GL_TEXTURE1);\n        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureU[0]);\n        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, uvFormat, bufferWidthUV, bufferHeightUV, 0, uvFormat, GLES20.GL_UNSIGNED_BYTE, bufferU);\n        GLES20.glUniform1i(muSamplerUHandle, 1);\n\n        GLES20.glActiveTexture(GLES20.GL_TEXTURE2);\n        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureV[0]);\n        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, uvFormat, bufferWidthUV, bufferHeightUV, 0, uvFormat, GLES20.GL_UNSIGNED_BYTE, bufferV);\n        GLES20.glUniform1i(muSamplerVHandle, 2);\n\n        GLES20.glDrawElements(GLES20.GL_TRIANGLES, INDICES_DATA_0.length, GLES20.GL_UNSIGNED_SHORT, mIndices);\n\n        mImage.close();\n        mImage = null;\n    }\n\n    @Override\n    public void onSurfaceChanged(GL10 glUnused, int width, int height) {\n        GLES20.glViewport(0, 0, width, height);\n        // GLU.gluPerspective(glUnused, 45.0f, (float)width/(float)height, 0.1f, 100.0f);\n    }\n\n    @Override\n    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {\n        GLES20.glEnable(GLES20.GL_BLEND);\n        GLES20.glDisable(GLES20.GL_DEPTH_TEST);\n        GLES20.glDisable(GLES20.GL_DITHER);\n        GLES20.glDisable(GLES20.GL_STENCIL_TEST);\n        GLES20.glDisable(GL10.GL_DITHER);\n\n        String extensions = GLES20.glGetString(GL10.GL_EXTENSIONS);\n        Log.d(TAG, \"OpenGL extensions=\" +extensions);\n\n        // Ignore the passed-in GL10 interface, and use the GLES20\n        // class's static methods instead.\n        mProgram = createProgram(VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);\n        if (mProgram == 0) {\n            return;\n        }\n        maPositionHandle = GLES20.glGetAttribLocation(mProgram, \"aPosition\");\n        checkGlError(\"glGetAttribLocation aPosition\");\n        if (maPositionHandle == -1) {\n            throw new RuntimeException(\"Could not get attrib location for aPosition\");\n        }\n        maTextureHandle = GLES20.glGetAttribLocation(mProgram, \"aTextureCoord\");\n        checkGlError(\"glGetAttribLocation aTextureCoord\");\n        if (maTextureHandle == -1) {\n            throw new RuntimeException(\"Could not get attrib location for aTextureCoord\");\n        }\n\n        muSamplerYHandle = GLES20.glGetUniformLocation(mProgram, \"SamplerY\");\n        if (muSamplerYHandle == -1) {\n            throw new RuntimeException(\"Could not get uniform location for SamplerY\");\n        }\n        muSamplerUHandle = GLES20.glGetUniformLocation(mProgram, \"SamplerU\");\n        if (muSamplerUHandle == -1) {\n            throw new RuntimeException(\"Could not get uniform location for SamplerU\");\n        }\n        muSamplerVHandle = GLES20.glGetUniformLocation(mProgram, \"SamplerV\");\n        if (muSamplerVHandle == -1) {\n            throw new RuntimeException(\"Could not get uniform location for SamplerV\");\n        }\n\n        updateVertices();\n\n        GLES20.glGenTextures(1, mTextureY, 0);\n        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureY[0]);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);\n\n        GLES20.glGenTextures(1, mTextureU, 0);\n        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureU[0]);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);\n\n        GLES20.glGenTextures(1, mTextureV, 0);\n        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureV[0]);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);\n        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);\n\n        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n    }\n\n    private int loadShader(int shaderType, String source) {\n        int shader = GLES20.glCreateShader(shaderType);\n        if (shader != 0) {\n            GLES20.glShaderSource(shader, source);\n            GLES20.glCompileShader(shader);\n            int[] compiled = new int[1];\n            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);\n            if (compiled[0] == 0) {\n                Log.e(TAG, \"Could not compile shader \" + shaderType + \":\");\n                Log.e(TAG, GLES20.glGetShaderInfoLog(shader));\n                GLES20.glDeleteShader(shader);\n                shader = 0;\n            }\n        }\n        return shader;\n    }\n\n    private int createProgram(String vertexSource, String fragmentSource) {\n        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);\n        if (vertexShader == 0) {\n            return 0;\n        }\n\n        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);\n        if (pixelShader == 0) {\n            return 0;\n        }\n\n        int program = GLES20.glCreateProgram();\n        if (program != 0) {\n            GLES20.glAttachShader(program, vertexShader);\n            checkGlError(\"glAttachShader\");\n            GLES20.glAttachShader(program, pixelShader);\n            checkGlError(\"glAttachShader\");\n            GLES20.glLinkProgram(program);\n            int[] linkStatus = new int[1];\n            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);\n            if (linkStatus[0] != GLES20.GL_TRUE) {\n                Log.e(TAG, \"Could not link program: \");\n                Log.e(TAG, GLES20.glGetProgramInfoLog(program));\n                GLES20.glDeleteProgram(program);\n                program = 0;\n            }\n        }\n        return program;\n    }\n\n    private void updateVertices() {\n        mTriangleVertices.rewind();\n        mIndices.rewind();\n\n        switch (mJpegOrientation) {\n            case 90:\n                mTriangleVertices.put(TRIANGLE_VERTICES_DATA_90).position(0);\n                mIndices.put(INDICES_DATA_90).position(0);\n                break;\n            case 180:\n                mTriangleVertices.put(TRIANGLE_VERTICES_DATA_180).position(0);\n                mIndices.put(INDICES_DATA_180).position(0);\n                break;\n            case 270:\n                mTriangleVertices.put(TRIANGLE_VERTICES_DATA_270).position(0);\n                mIndices.put(INDICES_DATA_270).position(0);\n                break;\n            case 0:\n                mTriangleVertices.put(TRIANGLE_VERTICES_DATA_0).position(0);\n                mIndices.put(INDICES_DATA_0).position(0);\n                break;\n            default:\n                throw new RuntimeException(\"Invalid orientation:\" + mJpegOrientation);\n        }\n\n        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);\n        GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);\n        checkGlError(\"glVertexAttribPointer maPosition\");\n\n        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);\n        GLES20.glEnableVertexAttribArray(maPositionHandle);\n        checkGlError(\"glEnableVertexAttribArray maPositionHandle\");\n        GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);\n        checkGlError(\"glVertexAttribPointer maTextureHandle\");\n        GLES20.glEnableVertexAttribArray(maTextureHandle);\n        checkGlError(\"glEnableVertexAttribArray maTextureHandle\");\n    }\n\n    private void checkGlError(String op) {\n        int error;\n        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {\n            Log.e(TAG, op + \": glError \" + error);\n            throw new RuntimeException(op + \": glError \" + error);\n        }\n    }\n}"
  },
  {
    "path": "demo/src/main/java/org/buyun/alpr/common/AlprImage.java",
    "content": "package org.buyun.alpr.common;\n\nimport android.media.Image;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class AlprImage {\n\n    Image mImage;\n    final AtomicInteger mRefCount;\n\n    private AlprImage(final Image image) {\n        assert image != null;\n        mImage = image;\n        mRefCount = new AtomicInteger(0);\n    }\n\n    public static AlprImage newInstance(final Image image) {\n        return new AlprImage(image);\n    }\n\n    public final Image getImage() {\n        assert mRefCount.intValue() >= 0;\n        return mImage;\n    }\n\n    public AlprImage takeRef() {\n        assert mRefCount.intValue() >= 0;\n        if (mRefCount.intValue() < 0) {\n            return null;\n        }\n        mRefCount.incrementAndGet();\n        return this;\n    }\n\n    public void releaseRef() {\n        assert mRefCount.intValue() >= 0;\n        final int refCount = mRefCount.decrementAndGet();\n        if (refCount <= 0) {\n            mImage.close();\n            mImage = null;\n        }\n    }\n\n    @Override\n    protected synchronized void finalize() {\n        if (mImage != null && mRefCount.intValue() < 0) {\n            mImage.close();\n        }\n    }\n}"
  },
  {
    "path": "demo/src/main/java/org/buyun/alpr/common/AlprPlateView.java",
    "content": "package org.buyun.alpr.common;\n\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Color;\nimport android.graphics.DashPathEffect;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.PointF;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Typeface;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.util.Size;\nimport android.util.TypedValue;\nimport android.view.View;\n\nimport androidx.annotation.NonNull;\n\nimport org.buyun.alpr.sdk.AlprResult;\n\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\npublic class AlprPlateView extends View {\n\n    static final String TAG = AlprPlateView.class.getCanonicalName();\n\n    static final float LPCI_MIN_CONFIDENCE = 80.f;\n    static final float VCR_MIN_CONFIDENCE = 80.f;\n    static final float VMMR_MIN_CONFIDENCE = 60.f;\n    static final float VBSR_MIN_CONFIDENCE = 70.f;\n    static final float VMMR_FUSE_DEFUSE_MIN_CONFIDENCE = 40.f;\n    static final int VMMR_FUSE_DEFUSE_MIN_OCCURRENCES = 3;\n\n    static final float TEXT_NUMBER_SIZE_DIP = 20;\n    static final float TEXT_LPCI_SIZE_DIP = 15;\n    static final float TEXT_CAR_SIZE_DIP = 15;\n    static final float TEXT_INFERENCE_TIME_SIZE_DIP = 20;\n    static final int STROKE_WIDTH = 10;\n\n    private final Paint mPaintTextNumber;\n    private final Paint mPaintTextNumberBackground;\n    private final Paint mPaintTextLPCI;\n    private final Paint mPaintTextLPCIBackground;\n    private final Paint mPaintTextCar;\n    private final Paint mPaintTextCarBackground;\n    private final Paint mPaintBorder;\n    private final Paint mPaintTextDurationTime;\n    private final Paint mPaintTextDurationTimeBackground;\n    private final Paint mPaintDetectROI;\n\n    private int mRatioWidth = 0;\n    private int mRatioHeight = 0;\n\n    private int mOrientation = 0;\n\n    private long mDurationTimeMillis;\n\n    private Size mImageSize;\n    private List<AlprUtils.Plate> mPlates = null;\n    private RectF mDetectROI;\n\n    /**\n     *\n     * @param context\n     * @param attrs\n     */\n    public AlprPlateView(final Context context, final AttributeSet attrs) {\n        super(context, attrs);\n\n//        final Typeface fontALPR = Typeface.createFromAsset(context.getAssets(), \"GlNummernschildEng-XgWd.ttf\");\n\n        mPaintTextNumber = new Paint();\n        mPaintTextNumber.setTextSize(TypedValue.applyDimension(\n                TypedValue.COMPLEX_UNIT_DIP, TEXT_NUMBER_SIZE_DIP, getResources().getDisplayMetrics()));\n        mPaintTextNumber.setColor(Color.BLACK);\n        mPaintTextNumber.setStyle(Paint.Style.FILL_AND_STROKE);\n//        mPaintTextNumber.setTypeface(Typeface.create(fontALPR, Typeface.BOLD));\n\n        mPaintTextNumberBackground = new Paint();\n        mPaintTextNumberBackground.setColor(Color.YELLOW);\n        mPaintTextNumberBackground.setStrokeWidth(STROKE_WIDTH);\n        mPaintTextNumberBackground.setStyle(Paint.Style.FILL_AND_STROKE);\n\n        mPaintTextLPCI = new Paint();\n        mPaintTextLPCI.setTextSize(TypedValue.applyDimension(\n                TypedValue.COMPLEX_UNIT_DIP, TEXT_LPCI_SIZE_DIP, getResources().getDisplayMetrics()));\n        mPaintTextLPCI.setColor(Color.WHITE);\n        mPaintTextLPCI.setStyle(Paint.Style.FILL_AND_STROKE);\n//        mPaintTextLPCI.setTypeface(Typeface.create(fontALPR, Typeface.BOLD));\n\n        mPaintTextLPCIBackground = new Paint();\n        mPaintTextLPCIBackground.setColor(Color.BLUE);\n        mPaintTextLPCIBackground.setStrokeWidth(STROKE_WIDTH);\n        mPaintTextLPCIBackground.setStyle(Paint.Style.FILL_AND_STROKE);\n\n        mPaintTextCar = new Paint();\n        mPaintTextCar.setTextSize(TypedValue.applyDimension(\n                TypedValue.COMPLEX_UNIT_DIP, TEXT_CAR_SIZE_DIP, getResources().getDisplayMetrics()));\n        mPaintTextCar.setColor(Color.BLACK);\n        mPaintTextCar.setStyle(Paint.Style.FILL_AND_STROKE);\n//        mPaintTextCar.setTypeface(Typeface.create(fontALPR, Typeface.BOLD));\n\n        mPaintTextCarBackground = new Paint();\n        mPaintTextCarBackground.setColor(Color.RED);\n        mPaintTextCarBackground.setStrokeWidth(STROKE_WIDTH);\n        mPaintTextCarBackground.setStyle(Paint.Style.FILL_AND_STROKE);\n\n        mPaintBorder = new Paint();\n        mPaintBorder.setStrokeWidth(STROKE_WIDTH);\n        mPaintBorder.setPathEffect(null);\n        mPaintBorder.setColor(Color.YELLOW);\n        mPaintBorder.setStyle(Paint.Style.STROKE);\n\n        mPaintTextDurationTime = new Paint();\n        mPaintTextDurationTime.setTextSize(TypedValue.applyDimension(\n                TypedValue.COMPLEX_UNIT_DIP, TEXT_INFERENCE_TIME_SIZE_DIP, getResources().getDisplayMetrics()));\n        mPaintTextDurationTime.setColor(Color.WHITE);\n        mPaintTextDurationTime.setStyle(Paint.Style.FILL_AND_STROKE);\n//        mPaintTextDurationTime.setTypeface(Typeface.create(fontALPR, Typeface.BOLD));\n\n        mPaintTextDurationTimeBackground = new Paint();\n        mPaintTextDurationTimeBackground.setColor(Color.BLACK);\n        mPaintTextDurationTimeBackground.setStrokeWidth(STROKE_WIDTH);\n        mPaintTextDurationTimeBackground.setStyle(Paint.Style.FILL_AND_STROKE);\n\n        mPaintDetectROI = new Paint();\n        mPaintDetectROI.setColor(Color.RED);\n        mPaintDetectROI.setStrokeWidth(STROKE_WIDTH);\n        mPaintDetectROI.setStyle(Paint.Style.STROKE);\n        mPaintDetectROI.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));\n    }\n\n    public void setDetectROI(final RectF roi) { mDetectROI = roi; }\n\n    /**\n     *\n     * @param width\n     * @param height\n     */\n    public void setAspectRatio(int width, int height) {\n        if (width < 0 || height < 0) {\n            throw new IllegalArgumentException(\"Size cannot be negative.\");\n        }\n        mRatioWidth = width;\n        mRatioHeight = height;\n        requestLayout();\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        Log.i(TAG, \"onMeasure\");\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n        int width = MeasureSpec.getSize(widthMeasureSpec);\n        int height = MeasureSpec.getSize(heightMeasureSpec);\n        if (0 == mRatioWidth || 0 == mRatioHeight) {\n            setMeasuredDimension(width, height);\n        } else {\n            if (width < height * mRatioWidth / mRatioHeight) {\n                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);\n            } else {\n                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);\n            }\n        }\n    }\n\n    /**\n     *\n     * @param result\n     * @param imageSize\n     */\n    public synchronized void setResult(@NonNull final AlprResult result, @NonNull final Size imageSize, @NonNull final long durationTime, @NonNull final int orientation) {\n        mPlates = AlprUtils.extractPlates(result);\n        mImageSize = imageSize;\n        mDurationTimeMillis = durationTime;\n        mOrientation = orientation;\n        postInvalidate();\n    }\n\n    @Override\n    public synchronized void draw(final Canvas canvas) {\n        super.draw(canvas);\n\n        if (mImageSize == null) {\n            Log.i(TAG, \"Not initialized yet\");\n            return;\n        }\n\n        final String mInferenceTimeMillisString = \"Point your camera at a License Plate \";\n        Rect boundsTextmInferenceTimeMillis = new Rect();\n        mPaintTextDurationTime.getTextBounds(mInferenceTimeMillisString, 0, mInferenceTimeMillisString.length(), boundsTextmInferenceTimeMillis);\n\n        int left = (canvas.getWidth() - boundsTextmInferenceTimeMillis.width()) / 2;\n        int top = 20;\n        canvas.drawRect(left, top, left + boundsTextmInferenceTimeMillis.width() + 5, top + boundsTextmInferenceTimeMillis.height() + 20, mPaintTextDurationTimeBackground);\n        canvas.drawText(mInferenceTimeMillisString, left, 20 + boundsTextmInferenceTimeMillis.height(), mPaintTextDurationTime);\n\n        // Transformation info\n        final AlprUtils.AlprTransformationInfo tInfo = new AlprUtils.AlprTransformationInfo(mImageSize.getWidth(), mImageSize.getHeight(), getWidth(), getHeight());\n\n        // ROI\n        if (mDetectROI != null && !mDetectROI.isEmpty()) {\n            canvas.drawRect(\n                    new RectF(\n                            tInfo.transformX(mDetectROI.left),\n                            tInfo.transformY(mDetectROI.top),\n                            tInfo.transformX(mDetectROI.right),\n                            tInfo.transformY(mDetectROI.bottom)\n                    ),\n                    mPaintDetectROI\n            );\n        }\n\n        // Plates\n        if (mPlates != null && !mPlates.isEmpty()) {\n            for (final AlprUtils.Plate plate : mPlates) {\n                // Transform corners\n                final float[] plateWarpedBox = plate.getWarpedBox();\n                final PointF plateCornerA = new PointF(tInfo.transformX(plateWarpedBox[0]), tInfo.transformY(plateWarpedBox[1]));\n                final PointF plateCornerB = new PointF(tInfo.transformX(plateWarpedBox[2]), tInfo.transformY(plateWarpedBox[3]));\n                final PointF plateCornerC = new PointF(tInfo.transformX(plateWarpedBox[4]), tInfo.transformY(plateWarpedBox[5]));\n                final PointF plateCornerD = new PointF(tInfo.transformX(plateWarpedBox[6]), tInfo.transformY(plateWarpedBox[7]));\n                // Draw border\n                final Path platePathBorder = new Path();\n                platePathBorder.moveTo(plateCornerA.x, plateCornerA.y);\n                platePathBorder.lineTo(plateCornerB.x, plateCornerB.y);\n                platePathBorder.lineTo(plateCornerC.x, plateCornerC.y);\n                platePathBorder.lineTo(plateCornerD.x, plateCornerD.y);\n                platePathBorder.lineTo(plateCornerA.x, plateCornerA.y);\n                platePathBorder.close();\n                mPaintBorder.setColor(mPaintTextNumberBackground.getColor());\n                canvas.drawPath(platePathBorder, mPaintBorder);\n\n                // Draw text number\n                final String number = plate.getNumber();\n                if (number != null && !number.isEmpty()) {\n                    Rect boundsTextNumber = new Rect();\n                    mPaintTextNumber.getTextBounds(number, 0, number.length(), boundsTextNumber);\n                    final RectF rectTextNumber = new RectF(\n                            plateCornerA.x,\n                            plateCornerA.y - (boundsTextNumber.height() + 10) * 2,\n                            plateCornerA.x + boundsTextNumber.width(),\n                            plateCornerA.y - (boundsTextNumber.height() + 10)\n                    );\n                    final Path pathTextNumber = new Path();\n                    pathTextNumber.moveTo(plateCornerA.x, plateCornerA.y - rectTextNumber.height() - 10);\n                    pathTextNumber.lineTo(Math.max(plateCornerB.x, (plateCornerA.x + rectTextNumber.width())), plateCornerB.y - rectTextNumber.height() - 10);\n                    pathTextNumber.addRect(rectTextNumber, Path.Direction.CCW);\n                    pathTextNumber.close();\n                    canvas.drawPath(pathTextNumber, mPaintTextNumberBackground);\n                    canvas.drawTextOnPath(number, pathTextNumber, 0, 0, mPaintTextNumber);\n                }\n\n                // Draw Car\n                if (plate.getCar() != null) {\n                    final AlprUtils.Car car = plate.getCar();\n                    if (car.getConfidence() >= 80.f) {\n                        String color = null;\n                        if (car.getColors() != null) {\n                            final AlprUtils.Car.Attribute colorObj0 = car.getColors().get(0); // sorted, most higher confidence first\n                            if (colorObj0.getConfidence() >= VCR_MIN_CONFIDENCE) {\n                                color = colorObj0.getName();\n                            }\n                            else if (car.getColors().size() >= 2) {\n                                final AlprUtils.Car.Attribute colorObj1 = car.getColors().get(1);\n                                final String colorMix = colorObj0.getName() + \"/\" + colorObj1.getName();\n                                float confidence = colorObj0.getConfidence();\n                                if (\"white/silver,silver/white,gray/silver,silver/gray\".indexOf(colorMix) != -1) {\n                                    confidence += colorObj1.getConfidence();\n                                }\n                                if (confidence >= VCR_MIN_CONFIDENCE) {\n                                    color = (colorMix.indexOf(\"white\") == -1) ? \"DarkSilver\" : \"LightSilver\";\n\t\t\t\t\t\t\t\t\tconfidence = Math.max(colorObj0.getConfidence(), colorObj1.getConfidence());\n                                }\n                            }\n                        }\n\n                        String make = null, model = null;\n                        if (car.getMakesModelsYears() != null) {\n                            final List<AlprUtils.Car.MakeModelYear> makesModelsYears = car.getMakesModelsYears();\n                            final AlprUtils.Car.MakeModelYear makeModelYear = makesModelsYears.get(0); // sorted, most higher confidence first\n                            if (makeModelYear.getConfidence() >= VMMR_MIN_CONFIDENCE) {\n                                make = makeModelYear.getMake();\n                                model = makeModelYear.getModel();\n                            }\n                            else {\n                                Map<String, Float> makes =  new HashMap<>();\n                                Map<String, Integer> occurrences =  new HashMap<>();\n\t\t\t\t\t\t\t\t// Fuse makes\n                                for (final AlprUtils.Car.MakeModelYear mmy : makesModelsYears) {\n                                    makes.put(mmy.getMake(), AlprUtils.getOrDefault(makes, mmy.getMake(), 0.f) + mmy.getConfidence()); // Map.getOrDefault requires API level 24\n                                    occurrences.put(mmy.getMake(), AlprUtils.getOrDefault(occurrences, mmy.getMake(), 0) + 1); // Map.getOrDefault requires API level 24\n                                }\n                                // Find make with highest confidence. Stream requires Java8\n                                Iterator<Map.Entry<String, Float> > itMake = makes.entrySet().iterator();\n                                Map.Entry<String, Float> bestMake = itMake.next();\n                                while (itMake.hasNext()) {\n                                    Map.Entry<String, Float> makeE = itMake.next();\n                                    if (makeE.getValue() > bestMake.getValue()) {\n                                        bestMake = makeE;\n                                    }\n                                }\n\t\t\t\t\t\t\t\t// Model fusion\n                                if (bestMake.getValue() >= VMMR_MIN_CONFIDENCE || (occurrences.get(bestMake.getKey()) >= VMMR_FUSE_DEFUSE_MIN_OCCURRENCES && bestMake.getValue() >= VMMR_FUSE_DEFUSE_MIN_CONFIDENCE)) {\n                                    make = bestMake.getKey();\n\n                                    // Fuse models\n                                    Map<String, Float> models =  new HashMap<>();\n                                    for (final AlprUtils.Car.MakeModelYear mmy : makesModelsYears) {\n                                        if (make.equals(mmy.getMake())) {\n                                            models.put(mmy.getModel(), AlprUtils.getOrDefault(models, mmy.getModel(), 0.f) + mmy.getConfidence()); // Map.getOrDefault requires API level 24\n                                        }\n                                    }\n                                    // Find model with highest confidence. Stream requires Java8\n                                    Iterator<Map.Entry<String, Float> > itModel = models.entrySet().iterator();\n                                    Map.Entry<String, Float> bestModel = itModel.next();\n                                    while (itModel.hasNext()) {\n                                        Map.Entry<String, Float> modelE = itModel.next();\n                                        if (modelE.getValue() > bestModel.getValue()) {\n                                            bestModel = modelE;\n                                        }\n                                    }\n                                    model = bestModel.getKey();\n                                }\n                            }\n                        }\n\n                        String bodyStyle = null;\n                        if (car.getBodyStyles() != null) {\n                            final AlprUtils.Car.Attribute vbsr = car.getBodyStyles().get(0); // sorted, most higher confidence first\n                            if (vbsr.getConfidence() >= VBSR_MIN_CONFIDENCE) {\n                                bodyStyle = vbsr.getName();\n                            }\n                        }\n\n                        // Transform corners\n                        final float[] carWarpedBox = car.getWarpedBox();\n                        final PointF carCornerA = new PointF(tInfo.transformX(carWarpedBox[0]), tInfo.transformY(carWarpedBox[1]));\n                        final PointF carCornerB = new PointF(tInfo.transformX(carWarpedBox[2]), tInfo.transformY(carWarpedBox[3]));\n                        final PointF carCornerC = new PointF(tInfo.transformX(carWarpedBox[4]), tInfo.transformY(carWarpedBox[5]));\n                        final PointF carCornerD = new PointF(tInfo.transformX(carWarpedBox[6]), tInfo.transformY(carWarpedBox[7]));\n                        // Draw border\n                        final Path carPathBorder = new Path();\n                        carPathBorder.moveTo(carCornerA.x, carCornerA.y);\n                        carPathBorder.lineTo(carCornerB.x, carCornerB.y);\n                        carPathBorder.lineTo(carCornerC.x, carCornerC.y);\n                        carPathBorder.lineTo(carCornerD.x, carCornerD.y);\n                        carPathBorder.lineTo(carCornerA.x, carCornerA.y);\n                        carPathBorder.close();\n                        mPaintBorder.setColor(mPaintTextCarBackground.getColor());\n                        canvas.drawPath(carPathBorder, mPaintBorder);\n\n                        // Draw car information\n                        final String carText = String.format(\n                                \"%s%s%s%s\",\n                                make != null ? make : \"Car\",\n                                model != null ? \", \" + model : \"\",\n                                color != null ? \", \" + color : \"\",\n                                bodyStyle != null ? \", \" + bodyStyle : \"\"\n                        );\n                        Rect boundsTextCar = new Rect();\n                        mPaintTextNumber.getTextBounds(carText, 0, carText.length(), boundsTextCar);\n                        final RectF rectTextNumber = new RectF(\n                                plateCornerA.x,\n                                plateCornerA.y - (boundsTextCar.height() + 5) * 3,\n                                plateCornerA.x + boundsTextCar.width(),\n                                plateCornerA.y - (boundsTextCar.height() + 5) * 2\n                        );\n                        final Path pathTextCar = new Path();\n                        pathTextCar.moveTo(plateCornerA.x, plateCornerA.y - (rectTextNumber.height() + 5) * 2);\n                        pathTextCar.lineTo(Math.max(plateCornerB.x, (plateCornerA.x + rectTextNumber.width())), plateCornerB.y - (rectTextNumber.height() + 5) * 2);\n                        pathTextCar.addRect(rectTextNumber, Path.Direction.CCW);\n                        pathTextCar.close();\n                        canvas.drawPath(pathTextCar, mPaintTextNumberBackground);\n                        canvas.drawTextOnPath(carText, pathTextCar, 0, 0, mPaintTextNumber);\n                    }\n                }\n\n                if (plate.getCountries() != null) {\n                    final AlprUtils.Country country = plate.getCountries().get(0); // sorted, most higher confidence first\n                    if (country.getConfidence() >= LPCI_MIN_CONFIDENCE) {\n                        final String countryString = country.getCode();\n                        Rect boundsConfidenceLPCI = new Rect();\n                        mPaintTextNumber.getTextBounds(countryString, 0, countryString.length(), boundsConfidenceLPCI);\n                        final RectF rectTextLPCI = new RectF(\n                                plateCornerA.x,\n                                plateCornerA.y - (boundsConfidenceLPCI.height() + 10),\n                                plateCornerA.x + (boundsConfidenceLPCI.width() + 10),\n                                plateCornerA.y\n                        );\n                        final Path pathTextLPCI = new Path();\n                        pathTextLPCI.moveTo(plateCornerA.x, plateCornerA.y);\n                        pathTextLPCI.lineTo(Math.max(plateCornerB.x, (plateCornerA.x + boundsConfidenceLPCI.width())), plateCornerB.y);\n                        pathTextLPCI.addRect(rectTextLPCI, Path.Direction.CCW);\n                        pathTextLPCI.close();\n                        canvas.drawPath(pathTextLPCI, mPaintTextNumberBackground);\n                        canvas.drawTextOnPath(countryString, pathTextLPCI, 0, 0, mPaintTextNumber);\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "demo/src/main/java/org/buyun/alpr/common/AlprUtils.java",
    "content": "package org.buyun.alpr.common;\n\nimport android.content.res.AssetFileDescriptor;\nimport android.content.res.AssetManager;\nimport android.graphics.PointF;\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport org.buyun.alpr.sdk.AlprResult;\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.nio.channels.FileChannel;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Utility class\n */\npublic class AlprUtils {\n    static final String TAG = AlprUtils.class.getCanonicalName();\n    /**\n     *\n     */\n    public static class AlprTransformationInfo {\n        final int mXOffset;\n        final int mYOffset;\n        final float mRatio;\n        final int mWidth;\n        final int mHeight;\n        public AlprTransformationInfo(final int imageWidth, final int imageHeight, final int canvasWidth, final int canvasHeight) {\n            final float xRatio = (float)canvasWidth / (float)imageWidth;\n            final float yRatio =  (float)canvasHeight / (float)imageHeight;\n            mRatio = Math.min( xRatio, yRatio );\n            mWidth = (int)(imageWidth * mRatio);\n            mHeight = (int)(imageHeight * mRatio);\n            mXOffset = (canvasWidth - mWidth) >> 1;\n            mYOffset = (canvasHeight - mHeight) >> 1;\n        }\n        public float transformX(final float x) { return x * mRatio + mXOffset; }\n        public float transformY(final float y) { return y * mRatio + mYOffset; }\n        public PointF transform(final PointF p) { return new PointF(transformX(p.x), transformY(p.y)); }\n        public int getXOffset() { return mXOffset; }\n        public int getYOffset() { return mYOffset; }\n        public float getRatio() { return mRatio; }\n        public int getWidth() { return mWidth; }\n        public int getHeight() { return mHeight; }\n    }\n\n    static class Car {\n        static class Attribute {\n            private int mKlass;\n            private String mName;\n            private float mConfidence;\n\n            public int getKlass() { return mKlass; }\n            public String getName() { return mName; }\n            public float getConfidence() { return mConfidence; }\n        }\n        static class MakeModelYear {\n            private int mKlass;\n            private String mMake;\n            private String mModel;\n            private String mYear; // Not integer on purpose, could be interval or...\n            private float mConfidence;\n\n            public int getKlass() { return mKlass; }\n            public String getMake() { return mMake; }\n            public String getModel() { return mModel; }\n            public String getYear() { return mYear; }\n            public float getConfidence() { return mConfidence; }\n        }\n\n        private float mConfidence;\n        private float mWarpedBox[];\n        private List<Car.Attribute> mColors;\n        private List<Car.Attribute> mBodyStyles;\n        private List<Car.MakeModelYear> mMakesModelsYears;\n\n        public float[] getWarpedBox() { return mWarpedBox; }\n        public float getConfidence() { return mConfidence; }\n        public List<Car.Attribute> getColors() { return mColors; }\n        public List<Car.Attribute> getBodyStyles() { return mBodyStyles; }\n        public List<Car.MakeModelYear> getMakesModelsYears() { return mMakesModelsYears; }\n    }\n\n    /**\n     *\n     */\n    static class Country {\n        private int mKlass;\n        private String mCode;\n        private String mName;\n        private String mState;\n        private String mOther;\n        private float mConfidence;\n\n        public int getKlass() { return mKlass; }\n        public String getCode() { return mCode; }\n        public String getName() { return mName; }\n        public String getState() { return mState; }\n        public String getOther() { return mOther; }\n        public float getConfidence() { return mConfidence; }\n    }\n\n    /**\n     *\n     */\n    static class Plate {\n        private String mNumber;\n        private float mDetectionConfidence;\n        private float mRecognitionConfidence;\n        private float mWarpedBox[];\n        private List<Country> mCountries;\n        private Car mCar;\n\n        public String getNumber() { return mNumber; }\n        public float getDetectionConfidence() { return mDetectionConfidence; }\n        public float getRecognitionConfidence() { return mRecognitionConfidence; }\n        public float[] getWarpedBox() { return mWarpedBox; }\n\n        public List<Country> getCountries() { return mCountries; }\n        public Car getCar() { return mCar; }\n    }\n\n    static public final long extractFrameId(final AlprResult result) {\n        final String jsonString = result.json();\n        if (jsonString != null) {\n            try {\n                final JSONObject jObject = new JSONObject(jsonString);\n                return jObject.getLong(\"frame_id\");\n            }\n            catch (JSONException e) { }\n        }\n        return 0;\n    }\n\n    static public final List<Plate> extractPlates(final AlprResult result) {\n        final List<Plate> plates = new LinkedList<>();\n        if (!result.isOK() || (result.numPlates() == 0 && result.numCars() == 0)) {\n            return plates;\n        }\n        final String jsonString = result.json();\n        //final String jsonString = \"{\\\"frame_id\\\":178,\\\"lantency\\\":0,\\\"plates\\\":[{\\\"car\\\":{\\\"color\\\":[{\\\"confidence\\\":59.76562,\\\"klass\\\":11,\\\"name\\\":\\\"white\\\"},{\\\"confidence\\\":27.73438,\\\"klass\\\":0,\\\"name\\\":\\\"black\\\"},{\\\"confidence\\\":11.32812,\\\"klass\\\":9,\\\"name\\\":\\\"silver\\\"},{\\\"confidence\\\":0.390625,\\\"klass\\\":4,\\\"name\\\":\\\"gray\\\"},{\\\"confidence\\\":0.390625,\\\"klass\\\":5,\\\"name\\\":\\\"green\\\"}],\\\"confidence\\\":89.45312,\\\"makeModelYear\\\":[{\\\"confidence\\\":5.46875,\\\"klass\\\":8072,\\\"make\\\":\\\"nissan\\\",\\\"model\\\":\\\"nv\\\",\\\"year\\\":2012},{\\\"confidence\\\":3.90625,\\\"klass\\\":4885,\\\"make\\\":\\\"gmc\\\",\\\"model\\\":\\\"yukon 1500\\\",\\\"year\\\":2007},{\\\"confidence\\\":1.953125,\\\"klass\\\":3950,\\\"make\\\":\\\"ford\\\",\\\"model\\\":\\\"f150\\\",\\\"year\\\":2001},{\\\"confidence\\\":1.953125,\\\"klass\\\":4401,\\\"make\\\":\\\"ford\\\",\\\"model\\\":\\\"ranger\\\",\\\"year\\\":2008},{\\\"confidence\\\":1.953125,\\\"klass\\\":3954,\\\"make\\\":\\\"ford\\\",\\\"model\\\":\\\"f150\\\",\\\"year\\\":2005}],\\\"warpedBox\\\":[37.26704,655.171,253.8487,655.171,253.8487,897.6935,37.26704,897.6935]},\\\"confidences\\\":[86.99596,99.60938],\\\"country\\\":[{\\\"code\\\":\\\"RUS\\\",\\\"confidence\\\":99.60938,\\\"klass\\\":65,\\\"name\\\":\\\"Russian Federation\\\",\\\"other\\\":\\\"Private vehicle\\\",\\\"state\\\":\\\"Republic of Karelia\\\"},{\\\"code\\\":\\\"USA\\\",\\\"confidence\\\":0.0,\\\"klass\\\":88,\\\"name\\\":\\\"United States of America\\\",\\\"state\\\":\\\"Iowa\\\"},{\\\"code\\\":\\\"USA\\\",\\\"confidence\\\":0.0,\\\"klass\\\":80,\\\"name\\\":\\\"United States of America\\\",\\\"state\\\":\\\"Connecticut\\\"},{\\\"code\\\":\\\"USA\\\",\\\"confidence\\\":0.0,\\\"klass\\\":81,\\\"name\\\":\\\"United States of America\\\",\\\"state\\\":\\\"Delaware\\\"},{\\\"code\\\":\\\"USA\\\",\\\"confidence\\\":0.0,\\\"klass\\\":82,\\\"name\\\":\\\"United States of America\\\",\\\"state\\\":\\\"Florida\\\"}],\\\"text\\\":\\\"K643ET10\\\",\\\"warpedBox\\\":[61.73531,819.796,145.57,819.796,145.57,881.916,61.73531,881.916]}]}\";\n        if (jsonString == null) { // No plate\n            return plates;\n        }\n\n        try {\n            final JSONObject jObject = new JSONObject(jsonString);\n            if (jObject.has(\"plates\")) {\n                final JSONArray jPlates = jObject.getJSONArray(\"plates\");\n                for (int i = 0; i < jPlates.length(); ++i) {\n                    final JSONObject jPlate = jPlates.getJSONObject(i);\n\n                    // The plate itself (backward-compatible with 2.0.0)\n                    final Plate plate = new Plate();\n                    plate.mWarpedBox = new float[8];\n                    if (jPlate.has(\"text\")) { // Starting 3.2 it's possible to have cars without plates when enabled\n                        final JSONArray jConfidences = jPlate.getJSONArray(\"confidences\");\n                        final JSONArray jWarpedBox = jPlate.getJSONArray(\"warpedBox\");\n                        plate.mNumber = jPlate.getString(\"text\");\n                        for (int j = 0; j < 8; ++j) {\n                            plate.mWarpedBox[j] = (float) jWarpedBox.getDouble(j);\n                        }\n                        plate.mRecognitionConfidence = (float) jConfidences.getDouble(0);\n                        plate.mDetectionConfidence = (float) jConfidences.getDouble(1);\n                    }\n                    else {\n                        plate.mNumber = \"\";\n                        plate.mRecognitionConfidence = 0.f;\n                        plate.mDetectionConfidence = 0.f;\n                    }\n\n                    if (jPlate.has(\"country\")) {\n                        plate.mCountries = new LinkedList<>();\n                        final JSONArray jCountries = jPlate.getJSONArray(\"country\");\n                        for (int k = 0; k < jCountries.length(); ++k) {\n                            final JSONObject jCountry = jCountries.getJSONObject(k);\n                            final Country country = new Country();\n                            country.mKlass = jCountry.getInt(\"klass\");\n                            country.mConfidence = (float) jCountry.getDouble(\"confidence\");\n                            country.mCode = jCountry.getString(\"code\");\n                            country.mName = jCountry.getString(\"name\");\n                            if (jCountry.has(\"state\")) { // optional\n                                country.mState = jCountry.getString(\"state\");\n                            }\n                            if (jCountry.has(\"other\")) { // optional\n                                country.mOther = jCountry.getString(\"other\");\n                            }\n\n                            plate.mCountries.add(country);\n                        }\n                    }\n\n                    // Car (Added in 3.0.0)\n                    if (jPlate.has(\"car\")) {\n                        final JSONObject jCar = jPlate.getJSONObject(\"car\");\n                        final JSONArray jCarWarpedBox = jCar.getJSONArray(\"warpedBox\");\n                        plate.mCar = new Car();\n                        plate.mCar.mConfidence = (float) jCar.getDouble(\"confidence\");\n                        plate.mCar.mWarpedBox = new float[8];\n                        for (int j = 0; j < 8; ++j) {\n                            plate.mCar.mWarpedBox[j] = (float) jCarWarpedBox.getDouble(j);\n                        }\n\n                        if (jCar.has(\"color\")) {\n                            plate.mCar.mColors = new LinkedList<>();\n                            final JSONArray jColors = jCar.getJSONArray(\"color\");\n                            for (int k = 0; k < jColors.length(); ++k) {\n                                final JSONObject jColor = jColors.getJSONObject(k);\n                                final Car.Attribute color = new Car.Attribute();\n                                color.mKlass = jColor.getInt(\"klass\");\n                                color.mConfidence = (float) jColor.getDouble(\"confidence\");\n                                color.mName = jColor.getString(\"name\"); // Name in English\n\n                                plate.mCar.mColors.add(color);\n                            }\n                        }\n\n                        if (jCar.has(\"makeModelYear\")) {\n                            plate.mCar.mMakesModelsYears = new LinkedList<>();\n                            final JSONArray jMMYs = jCar.getJSONArray(\"makeModelYear\");\n                            for (int k = 0; k < jMMYs.length(); ++k) {\n                                final JSONObject jMMY = jMMYs.getJSONObject(k);\n                                final Car.MakeModelYear mmy = new Car.MakeModelYear();\n                                mmy.mKlass = jMMY.getInt(\"klass\");\n                                mmy.mConfidence = (float) jMMY.getDouble(\"confidence\");\n                                mmy.mMake = jMMY.getString(\"make\");\n                                mmy.mModel = jMMY.getString(\"model\");\n                                mmy.mYear = jMMY.get(\"year\").toString(); // Maybe Integer or String or whatever\n\n                                plate.mCar.mMakesModelsYears.add(mmy);\n                            }\n                        }\n\n                        if (jCar.has(\"bodyStyle\")) {\n                            plate.mCar.mBodyStyles = new LinkedList<>();\n                            final JSONArray jBodyStyles = jCar.getJSONArray(\"bodyStyle\");\n                            for (int k = 0; k < jBodyStyles.length(); ++k) {\n                                final JSONObject jBodyStyle = jBodyStyles.getJSONObject(k);\n                                final Car.Attribute bodyStyle = new Car.Attribute();\n                                bodyStyle.mKlass = jBodyStyle.getInt(\"klass\");\n                                bodyStyle.mConfidence = (float) jBodyStyle.getDouble(\"confidence\");\n                                bodyStyle.mName = jBodyStyle.getString(\"name\"); // Name in English\n\n                                plate.mCar.mBodyStyles.add(bodyStyle);\n                            }\n                        }\n                    }\n\n                    plates.add(plate);\n                }\n            }\n        }\n        catch (JSONException e) {\n            e.printStackTrace();\n            Log.e(TAG, e.toString());\n        }\n        return plates;\n    }\n\n    public static <K, V> V getOrDefault(@NonNull Map<K, V> map, K key, V defaultValue) {\n        V v;\n        return (((v = map.get(key)) != null) || map.containsKey(key))\n                ? v\n                : defaultValue;\n    }\n\n    /**\n     * Checks if the returned result is success. An assertion will be raised if it's not the case.\n     * In production you should catch the exception and perform the appropriate action.\n     * @param result The result to check\n     * @return The same result\n     */\n    static public final AlprResult assertIsOk(final AlprResult result) {\n        if (!result.isOK()) {\n            throw new AssertionError(\"Operation failed: \" + result.phrase());\n        }\n        return result;\n    }\n\n    /**\n     * Converts the result to String.\n     * @param result\n     * @return\n     */\n    static public final String resultToString(final AlprResult result) {\n        return \"code: \" + result.code() + \", phrase: \" + result.phrase() + \", numPlates: \" + result.numPlates() + \", json: \" + result.json();\n    }\n\n    /**\n     *\n     * @param fileName\n     * @return Must close the returned object\n     */\n    static public FileChannel readFileFromAssets(final AssetManager assets, final String fileName) {\n        FileInputStream inputStream = null;\n        try {\n            AssetFileDescriptor fileDescriptor = assets.openFd(fileName);\n            inputStream = new FileInputStream(fileDescriptor.getFileDescriptor());\n            return inputStream.getChannel();\n            // To return DirectByteBuffer: fileChannel.map(FileChannel.MapMode.READ_ONLY, fileDescriptor.getStartOffset(), fileDescriptor.getDeclaredLength());\n        } catch (IOException e) {\n            e.printStackTrace();\n            Log.e(TAG, e.toString());\n            return null;\n        }\n    }\n}"
  },
  {
    "path": "demo/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/container\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"#000\"\n    tools:context=\"org.buyun.alpr.common.AlprActivity\" />\n"
  },
  {
    "path": "demo/src/main/res/layout/fragment_camera.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    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <org.buyun.alpr.common.AlprGLSurfaceView\n        android:id=\"@+id/glSurfaceView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentRight=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        android:layout_marginStart=\"0dp\"\n        android:layout_marginTop=\"0dp\"\n        android:layout_marginBottom=\"0dp\"/>\n\n    <org.buyun.alpr.common.AlprPlateView\n        android:id=\"@+id/plateView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentRight=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        android:layout_marginStart=\"-1dp\"\n        android:layout_marginTop=\"0dp\"\n        android:layout_marginBottom=\"0dp\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "demo/src/main/res/layout-land/fragment_camera.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    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <org.buyun.alpr.common.AlprGLSurfaceView\n        android:id=\"@+id/glSurfaceView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentRight=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        android:layout_marginStart=\"0dp\"\n        android:layout_marginTop=\"0dp\"\n        android:layout_marginBottom=\"0dp\" />\n\n    <org.buyun.alpr.common.AlprPlateView\n        android:id=\"@+id/plateView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_alignParentStart=\"true\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_alignParentTop=\"true\"\n        android:layout_alignParentBottom=\"true\"\n        android:layout_alignParentLeft=\"true\"\n        android:layout_alignParentRight=\"true\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        android:layout_marginStart=\"0dp\"\n        android:layout_marginTop=\"0dp\"\n        android:layout_marginBottom=\"0dp\" />\n\n</RelativeLayout>\n"
  },
  {
    "path": "demo/src/main/res/values/base-strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<resources>\n    <string name=\"app_name\">Alpr Demo</string>\n    <string name=\"intro_message\">\n        <![CDATA[\n        \n            \n            This sample demonstrates how to use the ALPR/ANPR SDK in sequential mode\n            to recognize license plates on video stream\n        \n        ]]>\n    </string>\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"control_background\">#cc4285f4</color>\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"picture\">Picture</string>\n    <string name=\"description_info\">Info</string>\n    <string name=\"request_permission\">This sample needs camera permission.</string>\n    <string name=\"camera_error\">This device doesn\\'t support Camera2 API.</string>\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"MaterialTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"android:windowFullscreen\">true</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values/template-dimens.xml",
    "content": "<resources>\n\n    <dimen name=\"margin_tiny\">4dp</dimen>\n    <dimen name=\"margin_small\">8dp</dimen>\n    <dimen name=\"margin_medium\">16dp</dimen>\n    <dimen name=\"margin_large\">32dp</dimen>\n    <dimen name=\"margin_huge\">64dp</dimen>\n\n    <dimen name=\"horizontal_page_margin\">@dimen/margin_medium</dimen>\n    <dimen name=\"vertical_page_margin\">@dimen/margin_medium</dimen>\n\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values/template-styles.xml",
    "content": "<resources>\n\n    <!-- Activity themes -->\n\n    <style name=\"Theme.Base\" parent=\"android:Theme.Light\" />\n\n    <style name=\"Theme.Sample\" parent=\"Theme.Base\" />\n\n    <style name=\"AppTheme\" parent=\"Theme.Sample\" />\n    <!-- Widget styling -->\n\n    <style name=\"Widget\" />\n\n    <style name=\"Widget.SampleMessage\">\n        <item name=\"android:textAppearance\">?android:textAppearanceMedium</item>\n        <item name=\"android:lineSpacingMultiplier\">1.1</item>\n    </style>\n\n    <style name=\"Widget.SampleMessageTile\">\n        <item name=\"android:background\">@drawable/tile</item>\n        <item name=\"android:shadowColor\">#7F000000</item>\n        <item name=\"android:shadowDy\">-3.5</item>\n        <item name=\"android:shadowRadius\">2</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values-sw600dp/template-dimens.xml",
    "content": "<resources>\n\n    <dimen name=\"horizontal_page_margin\">@dimen/margin_huge</dimen>\n    <dimen name=\"vertical_page_margin\">@dimen/margin_medium</dimen>\n\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values-sw600dp/template-styles.xml",
    "content": "<resources>\n\n    <style name=\"Widget.SampleMessage\">\n        <item name=\"android:textAppearance\">?android:textAppearanceLarge</item>\n        <item name=\"android:lineSpacingMultiplier\">1.2</item>\n        <item name=\"android:shadowDy\">-6.5</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values-v11/template-styles.xml",
    "content": "<resources>\n\n    <!-- Activity themes -->\n    <style name=\"Theme.Base\" parent=\"android:Theme.Holo.Light\" />\n\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values-v21/base-colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n</resources>\n"
  },
  {
    "path": "demo/src/main/res/values-v21/base-template-styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n    <!-- Activity themes -->\n    <style name=\"Theme.Base\" parent=\"android:Theme.Material.Light\">\n    </style>\n\n</resources>\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.5-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\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# 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# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Automatically convert third-party libraries to use AndroidX\nandroid.enableJetifier=true\n\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\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#      https://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\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\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\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\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='\"-Xmx64m\" \"-Xms64m\"'\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\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\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\" -a \"$nonstop\" = \"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 or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    \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=`expr $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# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@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\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\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=\"-Xmx64m\" \"-Xms64m\"\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 Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_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=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\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 ':sdk', ':demo'\nrootProject.name='AlprDemo'\n"
  }
]