package com.hdbarcode.hdbarcodereader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.Manifest.permission;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.PermissionChecker;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View.OnClickListener;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.WebView;


public class HDBarcodeReaderApp extends Activity implements OnClickListener, OnInitListener
{
    public enum LEDstates
    {
        UNKNOWN,
        NOT_AVAILABLE,
        OFF,
        ON
    }

    LEDstates       LEDstate            =   LEDstates.UNKNOWN;
    boolean			clickHDSecurLink    =   false;
    Camera	 		camera				=	null;
    int             attempts            =   0;
    int				canvasSizeX         =   0;
    int				canvasSizeY			=   0;
    int				previewSizeX        =   0;
    int				previewSizeY		=   0;
    int             programTimeout      =   120 * 1000; // 2 minutes
    int             bgRetCode           =   0;
    long            timeOfLastActivity  =   0;
    long            timeOfLastButton    =   0;
    String			decodedText         =   "";
    String          speakableText       =   "";
    ByteBuffer      directBuffer        =   null;
    byte []         bgData              =   null;
    String          fileToDisplay       =   null;
    File            pathToHDBarcode     =   null;
    File            pathToTempFiles     =   null;
    Bitmap			overlayPicture 		=	null;
    Bitmap 			scaledPicture		=	null;
    TextToSpeech	myTTS				=	null;
    ButtonView      buttonView          =	null;
    Activity        thisActivity        =   null;

    public native int	DecodeHDBarcode		(ByteBuffer directBuffer, int sizeX, int sizeY);
    static
    {
        System.loadLibrary("hdbarcode");
    }


    @Override protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        try
        {
            if (PermissionChecker.checkSelfPermission(getApplicationContext(), permission.CAMERA)                 != PermissionChecker.PERMISSION_GRANTED ||
                PermissionChecker.checkSelfPermission(getApplicationContext(), permission.WRITE_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED)
            {
                ActivityCompat.requestPermissions(this,new String[] {permission.CAMERA, permission.WRITE_EXTERNAL_STORAGE}, 1);
            }

            /* This logic makes it possible to preload an authorization from a file in the assets folder
            {
                int SizeX = 148;  // this must be a multiple of 4, and could be read directly from the file with a little more work
                int SizeY = 146;
                byte [] Buffer = new byte [SizeX * SizeY];
                ByteBuffer directBuffer;
                directBuffer = ByteBuffer.allocateDirect(SizeX * SizeY);
                LoadBitmap(Buffer, SizeX, SizeY, "AssetDemo.bmp");
                directBuffer.put(Buffer, 0, SizeX * SizeY);
                int RetCode = DecodeHDBarcode(directBuffer, SizeX, SizeY);
                Log.e("HDBarcode", "Preload returned " + RetCode);
            }
            */

            this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

            requestWindowFeature(Window.FEATURE_NO_TITLE);
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
            timeOfLastActivity = System.currentTimeMillis();
            myTTS = new TextToSpeech(this, this);

            pathToHDBarcode = new File(Environment.getExternalStorageDirectory(), "HD Barcode");
            if (pathToHDBarcode.exists())
            {
                pathToTempFiles = pathToHDBarcode;
                String fileList[] = pathToHDBarcode.list();
                for (String fileName : fileList)
                {
                    File fileToDelete = new File(pathToHDBarcode, fileName);
                    fileToDelete.delete();
                }
            }
            else
            {
                pathToTempFiles = getApplicationContext().getFilesDir();
                pathToHDBarcode = null;
            }
        }
        catch (Exception e)
        {
            Log.e("HDBarcode", "HDBarcode onCreate: " + e.getMessage());
        }
    }

    @Override protected void onResume()
    {
        super.onResume();
        if (System.currentTimeMillis() > timeOfLastActivity + programTimeout)
        {
            finish();
        }
        else
        {
            CreatePreviewWindow();
            timeOfLastActivity = System.currentTimeMillis();
        }
    }


