diff --git a/build.gradle b/build.gradle index 9157aa4..a6f7177 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.2' + classpath 'com.android.tools.build:gradle:3.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.4' diff --git a/mobile/src/main/res/drawable-hdpi/banner_screenshot.png b/mobile/src/main/res/drawable-hdpi/banner_screenshot.png new file mode 100644 index 0000000..6a184e6 Binary files /dev/null and b/mobile/src/main/res/drawable-hdpi/banner_screenshot.png differ diff --git a/mobile/src/main/res/drawable-mdpi/banner_screenshot.png b/mobile/src/main/res/drawable-mdpi/banner_screenshot.png new file mode 100644 index 0000000..ea562f8 Binary files /dev/null and b/mobile/src/main/res/drawable-mdpi/banner_screenshot.png differ diff --git a/mobile/src/main/res/drawable-xhdpi/banner_screenshot.png b/mobile/src/main/res/drawable-xhdpi/banner_screenshot.png new file mode 100644 index 0000000..1150cab Binary files /dev/null and b/mobile/src/main/res/drawable-xhdpi/banner_screenshot.png differ diff --git a/mobile/src/main/res/drawable-xxhdpi/banner_screenshot.png b/mobile/src/main/res/drawable-xxhdpi/banner_screenshot.png new file mode 100644 index 0000000..c6f297a Binary files /dev/null and b/mobile/src/main/res/drawable-xxhdpi/banner_screenshot.png differ diff --git a/mobile/src/main/res/drawable-xxxhdpi/banner_screenshot.png b/mobile/src/main/res/drawable-xxxhdpi/banner_screenshot.png new file mode 100644 index 0000000..82f3117 Binary files /dev/null and b/mobile/src/main/res/drawable-xxxhdpi/banner_screenshot.png differ diff --git a/things/src/main/AndroidManifest.xml b/things/src/main/AndroidManifest.xml index a113772..67a983e 100644 --- a/things/src/main/AndroidManifest.xml +++ b/things/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + diff --git a/things/src/main/java/com/example/androidthings/lantern/ChannelsRegistry.kt b/things/src/main/java/com/example/androidthings/lantern/ChannelsRegistry.kt index ff4e04d..c0f4c96 100644 --- a/things/src/main/java/com/example/androidthings/lantern/ChannelsRegistry.kt +++ b/things/src/main/java/com/example/androidthings/lantern/ChannelsRegistry.kt @@ -23,6 +23,12 @@ object ChannelsRegistry { Uri.parse("android.resource://com.example.androidthings.lantern/drawable/banner_clock"), customizable = true )), + Pair(::ScreenShot, ChannelInfo( + "ScreenShot", + "Take a picture", + "Take a picture of something that is under lantern and project it to any surface.", + Uri.parse("android.resource://com.example.androidthings.lantern/drawable/banner_screenshot") + )), Pair(::NowPlayingChannel, ChannelInfo( "now-playing", "Now playing", diff --git a/things/src/main/java/com/example/androidthings/lantern/channels/ScreenShot.kt b/things/src/main/java/com/example/androidthings/lantern/channels/ScreenShot.kt new file mode 100644 index 0000000..cb5af4a --- /dev/null +++ b/things/src/main/java/com/example/androidthings/lantern/channels/ScreenShot.kt @@ -0,0 +1,61 @@ +package com.example.androidthings.lantern.channels + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Matrix +import android.media.ImageReader +import android.os.Bundle +import android.os.Handler +import android.os.HandlerThread +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import com.example.androidthings.lantern.Channel +import com.example.androidthings.lantern.hardware.Camera + + +/** + * Makes a picture with the camera and projects it afterwards + */ +class ScreenShot : Channel() { + + val TAG = this::class.java.simpleName + + private lateinit var view: ImageView + + private val handler: Handler = Handler() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + this.view = ImageView(context) + this.view.scaleType = ImageView.ScaleType.CENTER_INSIDE + + val mCameraThread = HandlerThread("CameraBackground") + mCameraThread.start() + val mCameraHandler = Handler(mCameraThread.looper) + + val mCamera = Camera.getInstance() + mCamera.initializeCamera(this.activity, mCameraHandler, imageAvailableListener) + + handler.postDelayed({ mCamera.takePicture() }, 3000) + return view + } + + private val imageAvailableListener = ImageReader.OnImageAvailableListener { reader -> + Log.d(TAG, "ImageAvailable!") + val image = reader.acquireLatestImage() + val imageBuffer = image.planes[0].buffer + val imageBytes = ByteArray(imageBuffer.remaining()) + imageBuffer.get(imageBytes) + image.close() + val bitmap = getBitmapFromByteArray(imageBytes) + handler.post({ this.view.setImageBitmap(bitmap) }) + } + + private fun getBitmapFromByteArray(imageBytes: ByteArray): Bitmap { + val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, Matrix(), true) + } +} \ No newline at end of file diff --git a/things/src/main/java/com/example/androidthings/lantern/hardware/Camera.java b/things/src/main/java/com/example/androidthings/lantern/hardware/Camera.java new file mode 100644 index 0000000..496b59c --- /dev/null +++ b/things/src/main/java/com/example/androidthings/lantern/hardware/Camera.java @@ -0,0 +1,263 @@ +/* + * 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.lantern.hardware; + +import android.content.Context; +import android.graphics.ImageFormat; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraDevice; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.ImageReader; +import android.os.Handler; +import android.util.Log; +import android.util.Size; + +import java.util.Collections; + +import static android.content.Context.CAMERA_SERVICE; + +/** + * Helper class to deal with methods to deal with images from the camera. + */ +public class Camera { + private static final String TAG = Camera.class.getSimpleName(); + + private static final int IMAGE_WIDTH = 1280; + private static final int IMAGE_HEIGHT = 720; + private static final int MAX_IMAGES = 1; + + private CameraDevice mCameraDevice; + + private CameraCaptureSession mCaptureSession; + + /** + * An {@link ImageReader} that handles still image capture. + */ + private ImageReader mImageReader; + + // Lazy-loaded singleton, so only one instance of the camera is created. + private Camera() { + } + + private static class InstanceHolder { + private static Camera mCamera = new Camera(); + } + + public static Camera getInstance() { + return InstanceHolder.mCamera; + } + + /** + * Initialize the camera device + */ + public void initializeCamera(Context context, + Handler backgroundHandler, + ImageReader.OnImageAvailableListener imageAvailableListener) { + // Discover the camera instance + CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE); + String[] camIds = {}; + try { + camIds = manager.getCameraIdList(); + } catch (CameraAccessException e) { + Log.e(TAG, "Cam access exception getting IDs", e); + } + if (camIds.length < 1) { + Log.e(TAG, "No cameras found"); + return; + } + String id = camIds[0]; + Log.d(TAG, "Using camera id " + id); + + // Initialize the image processor + mImageReader = ImageReader.newInstance(IMAGE_WIDTH, IMAGE_HEIGHT, + ImageFormat.JPEG, MAX_IMAGES); + mImageReader.setOnImageAvailableListener( + imageAvailableListener, backgroundHandler); + + // Open the camera resource + try { + manager.openCamera(id, mStateCallback, backgroundHandler); + } catch (CameraAccessException cae) { + Log.d(TAG, "Camera access exception", cae); + } + } + + /** + * Callback handling device state changes + */ + private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { + @Override + public void onOpened(CameraDevice cameraDevice) { + Log.d(TAG, "Opened camera."); + mCameraDevice = cameraDevice; + } + + @Override + public void onDisconnected(CameraDevice cameraDevice) { + Log.d(TAG, "Camera disconnected, closing."); + cameraDevice.close(); + } + + @Override + public void onError(CameraDevice cameraDevice, int i) { + Log.d(TAG, "Camera device error, closing."); + cameraDevice.close(); + } + + @Override + public void onClosed(CameraDevice cameraDevice) { + Log.d(TAG, "Closed camera, releasing"); + mCameraDevice = null; + } + }; + + /** + * Begin a still image capture + */ + public void takePicture() { + if (mCameraDevice == null) { + Log.e(TAG, "Cannot capture image. Camera not initialized."); + return; + } + + // Here, we create a CameraCaptureSession for capturing still images. + try { + mCameraDevice.createCaptureSession( + Collections.singletonList(mImageReader.getSurface()), + mSessionCallback, + null); + } catch (CameraAccessException cae) { + Log.e(TAG, "access exception while preparing pic", cae); + } + } + + /** + * Callback handling session state changes + */ + private CameraCaptureSession.StateCallback mSessionCallback = + new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession cameraCaptureSession) { + // The camera is already closed + if (mCameraDevice == null) { + return; + } + + // When the session is ready, we start capture. + mCaptureSession = cameraCaptureSession; + triggerImageCapture(); + } + + @Override + public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { + Log.e(TAG, "Failed to configure camera"); + } + }; + + /** + * Execute a new capture request within the active session + */ + private void triggerImageCapture() { + try { + final CaptureRequest.Builder captureBuilder = + mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); + captureBuilder.addTarget(mImageReader.getSurface()); + captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); + Log.d(TAG, "Session initialized."); + mCaptureSession.capture(captureBuilder.build(), mCaptureCallback, null); + } catch (CameraAccessException cae) { + Log.e(TAG, "camera capture exception", cae); + } + } + + /** + * Callback handling capture session events + */ + private final CameraCaptureSession.CaptureCallback mCaptureCallback = + new CameraCaptureSession.CaptureCallback() { + + @Override + public void onCaptureProgressed(CameraCaptureSession session, + CaptureRequest request, + CaptureResult partialResult) { + Log.d(TAG, "Partial result"); + } + + @Override + public void onCaptureCompleted(CameraCaptureSession session, + CaptureRequest request, + TotalCaptureResult result) { + if (session != null) { + session.close(); + mCaptureSession = null; + Log.d(TAG, "CaptureSession closed"); + } + } + }; + + + /** + * Close the camera resources + */ + public void shutDown() { + if (mCameraDevice != null) { + mCameraDevice.close(); + } + } + + /** + * Helpful debugging method: Dump all supported camera formats to log. You don't need to run + * this for normal operation, but it's very helpful when porting this code to different + * hardware. + */ + public static void dumpFormatInfo(Context context) { + CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE); + String[] camIds = {}; + try { + camIds = manager.getCameraIdList(); + } catch (CameraAccessException e) { + Log.d(TAG, "Cam access exception getting IDs"); + } + if (camIds.length < 1) { + Log.d(TAG, "No cameras found"); + } + String id = camIds[0]; + Log.d(TAG, "Using camera id " + id); + try { + CameraCharacteristics characteristics = manager.getCameraCharacteristics(id); + StreamConfigurationMap configs = characteristics.get( + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + for (int format : configs.getOutputFormats()) { + Log.d(TAG, "Getting sizes for format: " + format); + for (Size s : configs.getOutputSizes(format)) { + Log.d(TAG, "\t" + s.toString()); + } + } + int[] effects = characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS); + for (int effect : effects) { + Log.d(TAG, "Effect available: " + effect); + } + } catch (CameraAccessException e) { + Log.d(TAG, "Cam access exception getting characteristics."); + } + } +} \ No newline at end of file