/* * 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); } } }); } }