com.poinsart.votar.VotarMain.java Source code

Java tutorial

Introduction

Here is the source code for com.poinsart.votar.VotarMain.java

Source

/*
VotAR : Vote with Augmented reality
Copyright (C) 2013 Stephane Poinsart <s@poinsart.com>
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.
    
You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.poinsart.votar;

import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.concurrent.CountDownLatch;
import java.lang.System;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;

import org.json.JSONArray;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.media.ExifInterface;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;

/**
 * An example full-screen activity that shows and hides the system UI (i.e.
 * status bar and navigation/system bar) with user interaction.
 * 
 * @see SystemUiHider
 */
public class VotarMain extends Activity {
    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;

    private Uri cameraFileUri;
    public String lastPhotoFilePath = null;
    public String lastPointsJsonString = null;

    /** Create a file Uri for saving an image or video */

    /**
     * The instance of the {@link SystemUiHider} for this activity.
     */
    private static final int CAMERA_REQUEST = 1;
    private static final int GALLERY_REQUEST = 2;
    private ImageView imageView;
    private ProgressBar bar[] = { null, null, null, null };
    private TextView barLabel[] = { null, null, null, null };
    public TextView wifiLabel = null;
    private LinearLayout mainLayout, controlLayout, imageLayout;

    public CountDownLatch photoLock;
    public CountDownLatch pointsLock;

    private static final long TIME_DIVIDE = 100000;
    public long datatimestamp = Long.MIN_VALUE / TIME_DIVIDE;

    public BitmapFactory.Options opt = null;

    private VotarWebServer votarwebserver;
    public AssetManager assetMgr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        System.loadLibrary("VotAR");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        assetMgr = this.getAssets();

        imageView = (ImageView) findViewById(R.id.imageView);
        bar[0] = (ProgressBar) findViewById(R.id.bar_a);
        bar[1] = (ProgressBar) findViewById(R.id.bar_b);
        bar[2] = (ProgressBar) findViewById(R.id.bar_c);
        bar[3] = (ProgressBar) findViewById(R.id.bar_d);

        barLabel[0] = (TextView) findViewById(R.id.label_a);
        barLabel[1] = (TextView) findViewById(R.id.label_b);
        barLabel[2] = (TextView) findViewById(R.id.label_c);
        barLabel[3] = (TextView) findViewById(R.id.label_d);

        wifiLabel = (TextView) findViewById(R.id.label_wifi);

        mainLayout = ((LinearLayout) findViewById(R.id.mainLayout));
        controlLayout = ((LinearLayout) findViewById(R.id.controlLayout));
        imageLayout = ((LinearLayout) findViewById(R.id.imageLayout));

        adjustLayoutForOrientation(getResources().getConfiguration().orientation);

