content: /*
   * Copyright 2016, The Android Open Source Project
   *
   * 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.example.androidthings.doorbell;
  
  import android.Manifest;
  import android.app.Activity;
  import android.content.pm.PackageManager;
  import android.media.Image;
  import android.media.ImageReader;
  import android.net.Uri;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.HandlerThread;
  import android.support.annotation.NonNull;
  import android.util.Log;
  import android.view.KeyEvent;
  
  import com.google.android.gms.tasks.OnFailureListener;
  import com.google.android.gms.tasks.OnSuccessListener;
  import com.google.android.things.contrib.driver.button.Button;
  import com.google.android.things.contrib.driver.button.ButtonInputDriver;
  import com.google.firebase.database.DatabaseReference;
  import com.google.firebase.database.FirebaseDatabase;
  import com.google.firebase.database.ServerValue;
  import com.google.firebase.storage.FirebaseStorage;
  import com.google.firebase.storage.StorageReference;
  import com.google.firebase.storage.UploadTask;
  
  import java.io.IOException;
  import java.nio.ByteBuffer;
  import java.util.Map;
  
  /**
   * Doorbell activity that capture a picture from an Android Things
   * camera on a button press and post it to Firebase and Google Cloud
   * Vision API.
   */
  public class DoorbellActivity extends Activity {
      private static final String TAG = DoorbellActivity.class.getSimpleName();
  
      private FirebaseDatabase mDatabase;
      private FirebaseStorage mStorage;
      private DoorbellCamera mCamera;
  
      /**
       * Driver for the doorbell button;
       */
      private ButtonInputDriver mButtonInputDriver;
  
      /**
       * A {@link Handler} for running Camera tasks in the background.
       */
      private Handler mCameraHandler;
  
      /**
       * An additional thread for running Camera tasks that shouldn't block the UI.
       */
      private HandlerThread mCameraThread;
  
      /**
       * A {@link Handler} for running Cloud tasks in the background.
       */
      private Handler mCloudHandler;
  
      /**
       * An additional thread for running Cloud tasks that shouldn't block the UI.
       */
      private HandlerThread mCloudThread;
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          Log.d(TAG, "Doorbell Activity created.");
  
          // We need permission to access the camera
          if (checkSelfPermission(Manifest.permission.CAMERA)
                  != PackageManager.PERMISSION_GRANTED) {
              // A problem occurred auto-granting the permission
              Log.e(TAG, "No permission");
              return;
          }
  
          mDatabase = FirebaseDatabase.getInstance();
          mStorage = FirebaseStorage.getInstance();
  
          // Creates new handlers and associated threads for camera and networking operations.
          mCameraThread = new HandlerThread("CameraBackground");
          mCameraThread.start();
          mCameraHandler = new Handler(mCameraThread.getLooper());
  
          mCloudThread = new HandlerThread("CloudThread");
          mCloudThread.start();
          mCloudHandler = new Handler(mCloudThread.getLooper());
  
          // Initialize the doorbell button driver
          initPIO();
  
          // Camera code is complicated, so we've shoved it all in this closet class for you.
          mCamera = DoorbellCamera.getInstance();
          mCamera.initializeCamera(this, mCameraHandler, mOnImageAvailableListener);
      }
  
      private void initPIO() {
          try {
              mButtonInputDriver = new ButtonInputDriver(
                      BoardDefaults.getGPIOForButton(),
                      Button.LogicState.PRESSED_WHEN_LOW,
                      KeyEvent.KEYCODE_ENTER);
              mButtonInputDriver.register();
          } catch (IOException e) {
              mButtonInputDriver = null;
              Log.w(TAG, "Could not open GPIO pins", e);
          }
      }
  
      @Override
      protected void onDestroy() {
          super.onDestroy();
          mCamera.shutDown();
  
          mCameraThread.quitSafely();
          mCloudThread.quitSafely();
          try {
              mButtonInputDriver.close();
          } catch (IOException e) {
              Log.e(TAG, "button driver error", e);
          }
      }
  
      @Override
      public boolean onKeyUp(int keyCode, KeyEvent event) {
          if (keyCode == KeyEvent.KEYCODE_ENTER) {
              // Doorbell rang!
              Log.d(TAG, "button pressed");
              mCamera.takePicture();
              return true;
          }
          return super.onKeyUp(keyCode, event);
      }
  
      /**
       * Listener for new camera images.
       */
      private ImageReader.OnImageAvailableListener mOnImageAvailableListener =
              new ImageReader.OnImageAvailableListener() {
          @Override
          public void onImageAvailable(ImageReader reader) {
              Image image = reader.acquireLatestImage();
              // get image bytes
              ByteBuffer imageBuf = image.getPlanes()[0].getBuffer();
              final byte[] imageBytes = new byte[imageBuf.remaining()];
              imageBuf.get(imageBytes);
              image.close();
  
              onPictureTaken(imageBytes);
          }
      };
  
      /**
       * Upload image data to Firebase as a doorbell event.
       */
      private void onPictureTaken(final byte[] imageBytes) {
          if (imageBytes != null) {
              final DatabaseReference log = mDatabase.getReference("logs").push();
              final StorageReference imageRef = mStorage.getReference().child(log.getKey());
  
              // upload image to storage
              UploadTask task = imageRef.putBytes(imageBytes);
              task.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
                  @Override
                  public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                      Uri downloadUrl = taskSnapshot.getDownloadUrl();
                      // mark image in the database
                      Log.i(TAG, "Image upload successful");
                      log.child("timestamp").setValue(ServerValue.TIMESTAMP);
                      log.child("image").setValue(downloadUrl.toString());
                      // process image annotations
                      annotateImage(log, imageBytes);
                  }
              }).addOnFailureListener(new OnFailureListener() {
                  @Override
                  public void onFailure(@NonNull Exception e) {
                      // clean up this entry
                      Log.w(TAG, "Unable to upload image to Firebase");
                      log.removeValue();
                  }
              });
          }
      }
  
      /**
       * Process image contents with Cloud Vision.
       */
      private void annotateImage(final DatabaseReference ref, final byte[] imageBytes) {
          mCloudHandler.post(new Runnable() {
              @Override
              public void run() {
                  Log.d(TAG, "sending image to cloud vision");
                  // annotate image by uploading to Cloud Vision API
                  try {
                      Map<String, Float> annotations = CloudVisionUtils.annotateImage(imageBytes);
                      Log.d(TAG, "cloud vision annotations:" + annotations);
                      if (annotations != null) {
                          ref.child("annotations").setValue(annotations);
                      }
                  } catch (IOException e) {
                      Log.e(TAG, "Cloud Vison API error: ", e);
                  }
              }
          });
      }
  }
  