com.minio.io.alice.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.minio.io.alice.MainActivity.java

Source

/*
 * Copyright (c) 2017 Minio, Inc. <https://www.minio.io>
 *
 * This file is part of Alice.
 *
 * Alice 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.minio.io.alice;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.SeekBar;
import android.widget.Toast;

import com.google.android.gms.vision.MultiProcessor;
import com.google.android.gms.vision.Tracker;
import com.google.android.gms.vision.face.Face;

import net.hockeyapp.android.CrashManager;
import net.hockeyapp.android.UpdateManager;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;

public class MainActivity extends Activity implements PreviewCallback {

    public static ClientWebSocket webSocket = null;
    public static Context context;
    public static String TAG = "__ALICE__";

    private static final int REQUEST_VIDEO_PERMISSIONS = 1;
    private boolean hasVideoPermission = false;
    private boolean hasLocationPermission = false;

    boolean mServiceBound = false;
    boolean serverThreadStarted = false;
    boolean serverThreadRunning = false;
    private static LinkedList<XrayResult> mServerReplyQueue;
    private static final int MAX_BUFFER = 100;

    public static LocationTracker locationTracker;

    private static final String[] VIDEO_PERMISSIONS = { Manifest.permission.CAMERA,
            Manifest.permission.RECORD_AUDIO, Manifest.permission.ACCESS_FINE_LOCATION, };

    GestureDetector gestureDetector;

    private CameraSourcePreview mPreview;
    private GraphicOverlay mGraphicOverlay;

    private static int mCameraId = CameraSource.CAMERA_FACING_FRONT;

    private CameraDeviceManager cameraManager;

    Thread frameHandlerThread;
    FrameHandler frameHandler;
    ServerHandler serverhandler;
    private ServerResponseHandler serverResponseHandler;
    private Thread serverResponseThread;

    public static boolean isAliceAwake = false;
    public static long prevFaceDetectionAt = 0;
    private long currentTime;
    private static int ELAPSED_DURATION = 60000; // 1 minute

    public MainActivity() {
        gestureDetector = new GestureDetector(context, new GestureListener());
        if (XDebug.LOG)
            Log.i(MainActivity.TAG, "Instantiated new " + this.getClass());
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        return gestureDetector.onTouchEvent(e);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        context = this;
        if (webSocket == null) {
            webSocket = new ClientWebSocket();
            Log.i(MainActivity.TAG, "About to connect to WS");
            webSocket.connect(context);
        }

        // Init media writers and location,sensor trackers
        serverhandler = new ServerHandler(context);

        // Spawn thread for handler for server response to Alice
        serverResponseHandler = new ServerResponseHandler();
        serverResponseThread = new Thread(serverResponseHandler);
        mServerReplyQueue = new LinkedList<XrayResult>();

        //Spawn thread for frame handler
        initFrameHandler();

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.activity_main);

        mPreview = (CameraSourcePreview) findViewById(R.id.ZoomCameraView);
        mPreview.setZoomControl((SeekBar) findViewById(R.id.CameraZoomControls));
        mGraphicOverlay = (GraphicOverlay) findViewById(R.id.faceOverlay);

        if (cameraManager == null) {
            cameraManager = new CameraDeviceManager(context, new GraphicFaceTrackerFactory(), frameHandler);
        }

        // Check for the camera permission before accessing the camera.  If the
        // permission is not granted yet, request permission.
        if (!checkSelfPermission(VIDEO_PERMISSIONS))
            cameraManager.createCameraSource(mCameraId);
        else
            requestVideoPermission();

        // Check for updates on hockey.
        checkForUpdates();
    }

    protected ServerHandler getServerHandler() {
        return this.serverhandler;
    }

    protected FrameHandler getFrameHandler() {
        return this.frameHandler;
    }

    protected Thread getFrameHandlerThread() {
        return this.frameHandlerThread;
    }

    @Override
    public void onPause() {
        super.onPause();

        serverThreadRunning = false;

        cameraManager.pauseCameraResource();
        mPreview.stop();
        serverhandler.stop();
        unregisterManagers();
    }

    //Spawns a new Framehandler thread
    private void initFrameHandler() {
        if (frameHandlerThread == null) {
            frameHandler = new FrameHandler(context);
            frameHandlerThread = new Thread(frameHandler);
        }
    }

    @Override
    public void onResume() {

        super.onResume();
        if (webSocket == null) {
            webSocket = new ClientWebSocket();
            webSocket.connect(context);
        }
        serverThreadRunning = true;
        cameraManager.startCameraSource();
        serverhandler.start();
        initFrameHandler();

        if (!serverThreadStarted) {
            serverThreadStarted = true;
            serverResponseThread.start();
        }

        checkForCrashes();
    }

    public void onDestroy() {

        super.onDestroy();

        frameHandler.stopRecording();
        frameHandlerThread = null;
        serverhandler.stop();
        serverResponseThread = null;
        serverThreadRunning = false;

        cameraManager.releaseCameraResource();
        unregisterManagers();
    }

    // Use Hockey Framework to collect crash reports on clients.
    private void checkForCrashes() {
        CrashManager.register(this);
    }

    // Hockey App Distribution
    private void checkForUpdates() {
        // Remove this for store builds!
        // UpdateManager.register(this);

    }

    private void unregisterManagers() {
        UpdateManager.unregister();
    }

    // Private class to handle touch events on camera.
    private class GestureListener extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onDown(MotionEvent e) {
            return false;
        }

        // event when double tap occurs
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            float x = e.getX();
            float y = e.getY();
            if (XDebug.LOG)
                Log.d(MainActivity.TAG, "Tapped at: (" + x + "," + y + ")");
            swapCamera();
            return true;
        }

        @Override
        public void onLongPress(MotionEvent event) {
            // triggers after onDown only for long press
            if (XDebug.LOG)
                Log.i(MainActivity.TAG, "Long Press");

            super.onLongPress(event);

        }
    }

    // Upon double tap, swap front  and back cameras
    public void swapCamera() {

        mCameraId = (mCameraId == CameraSource.CAMERA_FACING_FRONT) ? CameraSource.CAMERA_FACING_BACK
                : CameraSource.CAMERA_FACING_FRONT;
        cameraManager.swapCameraSource();
    }

    /**
     * Factory for creating a face tracker to be associated with a new face.  The multiprocessor
     * uses this factory to create face trackers as needed -- one for each individual.
     */
    private class GraphicFaceTrackerFactory implements MultiProcessor.Factory<Face> {
        @Override
        public Tracker<Face> create(Face face) {
            return new GraphicFaceTracker(mGraphicOverlay);
        }
    }

    // Callback for CameraDeviceManager to start preview
    public void startPreview(CameraSource cameraSource) throws IOException {
        mPreview.start(cameraSource, mGraphicOverlay);
    }

    private class ServerResponseHandler implements Runnable {

        ServerResponseTask stask = null;

        public ServerResponseHandler() {
        }

        @Override
        public void run() {
            while (serverThreadRunning) {
                XrayResult serverReply = dequeueServerReply();
                manageAliceDisplay();
                if (serverReply != null) {
                    stask = new ServerResponseTask(serverReply, mGraphicOverlay, mPreview);
                    stask.execute();
                }
            }
        }
    }

    // Enqueue server messages.
    public static void enqueueServerReply(XrayResult serverReply) {
        synchronized (mServerReplyQueue) {
            if (mServerReplyQueue.size() == MAX_BUFFER) {
                mServerReplyQueue.poll();
            }
            mServerReplyQueue.add(serverReply);
        }
    }

    //Poll mServerReplyQueue and process messages
    public XrayResult dequeueServerReply() {
        synchronized (mServerReplyQueue) {
            return mServerReplyQueue.poll();
        }
    }

    private void manageAliceDisplay() {
        currentTime = SystemClock.elapsedRealtime();
        long timeElapsed = currentTime - prevFaceDetectionAt;
        if (isAliceAwake && (timeElapsed > ELAPSED_DURATION)) {
            isAliceAwake = false;
        }

        //Wake up Alice if currently invisible
        if (isAliceAwake && !mPreview.isShown())
            toggleDisplay(mPreview, View.VISIBLE);

        //Blank display if Alice has not detected any face in ELAPSED_DURATION
        if (!isAliceAwake && mPreview.isShown())
            toggleDisplay(mPreview, View.INVISIBLE);
    }

    private void toggleDisplay(View view, int visibility) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                view.setVisibility(visibility);
            }
        });
    }
    // Get Video Permissions

    /**
     * Gets whether you should show UI with rationale for requesting permissions.
     *
     * @param permissions The permissions your app wants to request.
     * @return Whether you can show permission rationale UI.
     */
    private boolean shouldShowRequestPermissionRationale(String[] permissions) {
        boolean show = false;
        for (String permission : permissions) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
                show = true;
            }
        }
        return show;
    }

    private boolean checkSelfPermission(String[] permissions) {
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED)
                return true;
        }
        return false;
    }

    private void requestVideoPermission() {

        if (shouldShowRequestPermissionRationale(VIDEO_PERMISSIONS)) {
            // Show an explanation to the user *asynchronously* -- don't block
            // this thread waiting for the user's response! After the user
            // sees the explanation, try again to request the permission.
            ActivityCompat.requestPermissions(this, VIDEO_PERMISSIONS, REQUEST_VIDEO_PERMISSIONS);

            //showDialog(VIDEO_PERMISSIONS);
        } else {
            // No explanation needed, we can request the permission.
            ActivityCompat.requestPermissions(this, VIDEO_PERMISSIONS, REQUEST_VIDEO_PERMISSIONS);

            // REQUEST_VIDEO_PERMISSIONS is an
            // app-defined int constant. The callback method gets the
            // result of the request.
        }

    }

    // Find permissions that were not granted and return as an ArrayList
    private ArrayList<String> getPendingPermissions(int[] grantResults, String permissions[]) {
        HashMap<String, Integer> perms = new HashMap();
        ArrayList<String> pendingPermissions = new ArrayList<String>();
        for (int i = 0; i < permissions.length; i++) {
            perms.put(permissions[i], grantResults[i]);
            if (permissions[i] == Manifest.permission.ACCESS_FINE_LOCATION
                    && grantResults[i] == PackageManager.PERMISSION_GRANTED)
                this.hasLocationPermission = true;
            if (permissions[i] == Manifest.permission.CAMERA
                    && grantResults[i] == PackageManager.PERMISSION_GRANTED)
                this.hasVideoPermission = true;
        }

        for (int i = 0; i < VIDEO_PERMISSIONS.length; i++) {
            if (grantResults.length == VIDEO_PERMISSIONS.length) {
                if (perms.get(VIDEO_PERMISSIONS[i]) != PackageManager.PERMISSION_GRANTED) {
                    pendingPermissions.add(VIDEO_PERMISSIONS[i]);
                }
            } else {
                pendingPermissions.add(VIDEO_PERMISSIONS[i]);
            }
        }
        return pendingPermissions;
    }

    // Callback with the request from calling requestPermissions(...)
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
            @NonNull int[] grantResults) {
        // Make sure it's our original REQUEST_VIDEO_PERMISSIONS request
        if (requestCode == REQUEST_VIDEO_PERMISSIONS) {
            ArrayList<String> permissionsNeededYet = getPendingPermissions(grantResults, permissions);
            if (permissionsNeededYet.size() == 0) {
                //all permissions granted - allow camera access
                cameraManager.createCameraSource(mCameraId);
                return;

            } else {
                // showRationale = false if user clicks Never Ask Again, otherwise true
                boolean showRationale = shouldShowRequestPermissionRationale(
                        permissionsNeededYet.toArray(new String[0]));

                if (!showRationale) {
                    Toast.makeText(this,
                            "Video permission denied.Enable camera and location preferences on the App settings",
                            Toast.LENGTH_SHORT).show();
                    finish();
                } else {
                    Toast.makeText(this,
                            "Alice needs camera,microphone and location enabled to start tracking.Alice will now exit.",
                            Toast.LENGTH_SHORT).show();
                    startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                            Uri.parse("package:" + BuildConfig.APPLICATION_ID)));

                    finish();
                }
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    // show dialog to request for permissions
    void showDialog(final String permissions[]) {
        final Activity thisActivity = this;
        View.OnClickListener listener = new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ActivityCompat.requestPermissions(thisActivity, permissions, REQUEST_VIDEO_PERMISSIONS);
            }
        };

        Snackbar.make(mGraphicOverlay, R.string.permission_camera_rationale, Snackbar.LENGTH_INDEFINITE)
                .setAction(R.string.ok, listener).show();
    }
}