    @Override protected void onPause()
    {
        super.onPause();
        StopCamera();
    }


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


    void CreatePreviewWindow()
    {
        try
        {
            setContentView(new Preview(this));
            buttonView = new ButtonView(this);
            addContentView(buttonView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        }
        catch (Exception e)
        {
            Log.e("HDBarcode", "HDBarcode CreatePreviewWindow: " + e.getMessage());
        }
    }


    void SetParameterButIgnoreError(Camera camera, String heading, String value)
    {
        try
        {
            Camera.Parameters parameters = camera.getParameters();
            parameters.set(heading, value);
            camera.setParameters(parameters);
        }
        catch (Exception e)
        {
			/* ignore it */
        }
    }


    void StartPreview(SurfaceHolder holder)
    {
        try
        {
    		/* Reset flag */
            clickHDSecurLink = false;

            /* Remember me */
            thisActivity = this;

    		/* Open camera and get parameters */
            camera = Camera.open();
            Camera.Parameters parameters = camera.getParameters();

			/* Set flash mode */
			switch (LEDstate)
            {
                case UNKNOWN:
                    LEDstate = LEDstates.NOT_AVAILABLE;
                    parameters = camera.getParameters();
                    List <String> flashModes = parameters.getSupportedFlashModes();
                    if (flashModes != null)
                    {
                        if (flashModes.contains(Camera.Parameters.FLASH_MODE_TORCH))
                        {
                            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                            LEDstate = LEDstates.ON;
                        }
                    }
                    break;

                case ON:
                    parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                    break;
            }

			/* Set focus mode */
            List <String> focusModes = parameters.getSupportedFocusModes();
            if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
            {
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            }

			/* Pick the resolution that matches the screen size */
            if (canvasSizeY == 552)
            {
				/* HTC Flyer is just weird */
                parameters.setPreviewSize(1024, 560);
            }
            else if (canvasSizeX == 1280 && canvasSizeY == 720)
            {
				/* Override S3 */
                parameters.setPreviewSize(1280, 720);
            }
            else
            {
                List<Size> sizeList = parameters.getSupportedPreviewSizes();
                for (int look = 0; look < sizeList.size(); look++)
                {
                    Size choice = sizeList.get(look);
                    Log.i("HDBarcode", "HDBarcode supported preview size " + choice.width + "x" + choice.height);
                    if (choice.width * choice.height > previewSizeX * previewSizeY)
                    {
                        try
                        {
                            previewSizeX = choice.width;
                            previewSizeY = choice.height;
                            parameters.setPreviewSize(choice.width, choice.height);
                        }
                        catch (Exception e)
                        {
                            Log.e("HDBarcode", "HDBarcode setPreviewSize: " + e.getMessage());
                        }
                    }
                }
            }

			/* Record setup */
            camera.setParameters(parameters);

			/* Get actual preview size */
            previewSizeX = parameters.getPreviewSize().width;
            previewSizeY = parameters.getPreviewSize().height;
            Log.i("HDBarcode", "HDBarcode using preview size " + previewSizeX + "x" + previewSizeY);

			/* Start preview */
            camera.setPreviewDisplay(holder);
            camera.startPreview();

			/* Start collecting */
			camera.setPreviewCallback(new PreviewCallback());
        }
        catch (Exception e)
        {
            Log.e("HDBarcode", "HDBarcode StartPreview: " + e.getMessage());
        }
    }


    void StopCamera()
    {
        Log.i("HDBarcode", "HDBarcode StopCamera() called");
        try
        {
        	/* Clean up camera */
            if (camera != null)
            {
                SetParameterButIgnoreError(camera, "flash-mode", Camera.Parameters.FLASH_MODE_OFF);
                camera.setPreviewCallback(null);
                camera.stopPreview();
                camera.release();
                camera = null;
                Log.i("HDBarcode", "HDBarcode camera is now null");
            }
        }
        catch (Exception e)
        {
            Log.e("HDBarcode", "HDBarcode StopCamera: " + e.getMessage());
        }
    }


    String unZip(byte data[], int skip, int fileSize)
    {
        try
        {
			/* Save the embedded file */
            File zipFile = new File(pathToTempFiles, "Embedded.zip");
            FileOutputStream outputStream = new FileOutputStream(zipFile);
            outputStream.write(data, skip, fileSize);
            outputStream.close();
            FileInputStream  inputStream  = new FileInputStream(zipFile);
            ZipInputStream   inputZip     = new ZipInputStream(inputStream);
            ZipEntry zipEntry = inputZip.getNextEntry();
            fileSize = 0;
            for (;;)
            {
                int bytesRead = inputZip.read(data, skip + fileSize, data.length - skip - fileSize);
                if (bytesRead <= 0)
                {
                    break;
                }
                fileSize += bytesRead;
            }
            String zipFilename = zipEntry.getName();
            File outputFile    = new File(pathToTempFiles, zipFilename);
            outputStream       = new FileOutputStream(outputFile);
            outputStream.write(data, skip, fileSize);
            outputStream.close();
            inputZip.closeEntry();
            inputZip.close();
            String textFromData = (new String(data)).toLowerCase();
            int prefix = textFromData.indexOf("<speak>");
            if (prefix > 0)
            {
                int suffix = textFromData.indexOf("</speak>");
                if (suffix > prefix)
                {
                    speakableText = textFromData.substring(prefix + 7, suffix);
                }
            }
            if (textFromData.contains("<html>"))
            {
                zipFilename = "*" + zipFilename;
            }
            return(zipFilename);
        }
        catch (Exception e)
        {
            Log.e("HDBarcode", "HDBarcode unZip: " + e.getMessage());
            return(null);
        }
    }


    String RemoveHtmlTags(String input)
    {
        String result = "";
        boolean skipping = false;
        for (int scan = 0; scan < input.length(); scan++)
        {
            switch (input.charAt(scan))
            {
                case '<':
                    skipping = true;
                    break;

                case '>':
                    skipping = false;
                    result += ' ';
                    break;

                default:
                    if (!skipping)
                    {
                        result += input.charAt(scan);
                    }
                    break;
            }
        }
        return(result);
    }


    private class BackgroundPostDecode implements Runnable
    {
        @Override
        public void run()
        {
            byte [] data = bgData;

            /* Get RetCode */
            int retCode = bgRetCode;

            Log.i("HDBarcode", "HDBarcode postDecodeTask running with bgRetCode = " + bgRetCode + " and decodedText = " + decodedText);

            /* See if there is a picture */
            boolean looksLikeJPG = false;
            boolean looksLikePNG = false;
            boolean looksLikeZIP = false;
            int xPercent = 0;
            int yPercent = 0;
            int tilde;
            String fileToBrowse = null;

            /* Look for magic ~ character */
            tilde = decodedText.indexOf('~');
            if (tilde >= 0)
            {
                /* Look in assets first, and look in folder second */
                String[] list;
                AssetManager manager = getAssets();
                InputStream input = null;
                for (int places = 0; places < 2; places++)
                {
                    try
                    {
                        if (places == 0)
                        {
                            list = manager.list("");
                        } else
                        {
                            list = pathToTempFiles.list();
                        }
                        for (String filename : list)
                        {
                            if (filename.substring(0, tilde + 1).contentEquals(decodedText.substring(0, tilde + 1)))
                            {
                                if (places == 0)
                                {
                                    input = manager.open(filename);
                                } else
                                {
                                    input = new FileInputStream(filename);
                                }
                                break;
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        input = null;
                    }
                    if (input != null)
                    {
                        break;
                    }
                }
                if (input != null)
                {
                    byte[] buffer = new byte[65536];

                    data = new byte[retCode - tilde - 1];
                    System.arraycopy((decodedText + "\0").getBytes(), tilde + 1, data, 0, retCode - tilde - 1);
                    decodedText = decodedText.substring(tilde + 1);

                    for (; ; )
                    {
                        int length;
                        try
                        {
                            length = input.read(buffer);
                        } catch (Exception e)
                        {
                            length = -1;
                        }
                        if (length < 0)
                        {
                            break;
                        }
                        byte[] both = new byte[data.length + buffer.length];
                        System.arraycopy(data, 0, both, 0, data.length);
                        System.arraycopy(buffer, 0, both, data.length, length);
                        data = both;
                    }
                    try
                    {
                        input.close();
                    } catch (Exception e)
                    {
                        /* do nothing */
                    }
                    retCode = data.length;
                }
            }

            int skip = decodedText.length() + 1;
            int fileSize = retCode - skip;
            if (fileSize > 0)
            {
                looksLikeJPG = ((data[skip] & 0xFF) == 0xFF) && ((data[skip + 1] & 0xFF) == 0xD8);
                looksLikePNG = ((data[skip] & 0xFF) == 0x89) && ((data[skip + 1] & 0xFF) == 0x50);
                looksLikeZIP = ((data[skip] & 0xFF) == 0x50) && ((data[skip + 1] & 0xFF) == 0x4B);
            }

            speakableText = "";
            if (looksLikeZIP)
            {
                fileToBrowse = unZip(data, skip, fileSize);
                if (fileToBrowse == null)
                {
                    decodedText = "Internal zip error";
                } else if (!fileToBrowse.startsWith("*"))
                {
                    decodedText = "<html><img src=\"" + fileToBrowse + "\"></html>";
                } else
                {
                    decodedText = "";
                    fileToBrowse = fileToBrowse.substring(1);
                }
            }
            else if (looksLikeJPG || looksLikePNG)
            {
                float mag;
                overlayPicture = BitmapFactory.decodeByteArray(data, skip, fileSize);
                if (decodedText.length() == 0)
                {
                    mag = canvasSizeY / (float) overlayPicture.getHeight();
                    if (mag * overlayPicture.getWidth() > canvasSizeX)
                    {
                        mag = canvasSizeX / (float) overlayPicture.getWidth();
                    }
                } else
                {
                    mag = (canvasSizeY / 2) / (float) overlayPicture.getHeight();
                    if (3 * mag * overlayPicture.getWidth() > canvasSizeX)
                    {
                        mag = (canvasSizeX / 3) / (float) overlayPicture.getWidth();
                    }
                }
                int xSize = (int) (overlayPicture.getWidth() * mag);
                int ySize = (int) (overlayPicture.getHeight() * mag);
                xPercent = (xSize * 100 + canvasSizeX / 2) / canvasSizeX;
                yPercent = (ySize * 100 + canvasSizeY / 2) / canvasSizeY;
                scaledPicture = Bitmap.createScaledBitmap(overlayPicture, xSize, ySize, false);
                try
                {
                    fileToBrowse = "Embedded.jpg";
                    File outputFile = new File(pathToTempFiles, fileToBrowse);
                    FileOutputStream fos = new FileOutputStream(outputFile);
                    scaledPicture.compress(Bitmap.CompressFormat.JPEG, 90, fos);
                    fos.close();
                } catch (Exception e)
                {
                    fileToBrowse = null;
                    decodedText = "JPEG error: " + e.getMessage();
                }
            }
            else if (fileSize > 0)
            {
                decodedText = "Unknown file format";
                fileToBrowse = null;
            }

            if (!looksLikeZIP)
            {
                speakableText = RemoveHtmlTags(decodedText);
                try
                {
                    String text = decodedText.replace("\n", "\n<br>\n");
                    int height = canvasSizeY / 50;
                    if (height < 20)
                    {
                        height = 20;
                    }
                    text = "<p style=\"font-size:" + height + "px\">" + text + "</p></body></html>";
                    if (fileToBrowse != null)
                    {
                        text = "<img src=\"file://" + pathToTempFiles + "/" + fileToBrowse + "\" style=\"float:right;width:" + xPercent + "%;height:" + yPercent + "%\">" + text;
                    }
                    text = "<html><body><meta charset=\"UTF-8\">" + text;
                    fileToBrowse = "Decoded.html";
                    File file = new File(pathToTempFiles, fileToBrowse);
                    FileOutputStream fos = new FileOutputStream(file);
                    byte[] byteList = new byte[text.length()];
                    for (int index = 0; index < text.length(); index++)
                    {
                        byteList[index] = (byte) ((int) text.charAt(index) & 0xFF);
                    }
                    fos.write(byteList);
                    fos.close();
                }
                catch (Exception e)
                {
                    Log.e("HDBarcode", "HDBarcode postDecodeTask: " + e.getMessage());
                }
            }

            /* Display on next image */
            fileToDisplay = fileToBrowse;

            /* Reset flag */
            bgData = null;

            Log.i("HDBarcode", "HDBarcode postDecodeTask stopping with fileToDisplay = " + fileToDisplay);
        }
    }

    class SpeakTask extends AsyncTask<String, Void, Long>
    {
        @Override protected Long doInBackground(String... params)
        {
            myTTS.speak(params[0], TextToSpeech.QUEUE_FLUSH, null);
            return (Long) (long) 0;
        }
    }


    class Preview extends SurfaceView implements SurfaceHolder.Callback
    {
        Preview(Context context)
        {
            super(context);
            getHolder().addCallback(this);
//          getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        public void surfaceCreated(SurfaceHolder holder)
        {
        	/* nothing to do */
        }

        public void surfaceDestroyed(SurfaceHolder holder)
        {
        	/* nothing to do */
        }

        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
        {
            canvasSizeX = width;
            canvasSizeY = height;
            StartPreview(holder);
        }
    }


    private class ButtonView extends View
    {
        Paint paint;

        public ButtonView(Context context)
        {
            super(context);
            paint = new Paint();
        }

        @Override public boolean onTouchEvent(MotionEvent event)
        {
            int     which;

            Log.i("HDBarcode", "HDBarcode: onTouchEvent recorded action " + event.getAction());

            /* Pass upstream if not on a button */
            float y = event.getRawY();
            if (y < 5 * canvasSizeY / 6)
            {
                return(false);
            }

            /* Ignore if not a push or within the last 200 ms */
            if (event.getAction() != MotionEvent.ACTION_DOWN || System.currentTimeMillis() - timeOfLastButton < 200)
            {
                Log.i("HDBarcode", "HDBarcode: ignoring onTouchEvent because it was too soon since the last button press");
                timeOfLastActivity = System.currentTimeMillis();
                return(true);
            }

            /* Reset timeout */
            timeOfLastButton   = System.currentTimeMillis();
            timeOfLastActivity = timeOfLastButton;

            which = 11 + (int) (event.getRawX() * 4 / canvasSizeX);
            if (camera == null)
            {
                which += 10;
            }
            Log.i("HDBarcode", "HDBarcode: which = " + which);

            switch (which)
            {
                case 11:
                    switch (LEDstate)
                    {
                        case ON:
                            SetParameterButIgnoreError(camera, "flash-mode", Camera.Parameters.FLASH_MODE_OFF);
                            LEDstate = LEDstates.OFF;
                            this.invalidate();
                            break;

                        case OFF:
                            SetParameterButIgnoreError(camera, "flash-mode", Camera.Parameters.FLASH_MODE_TORCH);
                            LEDstate = LEDstates.ON;
                            this.invalidate();
                            break;
                    }
                    break;

                case 12:
                    /* future use */
                    break;

                case 13:
                    clickHDSecurLink = true;
                    break;

                case 14:
                case 24:
                    StopCamera();
                    DecodeHDBarcode(null, 0, 0);  // forget temporary keys
                    finish();
                    break;

                case 21:
                    CreatePreviewWindow();
                    break;

                case 22:
                    if (decodedText.startsWith("http"))
                    {
                        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(decodedText));
                        startActivity(intent);
                        finish();
                    }
                    break;

                case 23:
                    new SpeakTask().execute(speakableText);
                    break;
            }

            return(true);
        }


        void DrawLabel(Canvas canvas, Paint paint, String label, int position, boolean highlight)
        {
            float		x1;
            float		y1;
            float		x2;
            float		y2;
            float		sum;
            float		widths	[]	=	 new float[label.length()];

            paint.getTextWidths(label, 0, label.length(), widths);
            sum = 0;
            for (int index = 0; index < label.length(); index++)
            {
                sum += widths[index];
            }

            x1 = canvasSizeX * (1 + 6 * position) / 25;
            y1 = 5 * canvasSizeY / 6;
            x2 = canvasSizeX * (6 + 6 * position) / 25;
            y2 = canvasSizeY;

            if (highlight)
            {
                paint.setColor(Color.GRAY);
            }
            else
            {
                paint.setColor(Color.DKGRAY);
            }
            paint.setStyle(Paint.Style.FILL);
            canvas.drawRect(x1, y1, x2, y2, paint);
            if (highlight)
            {
                paint.setColor(Color.BLUE);
            }
            else
            {
                paint.setColor(Color.GRAY);
            }
            canvas.drawText(label, x1 + (x2 - x1 - sum) / 2, (y1 + y2 + paint.getTextSize()) / 2, paint);
        }


        @Override protected void onDraw(Canvas canvas)
        {
		    /* Draw buttons base on camera */
            paint.setTextSize(canvasSizeY / 20);
            paint.setAntiAlias(true);
            if (camera == null)
            {
                DrawLabel(canvas, paint, "Scan again",    0, true);
                DrawLabel(canvas, paint, "Go to website", 1, decodedText.startsWith("http"));
                DrawLabel(canvas, paint, "Speak text",    2, true);
            }
            else
            {
                switch (LEDstate)
                {
                    case NOT_AVAILABLE:
                        DrawLabel(canvas, paint, "LED n/a", 0, false);
                        break;

                    case OFF:
                        DrawLabel(canvas, paint, "LED ON",  0, true);
                        break;

                    case ON:
                        DrawLabel(canvas, paint, "LED OFF", 0, true);
                        break;
                }
                DrawLabel(canvas, paint, "",             1, true);
                DrawLabel(canvas, paint, "HDSecurLink",          2, true);
            }
            DrawLabel(canvas, paint, "Exit V5.0", 3, true);
        }
    }


    private class PreviewCallback implements Camera.PreviewCallback
    {
        public void onPreviewFrame(byte[] data, Camera camera)
        {
//          Log.i("HDBarcode", "HDBarcode: onPreviewFrame");

            /* Check for display first */
            if (fileToDisplay != null)
            {
                StopCamera();
                WebView webView = new WebView(thisActivity);
                webView.getSettings().setBuiltInZoomControls(true);
                webView.getSettings().setSupportZoom(true);
                webView.clearCache(true);
                webView.loadUrl("file://" + pathToTempFiles + "/" + fileToDisplay);
                setContentView(buttonView);
                addContentView(webView, new LayoutParams(canvasSizeX, 5 * canvasSizeY / 6));
                fileToDisplay = null;
            }
            else if (bgData == null)
            {
                /* Schedule background task */
                bgData = data;
                BackgroundTryDecode bgTryTask = new BackgroundTryDecode();
                AsyncTask.execute(bgTryTask);
            }
        }
    }



    private class BackgroundTryDecode implements Runnable
    {
        @Override
        public void run()
        {
            boolean ok = false;
            try
            {
                directBuffer = ByteBuffer.allocateDirect(previewSizeX * previewSizeY);
                directBuffer.put(bgData, 0, previewSizeX * previewSizeY);
                ok = true;
            }
            catch (Exception e)
            {
                Log.e("HDBarcode", "HDBarcode directBuffer: " + e.getMessage());
            }
            if (!ok)
            {
                bgData = null;
                return;
            }

    		/* Check for timeout */
            if (System.currentTimeMillis() > timeOfLastActivity + programTimeout)
            {
                decodedText = "No barcode was actually read.  This message appears if nothing is read for " + programTimeout / 1000 + " seconds.  This prevents battery drain from the camera being left on inadvertently.";
                bgRetCode = decodedText.length() + 1;
                BackgroundPostDecode postDecodeTask = new BackgroundPostDecode();
                AsyncTask.execute(postDecodeTask);
                return;
            }

    		/* Log if applicable */
            if (!clickHDSecurLink && ++attempts < 100)
            {
                String filename = "X" + (attempts / 10) + "" + (attempts % 10) + ".bmp";
                SaveBitmap(bgData, previewSizeX, previewSizeY, filename);
                Log.i("HDBarcode", "HDBarcode: saved " + filename);
            }

			/* Decode */
			int retCode = 0;
            if (!clickHDSecurLink)
            {
                Log.i("HDBarcode", "HDBarcode: calling DecodeHDBarcode(" + previewSizeX + ", " + previewSizeY + ")");
                retCode = DecodeHDBarcode(directBuffer, previewSizeX, previewSizeY);
                Log.i("HDBarcode", "HDBarcode: back from DecodeHDBarcode with retCode = " + retCode);

                /* Enable the following code if you want your application to ignore public HD Barcodes and only read company private HD Barcodes
                if (retCode > 0)
                {
                    byte [] oneByte = new byte [1];
                    directBuffer.position(retCode);
                    directBuffer.get(oneByte, 0, 1);
                    if (oneByte[0] == 0)
                    {
                        retCode = 0;
                    }
                }
                */
                
            }

            /* Because clickHDSecurLink may have changed, check again */
            if (clickHDSecurLink)
            {
                retCode = DecodeHDBarcode(directBuffer, 0, 0);
                Log.i("HDBarcode", "HDBarcode: DecodeHDBarcode(0, 0) returned " + retCode);
            }

			/* Rename file based upon success */
            if (clickHDSecurLink)
            {
                clickHDSecurLink = false;
            }
            else if (pathToHDBarcode != null && attempts < 100)
            {
                String filename = "X" + (attempts / 10) + "" + (attempts % 10) + ".bmp";
                File file = new File(pathToHDBarcode, filename);
                if (retCode > 0)
                {
                    file.renameTo(new File(file.getAbsolutePath().replace('X', 'P')));
                }
                else
                {
                    file.renameTo(new File(file.getAbsolutePath().replace('X', 'F')));
                }
            }

			/* If failure, just reset bgData */
            if (retCode <= 0)
            {
                bgData = null;
                return;
            }

            /* Get result */
            directBuffer.rewind();
            directBuffer.get(bgData, 0, retCode);

   	        /* Build result */
            int Spot    = 0;
            decodedText = "";
            while (bgData[Spot] != 0)
            {
                decodedText = decodedText + (char) (bgData[Spot++] & 0xFF);
            }

			/* Handle it */
            bgRetCode = retCode;
            BackgroundPostDecode postDecodeTask = new BackgroundPostDecode();
            AsyncTask.execute(postDecodeTask);
        }
    }


    private void PutLongInBytes(byte Stuff [], int Index, int Value)
    {
        Stuff[Index + 0] = (byte) ((Value >>  0) & 0xFF);
        Stuff[Index + 1] = (byte) ((Value >>  8) & 0xFF);
        Stuff[Index + 2] = (byte) ((Value >> 16) & 0xFF);
        Stuff[Index + 3] = (byte) ((Value >> 24) & 0xFF);
    }


    private void SaveBitmap(byte[] Buffer, int SizeX, int SizeY, String Filename)
    {
        try
        {
            byte Header [] = new byte [54 + 1024];
            Header[0] = 66;
            Header[1] = 77;
            PutLongInBytes(Header,  0 * 4 + 2, 54 + 4 * 256 + SizeX * SizeY);
            PutLongInBytes(Header,  1 * 4 + 2, 0);
            PutLongInBytes(Header,  2 * 4 + 2, 54 + 4 * 256);
            PutLongInBytes(Header,  3 * 4 + 2, 40);
            PutLongInBytes(Header,  4 * 4 + 2, SizeX);
            PutLongInBytes(Header,  5 * 4 + 2, SizeY);
            PutLongInBytes(Header,  6 * 4 + 2, (8 << 16) + 1);
            PutLongInBytes(Header,  7 * 4 + 2, 0);
            PutLongInBytes(Header,  8 * 4 + 2, 0);
            PutLongInBytes(Header,  9 * 4 + 2, 11811);
            PutLongInBytes(Header, 10 * 4 + 2, 11811);
            PutLongInBytes(Header, 11 * 4 + 2, 0);
            PutLongInBytes(Header, 12 * 4 + 2, 0);
            for (int Index = 0; Index < 256; Index++)
            {
                switch (Index + 4)
                {
                    case 0:
        				/* Red */
                        Header[54 + 4 * Index + 0] = 0;
                        Header[54 + 4 * Index + 1] = 0;
                        Header[54 + 4 * Index + 2] = -1;
                        break;

                    case 1:
        				/* Green */
                        Header[54 + 4 * Index + 0] = 0;
                        Header[54 + 4 * Index + 1] = -1;
                        Header[54 + 4 * Index + 2] = 0;
                        break;

                    case 2:
        				/* Blue */
                        Header[54 + 4 * Index + 0] = -1;
                        Header[54 + 4 * Index + 1] = 0;
                        Header[54 + 4 * Index + 2] = 0;
                        break;

                    case 3:
        				/* Yellow */
                        Header[54 + 4 * Index + 0] = 0;
                        Header[54 + 4 * Index + 1] = -1;
                        Header[54 + 4 * Index + 2] = -1;
                        break;

                    default:
                        Header[54 + 4 * Index + 0] = (byte) Index;
                        Header[54 + 4 * Index + 1] = (byte) Index;
                        Header[54 + 4 * Index + 2] = (byte) Index;
                        break;
                }
            }

            File f = new File(pathToTempFiles, Filename);
            FileOutputStream fos = new FileOutputStream(f);
            fos.write(Header);
            for (int Y = 0; Y < SizeY; Y++)
            {
                fos.write(Buffer, (SizeY - 1 - Y) * SizeX, SizeX);
            }
            fos.close();
        }
        catch (Exception e)
        {
            Log.e("HDBarcode", "HDBarcode SaveBitmap: " + e.getMessage());
        }
    }


    private void LoadBitmap(byte[] Buffer, int SizeX, int SizeY, String Filename)
    {
        try
        {
            InputStream is = getAssets().open("AssetDemo.bmp");
            is.read(Buffer, 0, 54 + 1024);
            for (int Y = 0; Y < SizeY; Y++)
            {
                is.read(Buffer, (SizeY - 1 - Y) * SizeX, SizeX);
            }
            is.close();
        }
        catch (Exception e)
        {
            Log.e("HDBarcode", "HDBarcode LoadBitmap: " + e.getMessage());
        }
    }

    //setup TTS
    public void onInit(int initStatus)
    {
		/* ignore it */
    }


    @Override public void onClick(View arg0)
    {
		/* ignore it */
    }
}