        findViewById(R.id.buttonCamera).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);

                cameraFileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image
                cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraFileUri); // set the image file name

                startActivityForResult(cameraIntent, CAMERA_REQUEST);
            }
        });
        findViewById(R.id.buttonGallery).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                startActivityForResult(Intent.createChooser(intent, "Select Picture"), GALLERY_REQUEST);
            }
        });
        votarwebserver = new VotarWebServer(51285, this);
        try {
            votarwebserver.start();
        } catch (IOException e) {
            Log.w("Votar MainAct", "The webserver could not be started, remote display wont be available");
        }
    }

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

    @Override
    public void onBackPressed() {
        AlertDialog.Builder alert = new AlertDialog.Builder(this);

        alert.setTitle(getString(R.string.quit_title));
        alert.setMessage(getString(R.string.quit_message));

        alert.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                finish();
            }
        });

        alert.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                //
            }
        });

        alert.show();
    }

    private void adjustLayoutForOrientation(int orientation) {
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            mainLayout.setOrientation(LinearLayout.HORIZONTAL);
            controlLayout
                    .setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT, 1));
        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            mainLayout.setOrientation(LinearLayout.VERTICAL);
            controlLayout
                    .setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1));
        }
    }

    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        adjustLayoutForOrientation(newConfig.orientation);
    }

    private static Uri getOutputMediaFileUri(int type) {
        return Uri.fromFile(getOutputMediaFile(type));
    }

    /** Create a File for saving an image or video */
    @SuppressLint("SimpleDateFormat")
    private static File getOutputMediaFile(int type) {
        // To be safe, you should check that the SDCard is mounted
        // using Environment.getExternalStorageState() before doing this.

        File mediaStorageDir = new File(
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "VotAR");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.

        // Create the storage directory if it does not exist
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.w("VotAR camera", "failed to create directory");
                return null;
            }
        }

        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
        } else if (type == MEDIA_TYPE_VIDEO) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4");
        } else {
            return null;
        }

        return mediaFile;
    }

    // mostly based on stackoverflow answer from ajma :
    // http://stackoverflow.com/questions/9573196/how-to-get-the-ip-of-the-wifi-hotspot-in-android
    public String getWifiIp() {
        Enumeration<NetworkInterface> en = null;
        try {
            en = NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e) {
            return null;
        }
        while (en.hasMoreElements()) {
            NetworkInterface intf = en.nextElement();
            if (intf.getName().contains("wlan")) {
                for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
                    InetAddress inetAddress = enumIpAddr.nextElement();
                    if (!inetAddress.isLoopbackAddress() && (inetAddress.getAddress().length == 4)) {
                        //Log.d("VotAR Main", inetAddress.getHostAddress());
                        return inetAddress.getHostAddress().toString();
                    }
                }
            }
        }
        return null;
    }

    public boolean updateWifiStatus() {
        String wifiIp = getWifiIp();
        if (wifiIp != null) {
            wifiLabel.setText("http://" + wifiIp + ":51285");
            return true;
        }
        wifiLabel.setText(getString(R.string.nowificon));
        return false;
    }

    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        registerReceiver(broadcastReceiver, intentFilter);
        updateWifiStatus();
    }

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

    private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                updateWifiStatus();
            }
        }
    };

    private class AnalyzeTask extends AsyncTask<Bitmap, Integer, Void> {
        private Mark mark[];
        private int prcount[];
        private Bitmap photo;

        private ProgressDialog mProgressDialog;

        // found in native code, check jni exports
        public native Mark[] nativeAnalyze(Bitmap b, int prcount[]);

        @Override
        protected Void doInBackground(Bitmap... photos) {
            // data is being updated and not ready for HTTP service, lock them
            photoLock = new CountDownLatch(1);
            pointsLock = new CountDownLatch(1);
            datatimestamp = System.nanoTime() / TIME_DIVIDE;

            opt.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(lastPhotoFilePath, opt);
            opt.inSampleSize = computeSampleSize(opt.outWidth, opt.outHeight);

            opt.inJustDecodeBounds = false;
            photo = BitmapFactory.decodeFile(lastPhotoFilePath, opt);
            if (photo == null)
                return null;

            // handle orientation changes in photos
            // inspired by MKJParekh answer on stackoverflow
            // http://stackoverflow.com/questions/12299963/facing-orientation-issue-with-camera-captured-image-on-android-phones
            ExifInterface exif;
            try {
                exif = new ExifInterface(lastPhotoFilePath);
            } catch (IOException e) {
                // invalid photo format ?
                return null;
            }
            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
            Log.e("orientation", "" + orientation);

            Matrix m = new Matrix();
            if ((orientation == ExifInterface.ORIENTATION_ROTATE_180)) {
                m.postRotate(180);
                Log.i("VotAR Main", "Orientation change: 70");
                photo = Bitmap.createBitmap(photo, 0, 0, photo.getWidth(), photo.getHeight(), m, true);
            } else if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
                m.postRotate(90);
                Log.i("VotAR Main", "Orientation change: 90");
                photo = Bitmap.createBitmap(photo, 0, 0, photo.getWidth(), photo.getHeight(), m, true);
            } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
                m.postRotate(270);
                Log.i("VotAR Main", "Orientation change: 270");
                photo = Bitmap.createBitmap(photo, 0, 0, photo.getWidth(), photo.getHeight(), m, true);
            }

            photoLock.countDown();
            prcount = new int[4];
            mark = nativeAnalyze(photo, prcount);
            return null;
        }

        private void showProgressDialog(int step) {
            switch (step) {
            case 0:
                mProgressDialog = new ProgressDialog(VotarMain.this);
                mProgressDialog.setTitle(getString(R.string.title_processing_1));
                //mProgressDialog.setProgressNumberFormat("%1d%%"); // not working on API level 9
                mProgressDialog.setMax(100);
                mProgressDialog.setProgress(0);
                mProgressDialog.setIndeterminate(false);
                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                mProgressDialog.setMessage(getString(R.string.processing_1));
                mProgressDialog.show();
                break;
            case 1:
                mProgressDialog.setProgress(20);
                float mpSize = (float) photo.getWidth() * photo.getHeight() / 1000000;
                mProgressDialog.setTitle(
                        getString(R.string.title_processing_2) + new DecimalFormat("#.#").format(mpSize) + "mp");
                mProgressDialog.setMessage(getString(R.string.processing_2));
                break;
            case 2:
                mProgressDialog.setProgress(40);
                mProgressDialog.setMessage(getString(R.string.processing_3));
                break;
            case 3:
                mProgressDialog.setProgress(90);
                mProgressDialog.setMessage(getString(R.string.processing_4));
                break;
            case 4:
                mProgressDialog.dismiss();
                break;
            }
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            showProgressDialog(progress[0]);
        }

        protected void onPreExecute() {
            showProgressDialog(0);
        }

        @Override
        protected void onPostExecute(Void unused) {
            // nativeAnalyze returns null if anything goes wrong, just silently ignore
            if (prcount != null && mark != null) {
                int max = 0;
                for (int i = 0; i < 4; i++) {
                    if (prcount[i] > max)
                        max = prcount[i];
                    barLabel[i].setText(new String(Character.toChars(97 + i)) + ": " + prcount[i]);
                }
                for (int i = 0; i < 4; i++) {
                    bar[i].setMax(max);
                    bar[i].setProgress(prcount[i]);
                }

                writeJsonPoints(mark);

                if (photo.getWidth() > imageLayout.getWidth() && photo.getHeight() > imageLayout.getHeight()) {
                    int maxWidth, maxHeight;
                    if (((float) photo.getWidth() / imageLayout.getWidth()) > ((float) photo.getHeight()
                            / imageLayout.getHeight())) {
                        // photo is large, limit to imageView width, preserve aspect ratio
                        maxWidth = imageLayout.getWidth();
                        maxHeight = photo.getHeight() * imageLayout.getWidth() / photo.getWidth();
                    } else {
                        // photo is high, limit to imageview height, preserve aspect ratio
                        maxHeight = imageLayout.getHeight();
                        maxWidth = photo.getWidth() * imageLayout.getHeight() / photo.getHeight();
                    }
                    Log.i("VotAR Main",
                            "Image resized for display: " + photo.getWidth() + "x" + photo.getHeight() + " -> "
                                    + maxWidth + "x" + maxHeight + " [in " + imageLayout.getWidth() + "x"
                                    + imageLayout.getHeight() + "]");
                    photo = Bitmap.createScaledBitmap(photo, maxWidth, maxHeight, true);
                }

                imageView.setImageBitmap(photo);
            }

            pointsLock.countDown();
            showProgressDialog(4);
        }
    }

    /*
     *  for now this just save the points into a json string,
     *  could use a file for more permanent storage
     */
    private void writeJsonPoints(Mark mark[]) {
        if (mark == null)
            return;
        JSONArray jsonmark = new JSONArray();
        for (int i = 0; i < mark.length; i++) {
            JSONArray jsoncurrentmark = new JSONArray(Arrays.asList(
                    new Integer[] { mark[i].x * opt.inSampleSize, mark[i].y * opt.inSampleSize, mark[i].pr }));
            jsonmark.put(jsoncurrentmark);
        }
        lastPointsJsonString = jsonmark.toString();
    }

    private int computeSampleSize(int w, int h) {
        int pixelCount = w * h;
        int allowedPixelCount;
        long maxMemory = Runtime.getRuntime().maxMemory();

        // large memory devices are allowed bigger images
        if (maxMemory < 48000000) {
            allowedPixelCount = 4000000;
        } else if (maxMemory < 96000000) {
            allowedPixelCount = 5500000;
        } else if (maxMemory < 128000000) {
            allowedPixelCount = 9000000;
        } else {
            allowedPixelCount = 16000000;
        }

        if (pixelCount <= allowedPixelCount) {
            return 1;
        }

        // if the image is too large for processing
        // we divide it's dimensions by 2,
        // so it's pixelCount goes down 4, should be enough
        return 2;
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Bitmap photo = null;
        opt = new BitmapFactory.Options();
        opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
        Uri uri = null;

        if (resultCode != RESULT_OK)
            return;

        if (requestCode == GALLERY_REQUEST && data != null && data.getData() != null) {
            uri = data.getData();
            if (uri == null)
                return;
            //User had pick an image.
            Cursor cursor = getContentResolver().query(uri,
                    new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
            cursor.moveToFirst();
            //Link to the image
            lastPhotoFilePath = cursor.getString(0);
            cursor.close();
        }
        if (requestCode == CAMERA_REQUEST) {
            uri = cameraFileUri;
            lastPhotoFilePath = uri.getPath();
        }

        if (lastPhotoFilePath == null)
            return;

        lastPointsJsonString = null;

        new AnalyzeTask().execute(photo);
    }
}