com.dmitrybrant.android.cardboardmpo.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.dmitrybrant.android.cardboardmpo.MainActivity.java

Source

/*
 * Copyright 2015 Dmitry Brant.
    
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dmitrybrant.android.cardboardmpo;

import com.google.vrtoolkit.cardboard.CardboardActivity;
import com.google.vrtoolkit.cardboard.CardboardView;
import com.google.vrtoolkit.cardboard.Eye;
import com.google.vrtoolkit.cardboard.HeadTransform;
import com.google.vrtoolkit.cardboard.Viewport;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.microedition.khronos.egl.EGLConfig;

public class MainActivity extends CardboardActivity implements CardboardView.StereoRenderer {
    private static final String TAG = "MainActivity";
    private static final int READ_PERMISSION_REQUEST = 50;

    private static final float Z_NEAR = 0.1f;
    private static final float Z_FAR = 5.0f;
    private static final float CAMERA_Z = 0.01f;

    private List<File> mpoFileList = new ArrayList<>();
    private int currentFileIndex = 0;

    private CardboardView cardboardView;
    private Vibrator vibrator;

    private ProgressBar progressLeft;
    private ProgressBar progressRight;
    private TextView statusLeft;
    private TextView statusRight;

    private TexturedRect rectLeftEye;
    private TexturedRect rectRightEye;

    private float[] camera;
    private float[] view;
    private float[] headView;
    private float[] modelView;
    private float[] modelViewProjection;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.common_ui);

        cardboardView = (CardboardView) findViewById(R.id.cardboard_view);
        cardboardView.setRestoreGLStateEnabled(false);
        cardboardView.setRenderer(this);
        setCardboardView(cardboardView);

        vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);

        progressLeft = (ProgressBar) findViewById(R.id.progress_left);
        progressRight = (ProgressBar) findViewById(R.id.progress_right);
        statusLeft = (TextView) findViewById(R.id.status_text_left);
        statusRight = (TextView) findViewById(R.id.status_text_right);

        setProgress(true);
        setStatus(true, getString(R.string.status_finding_files));

        camera = new float[16];
        view = new float[16];
        modelViewProjection = new float[16];
        modelView = new float[16];
        headView = new float[16];

        checkReadPermissionThenScanImages();
    }

    @Override
    public void onRendererShutdown() {
        Log.d(TAG, "onRendererShutdown");
    }

    @Override
    public void onSurfaceChanged(int width, int height) {
        Log.d(TAG, "onSurfaceChanged");
    }

    @Override
    public void onSurfaceCreated(EGLConfig config) {
        Log.i(TAG, "onSurfaceCreated");
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

        rectLeftEye = new TexturedRect();
        rectRightEye = new TexturedRect();
        checkGLError("Error after creating textures");
    }

    @Override
    public void onNewFrame(HeadTransform headTransform) {
        Matrix.setLookAtM(camera, 0, 0.0f, 0.0f, CAMERA_Z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
        headTransform.getHeadView(headView, 0);
    }

    @Override
    public void onDrawEye(Eye eye) {
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

        // Apply the eye transformation to the camera.
        Matrix.multiplyMM(view, 0, eye.getEyeView(), 0, camera, 0);

        // TODO: Do something with the head transform (e.g. pan the photo around)
        // For now, just reset the view matrix, so that the photo is in the center at all times.
        Matrix.setIdentityM(view, 0);

        float[] perspective = eye.getPerspective(Z_NEAR, Z_FAR);
        if (eye.getType() == 1) {
            Matrix.multiplyMM(modelView, 0, view, 0, rectLeftEye.getModelMatrix(), 0);
            Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);
            rectLeftEye.draw(modelViewProjection);
        } else {
            Matrix.multiplyMM(modelView, 0, view, 0, rectRightEye.getModelMatrix(), 0);
            Matrix.multiplyMM(modelViewProjection, 0, perspective, 0, modelView, 0);
            rectRightEye.draw(modelViewProjection);
        }
    }

    @Override
    public void onFinishFrame(Viewport viewport) {
    }

    @Override
    public void onCardboardTrigger() {
        Log.d(TAG, "onCardboardTrigger");

        currentFileIndex++;
        loadNextMpo();

        // Always give user feedback.
        vibrator.vibrate(50);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        switch (requestCode) {
        case READ_PERMISSION_REQUEST:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                findImages();
            } else {
                Toast.makeText(this, R.string.error_grant_permission, Toast.LENGTH_SHORT).show();
                finish();
            }
            break;
        default:
            break;
        }
    }

    private static void checkGLError(String label) {
        int error;
        if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e(TAG, label + ": glError " + error);
            throw new RuntimeException(label + ": glError " + error);
        }
    }

    private void setProgress(boolean enabled) {
        progressLeft.setVisibility(enabled ? View.VISIBLE : View.GONE);
        progressRight.setVisibility(enabled ? View.VISIBLE : View.GONE);
    }

    private void setStatus(boolean visible, String status) {
        statusLeft.setVisibility(visible ? View.VISIBLE : View.GONE);
        statusRight.setVisibility(visible ? View.VISIBLE : View.GONE);
        statusLeft.setText(status);
        statusRight.setText(status);
    }

    private void checkReadPermissionThenScanImages() {
        if ((ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            requestPermissions(new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, READ_PERMISSION_REQUEST);
        } else {
            findImages();
        }
    }

    private void findImages() {
        // kick off our task to find all MPOs, which will in turn kick off showing the first one.
        new MainFindTask().execute((Void) null);
    }

    private class MainFindTask extends MpoUtils.MpoFindTask {
        public MainFindTask() {
            super(MainActivity.this);
        }

        @Override
        protected void onPostExecute(List<File> results) {
            if (isActivityGone()) {
                return;
            }
            mpoFileList.clear();
            setProgress(false);
            if (results.size() == 0) {
                setStatus(true, getString(R.string.status_error_not_found));
                return;
            }
            mpoFileList.addAll(results);
            currentFileIndex = 0;
            setStatus(false, "");
            loadNextMpo();
        }
    }

    private class MainLoadTask extends MpoUtils.MpoLoadTask {
        private final int MAX_BMP_SIZE = 1024;
        private Bitmap bmpLeft;
        private Bitmap bmpRight;

        @Override
        protected List<Long> doInBackground(File... file) {
            List<Long> results = super.doInBackground(file);
            try {
                if (results.size() == 2) {
                    // this is the most common type of MPO, which is left-eye / right-eye
                    Log.d(TAG, "Found 2 JPGs, so loading 0/1...");
                    bmpLeft = MpoUtils.loadMpoBitmapFromFile(mpoFile, results.get(0), MAX_BMP_SIZE, MAX_BMP_SIZE);
                    bmpRight = MpoUtils.loadMpoBitmapFromFile(mpoFile, results.get(1), MAX_BMP_SIZE, MAX_BMP_SIZE);
                } else if (results.size() == 4) {
                    // I've seen this type in the wild, as well, which seems to be
                    // left-eye-hi-res / left-eye-lo-res / right-eye-hi-res / right-eye-lo-res
                    Log.d(TAG, "Found 4 JPGs, so loading 0/2...");
                    bmpLeft = MpoUtils.loadMpoBitmapFromFile(mpoFile, results.get(0), MAX_BMP_SIZE, MAX_BMP_SIZE);
                    bmpRight = MpoUtils.loadMpoBitmapFromFile(mpoFile, results.get(2), MAX_BMP_SIZE, MAX_BMP_SIZE);
                }
            } catch (IOException e) {
                Log.e(TAG, "Error while reading file.", e);
                cleanupBitmap(bmpLeft);
                cleanupBitmap(bmpRight);
                bmpLeft = null;
                bmpRight = null;
            }
            return results;
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
        }

        @Override
        protected void onPostExecute(List<Long> results) {
            if (isActivityGone()) {
                return;
            }
            if (bmpLeft == null || bmpRight == null) {
                setStatus(true, getString(R.string.status_error_load));
            } else {

                cardboardView.queueEvent(new Runnable() {
                    @Override
                    public void run() {
                        rectLeftEye.loadTexture(bmpLeft);
                        cleanupBitmap(bmpLeft);
                        bmpLeft = null;

                        rectRightEye.loadTexture(bmpRight);
                        cleanupBitmap(bmpRight);
                        bmpRight = null;
                    }
                });
                setStatus(false, "");
            }
            setProgress(false);
        }
    }

    /**
     * Load the next MPO file in our sequence. Wrap to the beginning if we're at the end.
     */
    private void loadNextMpo() {
        if (mpoFileList.size() == 0) {
            return;
        }
        if (currentFileIndex >= mpoFileList.size()) {
            currentFileIndex = 0;
        }
        setProgress(true);
        setStatus(true, String.format(getString(R.string.status_loading_file),
                mpoFileList.get(currentFileIndex).getName()));
        new MainLoadTask().execute(mpoFileList.get(currentFileIndex));
    }

    private void cleanupBitmap(Bitmap bmp) {
        if (bmp != null) {
            try {
                bmp.recycle();
            } catch (Exception e) {
                Log.e(TAG, "Error while cleaning up bitmap.", e);
            }
        }
    }

    private boolean isActivityGone() {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
            if (isDestroyed()) {
                return true;
            }
        }
        return isFinishing();
    }
}