summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJack Yoo <jyoo@codeaurora.org>2016-02-10 17:22:09 -0800
committerJay Wang <jaywang@codeaurora.org>2016-09-27 15:54:51 -0700
commit7dd609bbc372c2bbeb9e3c10fe567e085da4f15d (patch)
treec947d9f35a0bfded03cea094ab81f222afde7258 /src
parent13f5f043eca7b0b6487f2809a06a010c268648b6 (diff)
downloadandroid_packages_apps_Snap-7dd609bbc372c2bbeb9e3c10fe567e085da4f15d.tar.gz
android_packages_apps_Snap-7dd609bbc372c2bbeb9e3c10fe567e085da4f15d.tar.bz2
android_packages_apps_Snap-7dd609bbc372c2bbeb9e3c10fe567e085da4f15d.zip
SnapdragonCamera: Panorama Module
Introducing Panorama capture module for Camera2 Change-Id: I98306c6c88c34c58b99adf9d472418392015f6c5 CRs-Fixed: 1067848
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/CameraActivity.java24
-rw-r--r--src/com/android/camera/CaptureModule.java18
-rw-r--r--src/com/android/camera/PanoCaptureFrameProcessor.java208
-rw-r--r--src/com/android/camera/PanoCaptureModule.java752
-rw-r--r--src/com/android/camera/PanoCaptureUI.java383
-rw-r--r--src/com/android/camera/SettingsManager.java3
-rw-r--r--src/com/android/camera/ui/CameraControls.java36
-rw-r--r--src/com/android/camera/ui/ModuleSwitcher.java3
-rw-r--r--src/com/android/camera/ui/PanoCaptureProcessView.java1097
9 files changed, 2518 insertions, 6 deletions
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index f5fe903da..749716254 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -85,6 +85,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.ShareActionProvider;
+import android.widget.Toast;
import com.android.camera.app.AppManagerFactory;
import com.android.camera.app.PlaceholderManager;
@@ -106,6 +107,8 @@ import com.android.camera.ui.ModuleSwitcher;
import com.android.camera.ui.DetailsDialog;
import com.android.camera.ui.FilmStripView;
import com.android.camera.ui.FilmStripView.ImageData;
+import com.android.camera.ui.PanoCaptureProcessView;
+import com.android.camera.ui.RotateTextToast;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.GcamHelper;
@@ -199,6 +202,7 @@ public class CameraActivity extends Activity
private VideoModule mVideoModule;
private WideAnglePanoramaModule mPanoModule;
private CaptureModule mCaptureModule;
+ private PanoCaptureModule mPano2Module;
private FrameLayout mAboveFilmstripControlLayout;
private FrameLayout mCameraRootFrame;
private View mCameraPhotoModuleRootView;
@@ -2095,6 +2099,26 @@ public class CameraActivity extends Activity
mCurrentModule = mCaptureModule;
mCameraCaptureModuleRootView.setVisibility(View.VISIBLE);
break;
+
+ case ModuleSwitcher.PANOCAPTURE_MODULE_INDEX:
+ final Activity activity = this;
+ if(!PanoCaptureProcessView.isSupportedStatic()) {
+ this.runOnUiThread(new Runnable() {
+ public void run() {
+ RotateTextToast.makeText(activity, "Panocapture library is missing", Toast.LENGTH_SHORT).show();
+ }
+ });
+ mCurrentModuleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
+ //Let it fall through to photo module
+ } else {
+ if (mPano2Module == null) {
+ mPano2Module = new PanoCaptureModule();
+ mPano2Module.init(this, mCameraPanoModuleRootView);
+ }
+ mCurrentModule = mPano2Module;
+ mCameraPanoModuleRootView.setVisibility(View.VISIBLE);
+ break;
+ }
case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX: //Unused module for now
case ModuleSwitcher.GCAM_MODULE_INDEX: //Unused module for now
default:
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index 64d111678..69cdecfc5 100644
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -1880,10 +1880,10 @@ public class CaptureModule implements CameraModule, PhotoController,
mSound = new MediaActionSound();
}
+ String scene = mSettingsManager.getValue(SettingsManager.KEY_SCENE_MODE);
if(mPostProcessor != null) {
String longshot = mSettingsManager.getValue(SettingsManager.KEY_LONGSHOT);
String flashMode = mSettingsManager.getValue(SettingsManager.KEY_FLASH_MODE);
- String scene = mSettingsManager.getValue(SettingsManager.KEY_SCENE_MODE);
if (scene != null) {
int mode = Integer.parseInt(scene);
Log.d(TAG, "Chosen postproc filter id : " + getPostProcFilterId(mode));
@@ -1937,6 +1937,11 @@ public class CaptureModule implements CameraModule, PhotoController,
}
});
mUI.enableShutter(true);
+
+ if(isPanoSetting(scene)) {
+ mActivity.onModuleSelected(ModuleSwitcher.PANOCAPTURE_MODULE_INDEX);
+ mSettingsManager.setValue(SettingsManager.KEY_SCENE_MODE, SettingsManager.SCENE_MODE_AUTO_INT+"");
+ }
}
@Override
@@ -3389,6 +3394,17 @@ public class CaptureModule implements CameraModule, PhotoController,
}
}
+ private boolean isPanoSetting(String value) {
+ try {
+ int mode = Integer.parseInt(value);
+ if(mode == SettingsManager.SCENE_MODE_PANORAMA_INT) {
+ return true;
+ }
+ } catch(Exception e) {
+ }
+ return false;
+ }
+
private void updateFaceDetection() {
final String value = mSettingsManager.getValue(SettingsManager.KEY_FACE_DETECTION);
mActivity.runOnUiThread(new Runnable() {
diff --git a/src/com/android/camera/PanoCaptureFrameProcessor.java b/src/com/android/camera/PanoCaptureFrameProcessor.java
new file mode 100644
index 000000000..5aae1e2ca
--- /dev/null
+++ b/src/com/android/camera/PanoCaptureFrameProcessor.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.android.camera;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicYuvToRGB;
+import android.renderscript.Type;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import com.android.camera.ui.PanoCaptureProcessView;
+
+import java.io.ByteArrayOutputStream;
+
+public class PanoCaptureFrameProcessor {
+
+ private Allocation mInputAllocation;
+ private Allocation mARGBOutputAllocation;
+
+ private Surface mSurface;
+ private HandlerThread mProcessingThread;
+ private Handler mProcessingHandler;
+
+ public ProcessingTask mTask;
+ private Size mSize;
+ private RenderScript mRs;
+ private PanoCaptureUI mUI;
+ private Activity mActivity;
+ private PanoCaptureModule mController;
+ ScriptIntrinsicYuvToRGB mRsYuvToRGB;
+ private Bitmap mBitmap;
+ private boolean mIsPanoActive = false;
+ private Object mPanoSwitchLock = new Object();
+ private Object mAllocationLock = new Object();
+ private boolean mIsAllocationEverUsed;
+
+ public PanoCaptureFrameProcessor(Size dimensions, Activity activity, PanoCaptureUI ui, PanoCaptureModule controller) {
+ mUI = ui;
+ mSize = dimensions;
+ mActivity = activity;
+ mController = controller;
+ synchronized (mAllocationLock) {
+ mRs = RenderScript.create(mActivity);
+ mRsYuvToRGB = ScriptIntrinsicYuvToRGB.create(mRs, Element.RGBA_8888(mRs));
+
+ Type.Builder yuvTypeBuilder = new Type.Builder(mRs, Element.YUV(mRs));
+ yuvTypeBuilder.setX(dimensions.getWidth());
+ yuvTypeBuilder.setY(dimensions.getHeight());
+ yuvTypeBuilder.setYuvFormat(ImageFormat.YUV_420_888);
+ mInputAllocation = Allocation.createTyped(mRs, yuvTypeBuilder.create(),
+ Allocation.USAGE_IO_INPUT | Allocation.USAGE_SCRIPT);
+
+ Type.Builder rgbTypeBuilder = new Type.Builder(mRs, Element.RGBA_8888(mRs));
+ rgbTypeBuilder.setX(dimensions.getWidth());
+ rgbTypeBuilder.setY(dimensions.getHeight());
+ mARGBOutputAllocation = Allocation.createTyped(mRs, rgbTypeBuilder.create(), Allocation.USAGE_SCRIPT);
+
+ if (mProcessingThread == null) {
+ mProcessingThread = new HandlerThread("PanoCapture_FrameProcessor");
+ mProcessingThread.start();
+ mProcessingHandler = new Handler(mProcessingThread.getLooper());
+ }
+ mTask = new ProcessingTask();
+ mInputAllocation.setOnBufferAvailableListener(mTask);
+ mIsAllocationEverUsed = false;
+ }
+ }
+
+ public void clear() {
+ if(mIsPanoActive) {
+ changePanoStatus(false, true);
+ }
+ synchronized (mAllocationLock) {
+ mInputAllocation.setOnBufferAvailableListener(null);
+ if(mIsAllocationEverUsed) {
+ mRs.destroy();
+ mInputAllocation.destroy();
+ mARGBOutputAllocation.destroy();
+ }
+ mRs = null;
+ mInputAllocation = null;
+ mARGBOutputAllocation = null;
+ }
+ mProcessingThread.quitSafely();
+ try {
+ mProcessingThread.join();
+ mProcessingThread = null;
+ mProcessingHandler = null;
+ } catch (InterruptedException e) {
+ }
+ }
+
+ public Surface getInputSurface() {
+ synchronized (mAllocationLock) {
+ if (mInputAllocation == null)
+ return null;
+ return mInputAllocation.getSurface();
+ }
+ }
+
+ public void changePanoStatus(boolean newStatus, boolean isCancelling) {
+ if(newStatus == mIsPanoActive) {
+ return;
+ }
+ synchronized (mPanoSwitchLock) {
+ if(mUI.isPanoCompleting()) {
+ return;
+ }
+ mIsPanoActive = newStatus;
+ if (!mIsPanoActive) {
+ mUI.onFrameAvailable(null, isCancelling);
+ }
+ }
+ if(!mIsPanoActive) {
+ mController.unlockFocus();
+ }
+ }
+
+ public boolean isPanoActive() {
+ return mIsPanoActive;
+ }
+
+ class ProcessingTask implements Runnable, Allocation.OnBufferAvailableListener {
+ private int mPendingFrames = 0;
+ private int mFrameCounter = 0;
+
+ public ProcessingTask() {
+ mBitmap = Bitmap.createBitmap(mSize.getWidth(), mSize.getHeight(), Bitmap.Config.ARGB_8888);
+ }
+
+ @Override
+ public void onBufferAvailable(Allocation a) {
+ if(mProcessingHandler == null)
+ return;
+ synchronized(this) {
+ mPendingFrames++;
+ mProcessingHandler.post(this);
+ }
+ }
+
+ @Override
+ public void run() {
+ int pendingFrames;
+ synchronized(this) {
+ pendingFrames = mPendingFrames;
+ mPendingFrames = 0;
+ mProcessingHandler.removeCallbacks(this);
+ }
+
+ synchronized (mAllocationLock) {
+ if(mInputAllocation == null || mARGBOutputAllocation == null)
+ return;
+ mIsAllocationEverUsed = true;
+ for (int i = 0; i < pendingFrames; i++) {
+ mInputAllocation.ioReceive();
+ }
+ synchronized (mPanoSwitchLock) {
+ if (mIsPanoActive && !mUI.isFrameProcessing()) {
+ mRsYuvToRGB.setInput(mInputAllocation);
+ mRsYuvToRGB.forEach(mARGBOutputAllocation);
+ mARGBOutputAllocation.copyTo(mBitmap);
+ mUI.onFrameAvailable(mBitmap, false);
+ }
+ }
+ }
+ }
+ }
+
+}
+
diff --git a/src/com/android/camera/PanoCaptureModule.java b/src/com/android/camera/PanoCaptureModule.java
new file mode 100644
index 000000000..7a9f7a705
--- /dev/null
+++ b/src/com/android/camera/PanoCaptureModule.java
@@ -0,0 +1,752 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Copyright (C) 2012 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.android.camera;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.ImageFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+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.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.location.Location;
+import android.media.Image;
+import android.media.ImageReader;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Size;
+import android.util.SparseIntArray;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.widget.Toast;
+
+import com.android.camera.PhotoModule.NamedImages;
+import com.android.camera.PhotoModule.NamedImages.NamedEntity;
+import com.android.camera.data.LocalData;
+import com.android.camera.exif.ExifInterface;
+import com.android.camera.ui.RotateTextToast;
+import com.android.camera.util.CameraUtil;
+
+import org.codeaurora.snapcam.R;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class PanoCaptureModule implements CameraModule, PhotoController {
+ /**
+ * Camera state: Showing camera preview.
+ */
+ private static final int STATE_PREVIEW = 0;
+
+ private static final String TAG = "SnapCam_PanoCaptureModule";
+
+ private static final int BAYER_CAMERA_ID = 0;
+ private CaptureRequest.Builder mPreviewRequestBuilder;
+ private CaptureRequest mPreviewRequest;
+ private int mState = STATE_PREVIEW;
+ private Semaphore mCameraOpenCloseLock = new Semaphore(1);
+
+ private boolean mSurfaceReady = false;
+ private boolean mCameraOpened = false;
+ private CameraDevice mCameraDevice;
+ private String mCameraId;
+ private PanoCaptureUI mUI;
+ private CameraActivity mActivity;
+
+ private CameraCaptureSession mCaptureSession;
+
+ private HandlerThread mCameraThread;
+
+ private Handler mCameraHandler;
+
+ private ContentResolver mContentResolver;
+ private Size mOutputSize;
+ private PanoCaptureFrameProcessor mFrameProcessor;
+ private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+ private LocationManager mLocationManager;
+ private Object mSessionLock = new Object();
+ public static final float TARGET_RATIO = 4f/3f;
+ private static final int STATE_WAITING_LOCK = 1;
+ private Semaphore mFocusLockSemaphore = new Semaphore(1);
+ private boolean mIsLockFocusAttempted = false;
+
+ private CameraCaptureSession.CaptureCallback mCaptureCallback
+ = new CameraCaptureSession.CaptureCallback() {
+
+ private void process(CaptureResult result) {
+ switch (mState) {
+ case STATE_PREVIEW: {
+ break;
+ }
+ case STATE_WAITING_LOCK: {
+ Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
+ Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ Log.d(TAG, "STATE_WAITING_LOCK afState:" + afState + " aeState:" + aeState);
+ if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
+ CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
+ changePanoStatus(true, false);
+ mState = STATE_PREVIEW;
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onCaptureProgressed(CameraCaptureSession session,
+ CaptureRequest request,
+ CaptureResult partialResult) {
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session,
+ CaptureRequest request,
+ TotalCaptureResult result) {
+ process(result);
+ }
+
+ };
+ private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
+
+ @Override
+ public void onOpened(CameraDevice cameraDevice) {
+ mCameraOpenCloseLock.release();
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mUI.onCameraOpened();
+ }
+
+
+ });
+ mCameraDevice = cameraDevice;
+ mCameraOpened = true;
+ createSession();
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice cameraDevice) {
+ mCameraOpenCloseLock.release();
+ cameraDevice.close();
+ mCameraDevice = null;
+ }
+
+ @Override
+ public void onError(CameraDevice cameraDevice, int error) {
+ int id = Integer.parseInt(cameraDevice.getId());
+ mCameraOpenCloseLock.release();
+ cameraDevice.close();
+ mCameraDevice = null;
+ if (null != mActivity) {
+ mActivity.finish();
+ }
+ }
+
+ };
+
+ private void closeSession() {
+ synchronized (mSessionLock) {
+ if (mFrameProcessor != null) {
+ mFrameProcessor.clear();
+ mFrameProcessor = null;
+ }
+ }
+ }
+
+ private void createSession() {
+ if (!mCameraOpened || !mSurfaceReady) return;
+ synchronized (mSessionLock) {
+ List<Surface> list = new LinkedList<Surface>();
+ try {
+ Surface surface = null;
+ SurfaceHolder sh = mUI.getSurfaceHolder();
+ if (sh != null) {
+ surface = sh.getSurface();
+ }
+ if (surface == null)
+ return;
+
+ if(mFrameProcessor == null) {
+ mFrameProcessor = new PanoCaptureFrameProcessor(mOutputSize, mActivity, mUI, this);
+ }
+
+ mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice
+ .TEMPLATE_PREVIEW);
+ mPreviewRequestBuilder.addTarget(mFrameProcessor.getInputSurface());
+ mPreviewRequestBuilder.addTarget(surface);
+
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ mPreviewRequest = mPreviewRequestBuilder.build();
+ list.add(surface);
+ list.add(mFrameProcessor.getInputSurface());
+ mCameraDevice.createCaptureSession(list,
+ new CameraCaptureSession.StateCallback() {
+
+ @Override
+ public void onConfigured(CameraCaptureSession cameraCaptureSession) {
+ if (null == mCameraDevice) {
+ Log.e(TAG, "The camera is already closed.");
+ return;
+ }
+ // When the session is ready, we start displaying the preview.
+ mCaptureSession = cameraCaptureSession;
+ try {
+ mCaptureSession.setRepeatingRequest(mPreviewRequest,
+ mCaptureCallback, mCameraHandler);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "createCaptureSession: " + e.toString());
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(
+ CameraCaptureSession cameraCaptureSession) {
+ Log.e(TAG, "Capture session configuration is failed");
+ }
+ }, null
+ );
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "createSession: " + e.toString());
+ mActivity.finish();
+ } catch (SecurityException e) {
+ Log.e(TAG, "createSession: " + e.toString());
+ mActivity.finish();
+ }
+ }
+ }
+
+ @Override
+ public void init(CameraActivity activity, View parent) {
+ mCameraOpened = false;
+ mSurfaceReady = false;
+ mActivity = activity;
+
+ mUI = new PanoCaptureUI(activity, this, parent);
+ mContentResolver = mActivity.getContentResolver();
+ mLocationManager = new LocationManager(mActivity, null);
+ }
+
+ public void changePanoStatus(boolean newStatus, boolean isCancelling) {
+ if(mFrameProcessor != null) {
+ mFrameProcessor.changePanoStatus(newStatus, isCancelling);
+ }
+ }
+
+ public boolean isPanoActive() {
+ if(mFrameProcessor != null) {
+ return mFrameProcessor.isPanoActive();
+ }
+ return false;
+ }
+
+ private void setUpCameraOutputs() {
+ Activity activity = mActivity;
+ CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
+ try {
+ String[] cameraIdList = manager.getCameraIdList();
+ String cameraId = cameraIdList[BAYER_CAMERA_ID];
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
+ StreamConfigurationMap map = characteristics.get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ if (map == null) {
+ return;
+ }
+
+ Display display = mActivity.getWindowManager().getDefaultDisplay();
+ Point ds = new Point();
+ display.getSize(ds);
+ mOutputSize = getOutputSize(TARGET_RATIO, map.getOutputSizes(ImageFormat.YUV_420_888), ds.x, ds.y);
+ mCameraId = cameraId;
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "setUpCameraOutputs: " + e.toString());
+ }
+ }
+
+ private Size getOutputSize(float ratio, Size[] prevSizes, int screenW, int
+ screenH) {
+ Size optimal = prevSizes[0];
+ for (Size prevSize: prevSizes) {
+ float prevRatio = (float) prevSize.getWidth() / prevSize.getHeight();
+ if (Math.abs(prevRatio - ratio) < 0.01) {
+ if (prevSize.getWidth() <= screenH && prevSize.getHeight() <= screenW) {
+ return prevSize;
+ } else {
+ optimal = prevSize;
+ }
+ }
+ }
+ return optimal;
+ }
+
+ public Size getPictureOutputSize() {
+ return mOutputSize;
+ }
+
+ /**
+ * Closes the current {@link CameraDevice}.
+ */
+ private void closeCamera() {
+ boolean wasPreviousCameraOpenFailed = false;
+ try {
+ if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
+ Log.d(TAG, "Time out waiting to lock camera closing.");
+ wasPreviousCameraOpenFailed = true;
+ }
+ if (null != mCaptureSession) {
+ mCaptureSession.close();
+ mCaptureSession = null;
+ }
+ if (null != mCameraDevice) {
+ mCameraDevice.close();
+ mCameraDevice = null;
+ mCameraOpened = false;
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
+ } finally {
+ mCameraOpenCloseLock.release();
+ if(wasPreviousCameraOpenFailed) {
+ mActivity.finish();
+ }
+ }
+ }
+
+ /**
+ * Starts a background thread and its {@link Handler}.
+ */
+ private void startBackgroundThread() {
+ mCameraThread = new HandlerThread("CameraBackground");
+ mCameraThread.start();
+ mCameraHandler = new Handler(mCameraThread.getLooper());
+ }
+
+ /**
+ * Stops the background thread and its {@link Handler}.
+ */
+ private void stopBackgroundThread() {
+ mCameraThread.quitSafely();
+ try {
+ mCameraThread.join();
+ mCameraThread = null;
+ mCameraHandler = null;
+ } catch (InterruptedException e) {
+ //Ignore this
+ }
+ }
+
+ private void openCamera() {
+ CameraManager manager;
+ try {
+ manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
+ mCameraId = manager.getCameraIdList()[BAYER_CAMERA_ID];
+ if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
+ Log.d(TAG, "Time out waiting to lock camera opening.");
+ }
+ manager.openCamera(mCameraId, mStateCallback, mCameraHandler);
+ } catch (SecurityException e) {
+ Log.e(TAG, "openCamera: " + e.toString());
+ Toast.makeText(mActivity, "Can't open camera, please restart it", Toast.LENGTH_LONG).show();
+ mActivity.finish();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "openCamera: " + e.toString());
+ Toast.makeText(mActivity, "Can't open camera, please restart it", Toast.LENGTH_LONG).show();
+ mActivity.finish();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "openCamera: " + e.toString());
+ }
+ }
+
+ @Override
+ public void onPreviewFocusChanged(boolean previewFocused) {
+
+ }
+
+ @Override
+ public void onPauseBeforeSuper() {
+ mUI.applySurfaceChange(0, false);
+ }
+
+ @Override
+ public void onPauseAfterSuper() {
+ stopBackgroundThread();
+ closeCamera();
+ mUI.onPause();
+ }
+
+ @Override
+ public void onResumeBeforeSuper() {
+
+ }
+
+ @Override
+ public void onResumeAfterSuper() {
+ mUI.onResume();
+ openCamera();
+ setUpCameraOutputs();
+ mUI.applySurfaceChange(2, false);
+ mUI.setLayout(mOutputSize);
+ startBackgroundThread();
+ mUI.enableShutter(true);
+ mUI.setSwitcherIndex();
+ mUI.initializeShutterButton();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration config) {
+
+ }
+
+ @Override
+ public void onStop() {
+
+ }
+
+ public Uri savePanorama(byte[] jpegData, int width, int height, int orientation) {
+ long timeTaken = System.currentTimeMillis();
+
+ if (jpegData != null) {
+ String filename = PanoUtil.createName(
+ mActivity.getResources().getString(R.string.pano_file_name_format), timeTaken);
+ String filepath = Storage.generateFilepath(filename,
+ PhotoModule.PIXEL_FORMAT_JPEG);
+
+ Location loc = mLocationManager.getCurrentLocation();
+ ExifInterface exif = new ExifInterface();
+ try {
+ exif.readExif(jpegData);
+ exif.addGpsDateTimeStampTag(timeTaken);
+ exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, timeTaken,
+ TimeZone.getDefault());
+ exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,orientation));
+ writeLocation(loc, exif);
+ exif.writeExif(jpegData, filepath);
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot set exif for " + filepath, e);
+ Storage.writeFile(filepath, jpegData);
+ }
+ int jpegLength = (int) (new File(filepath).length());
+ return Storage.addImage(mContentResolver, filename, timeTaken, loc, orientation,
+ jpegLength, filepath, width, height, LocalData.MIME_TYPE_JPEG);
+ }
+ return null;
+ }
+
+ private static void writeLocation(Location location, ExifInterface exif) {
+ if (location == null) {
+ return;
+ }
+ exif.addGpsTags(location.getLatitude(), location.getLongitude());
+ exif.setTag(exif.buildTag(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider()));
+ }
+
+ @Override
+ public void installIntentFilter() {
+
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+
+ }
+
+ @Override
+ public boolean onBackPressed() {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public int onZoomChanged(int requestedZoom) {
+ return 0;
+ }
+
+ @Override
+ public void onZoomChanged(float requestedZoom) {
+
+ }
+
+ @Override
+ public boolean isImageCaptureIntent() {
+ return false;
+ }
+
+ @Override
+ public boolean isCameraIdle() {
+ return false;
+ }
+
+ @Override
+ public void onCaptureDone() {
+
+ }
+
+ @Override
+ public void onCaptureCancelled() {
+
+ }
+
+ @Override
+ public void onCaptureRetake() {
+
+ }
+
+ @Override
+ public void cancelAutoFocus() {
+
+ }
+
+ @Override
+ public void stopPreview() {
+
+ }
+
+ @Override
+ public int getCameraState() {
+ return 0;
+ }
+
+ @Override
+ public void onSingleTapUp(View view, int x, int y) {
+
+ }
+
+ @Override
+ public void onCountDownFinished() {
+
+ }
+
+ @Override
+ public void onScreenSizeChanged(int width, int height) {
+
+ }
+
+ @Override
+ public void onPreviewRectChanged(Rect previewRect) {
+
+ }
+
+ @Override
+ public void updateCameraOrientation() {
+
+ }
+
+ @Override
+ public void enableRecordingLocation(boolean enable) {
+
+ }
+
+ @Override
+ public void setPreferenceForTest(String key, String value) {
+ }
+
+ @Override
+ public void onPreviewUIReady() {
+ mSurfaceReady = true;
+ createSession();
+ }
+
+ @Override
+ public void onPreviewUIDestroyed() {
+ closeSession();
+ }
+
+ @Override
+ public void onPreviewTextureCopied() {
+
+ }
+
+ @Override
+ public void onCaptureTextureCopied() {
+
+ }
+
+ @Override
+ public void onUserInteraction() {
+
+ }
+
+ @Override
+ public boolean updateStorageHintOnResume() {
+ return false;
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+ int newOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
+
+ if (mOrientation != newOrientation) {
+ mOrientation = newOrientation;
+ mUI.setOrientation(newOrientation, true);
+ }
+ }
+
+ @Override
+ public void onShowSwitcherPopup() {
+
+ }
+
+ @Override
+ public void onMediaSaveServiceConnected(MediaSaveService s) {
+
+ }
+
+ @Override
+ public boolean arePreviewControlsVisible() {
+ return false;
+ }
+
+ @Override
+ public void resizeForPreviewAspectRatio() {
+
+ }
+
+ @Override
+ public void onSwitchSavePath() {
+
+ }
+
+ @Override
+ public void waitingLocationPermissionResult(boolean waiting) {
+
+ }
+
+ @Override
+ public void onShutterButtonFocus(boolean pressed) {
+
+ }
+
+ @Override
+ public void onShutterButtonClick() {
+ if(!mFocusLockSemaphore.tryAcquire())
+ return;
+ mFocusLockSemaphore.release();
+ if (mState == STATE_WAITING_LOCK) {
+ return;
+ } else {
+ if (isPanoActive()) {
+ changePanoStatus(false, false);
+ } else {
+ lockFocus();
+ }
+ }
+ }
+
+ private void lockFocus() {
+ Log.d(TAG, "lockFocus");
+ mIsLockFocusAttempted = true;
+ try {
+ mFocusLockSemaphore.acquire();
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
+ mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
+ mCaptureCallback, mCameraHandler);
+ mState = STATE_WAITING_LOCK;
+ mFocusLockSemaphore.release();
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ public void unlockFocus() {
+ if(!mIsLockFocusAttempted) {
+ return;
+ }
+ Log.d(TAG, "unlockFocus ");
+ try {
+ mFocusLockSemaphore.acquire();
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
+ mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
+ mCaptureCallback, mCameraHandler);
+ mState = STATE_PREVIEW;
+ mFocusLockSemaphore.release();
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ }
+ mIsLockFocusAttempted = false;
+ }
+
+ @Override
+ public void onShutterButtonLongClick() {
+ }
+
+ /**
+ * Compares two {@code Size}s based on their areas.
+ */
+ static class CompareSizesByArea implements Comparator<Size> {
+
+ @Override
+ public int compare(Size lhs, Size rhs) {
+ // We cast here to ensure the multiplications won't overflow
+ return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
+ (long) rhs.getWidth() * rhs.getHeight());
+ }
+
+ }
+}
diff --git a/src/com/android/camera/PanoCaptureUI.java b/src/com/android/camera/PanoCaptureUI.java
new file mode 100644
index 000000000..256647ad0
--- /dev/null
+++ b/src/com/android/camera/PanoCaptureUI.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Copyright (C) 2012 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.android.camera;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.hardware.Camera.Face;
+import android.util.Log;
+import android.util.Size;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.camera.ui.AutoFitSurfaceView;
+import com.android.camera.ui.CameraControls;
+import com.android.camera.ui.CameraRootView;
+import com.android.camera.ui.FocusIndicator;
+import com.android.camera.ui.ModuleSwitcher;
+import com.android.camera.ui.PanoCaptureProcessView;
+import com.android.camera.util.CameraUtil;
+
+import org.codeaurora.snapcam.R;
+
+public class PanoCaptureUI implements
+ SurfaceHolder.Callback,
+ LocationManager.Listener,
+ CameraRootView.MyDisplayListener,
+ CameraManager.CameraFaceDetectionCallback {
+
+ private static final String TAG = "SnapCam_PanoCaptureUI";
+ private CameraActivity mActivity;
+ private PanoCaptureModule mController;
+
+ private View mRootView;
+ private SurfaceHolder mSurfaceHolder;
+ private ShutterButton mShutterButton;
+ private ModuleSwitcher mSwitcher;
+ private CameraControls mCameraControls;
+ // Small indicators which show the camera settings in the viewfinder.
+ private OnScreenIndicators mOnScreenIndicators;
+
+ private AutoFitSurfaceView mSurfaceView = null;
+ private Matrix mMatrix = null;
+ private boolean mUIhidden = false;
+
+ private int mTopMargin = 0;
+ private int mBottomMargin = 0;
+ private int mSurfaceMode = 0; //0: INIT 1: TextureView 2: SurfaceView
+ private PanoCaptureProcessView mPreviewProcessView;
+ private ImageView mThumbnail;
+
+ private int mOrientation;
+
+ public void clearSurfaces() {
+ mSurfaceHolder = null;
+ }
+
+ public boolean isPanoCompleting() {
+ return mPreviewProcessView.isPanoCompleting();
+ }
+
+ public boolean isFrameProcessing() {
+ return mPreviewProcessView.isFrameProcessing();
+ }
+
+ public void onFrameAvailable(Bitmap bitmap, boolean isCancelling) {
+ mPreviewProcessView.onFrameAvailable(bitmap, isCancelling);
+ }
+
+ public void setSwitcherIndex() {
+ mSwitcher.setCurrentIndex(ModuleSwitcher.PANOCAPTURE_MODULE_INDEX);
+ }
+
+ /*
+ * mode
+ * 0: Hiding and closing
+ * 1: TextureView
+ * 2: SurfaceView
+ */
+ public synchronized void applySurfaceChange(int mode, boolean isForcing) {
+ if(mode == 0) {
+ clearSurfaces();
+ mSurfaceView.setVisibility(View.GONE);
+ mSurfaceMode = 0;
+ return;
+ }
+ if(!isForcing &&
+ ((mode == 1 && mSurfaceMode == 1) || (mode == 2 && mSurfaceMode == 2)))
+ return;
+ if(mode == 1) {
+ mSurfaceView.setVisibility(View.GONE);
+ mSurfaceMode = 1;
+ } else {
+ mSurfaceView.setVisibility(View.VISIBLE);
+ mSurfaceMode = 2;
+ }
+ }
+
+ private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ int width = right - left;
+ int height = bottom - top;
+ Size size = mController.getPictureOutputSize();
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(width, height, Gravity.CENTER);
+ mPreviewProcessView.setLayoutParams(lp);
+ mPreviewProcessView.setPanoPreviewSize(lp.width,
+ lp.height,
+ size.getWidth(),
+ size.getHeight());
+ }
+ };
+
+
+ public void setLayout(Size size) {
+ mSurfaceView.setAspectRatio(size.getHeight(), size.getWidth());
+ }
+
+ public PanoCaptureUI(CameraActivity activity, PanoCaptureModule controller, View parent) {
+ mActivity = activity;
+ mController = controller;
+ mRootView = parent;
+ mActivity.getLayoutInflater().inflate(R.layout.pano_capture_module,
+ (ViewGroup) mRootView, true);
+
+ mPreviewProcessView = (PanoCaptureProcessView)mRootView.findViewById(R.id.preview_process_view);
+ mPreviewProcessView.setContext(activity, mController);
+ mSurfaceView = (AutoFitSurfaceView) mRootView.findViewById(R.id.mdp_preview_content);
+ mSurfaceView.setVisibility(View.VISIBLE);
+ mSurfaceView.addOnLayoutChangeListener(mLayoutListener);
+ mSurfaceHolder = mSurfaceView.getHolder();
+ mSurfaceHolder.addCallback(this);
+ mRootView.findViewById(R.id.mute_button).setVisibility(View.GONE);
+ mRootView.findViewById(R.id.menu).setVisibility(View.GONE);
+ applySurfaceChange(2, false);
+
+ mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
+ mShutterButton.setLongClickable(false);
+ mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher);
+ mSwitcher.setCurrentIndex(ModuleSwitcher.PHOTO_MODULE_INDEX);
+ mSwitcher.setSwitchListener(mActivity);
+ mSwitcher.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mController.getCameraState() == PhotoController.LONGSHOT) {
+ return;
+ }
+ mSwitcher.showPopup();
+ mSwitcher.setOrientation(mOrientation, false);
+ }
+ });
+ mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls);
+
+ mThumbnail = (ImageView) mRootView.findViewById(R.id.preview_thumb);
+ mThumbnail.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!CameraControls.isAnimating())
+ mActivity.gotoGallery();
+ }
+ });
+
+ initIndicators();
+
+ Point size = new Point();
+ mActivity.getWindowManager().getDefaultDisplay().getSize(size);
+ calculateMargins(size);
+ mCameraControls.setMargins(mTopMargin, mBottomMargin);
+ }
+
+ private void calculateMargins(Point size) {
+ int l = size.x > size.y ? size.x : size.y;
+ int tm = mActivity.getResources().getDimensionPixelSize(R.dimen.preview_top_margin);
+ int bm = mActivity.getResources().getDimensionPixelSize(R.dimen.preview_bottom_margin);
+ mTopMargin = l / 4 * tm / (tm + bm);
+ mBottomMargin = l / 4 - mTopMargin;
+ }
+
+ private void setTransformMatrix(int width, int height) {
+ mMatrix = mSurfaceView.getMatrix();
+
+ // Calculate the new preview rectangle.
+ RectF previewRect = new RectF(0, 0, width, height);
+ mMatrix.mapRect(previewRect);
+ mController.onPreviewRectChanged(CameraUtil.rectFToRect(previewRect));
+ }
+
+ // SurfaceHolder callbacks
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.v(TAG, "surfaceChanged: width =" + width + ", height = " + height);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.v(TAG, "surfaceCreated");
+ mSurfaceHolder = holder;
+ mController.onPreviewUIReady();
+ mActivity.updateThumbnail(mThumbnail);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.v(TAG, "surfaceDestroyed");
+ mController.onPreviewUIDestroyed();
+ mSurfaceHolder = null;
+ }
+
+ public View getRootView() {
+ return mRootView;
+ }
+
+ private void initIndicators() {
+ mOnScreenIndicators = new OnScreenIndicators(mActivity,
+ mRootView.findViewById(R.id.on_screen_indicators));
+ }
+
+ public void onCameraOpened() {
+
+ }
+
+ public void hideUI() {
+ mSwitcher.closePopup();
+ if (mUIhidden)
+ return;
+ mUIhidden = true;
+ mCameraControls.hideUI();
+ }
+
+ public void showUI() {
+ mUIhidden = false;
+ mCameraControls.showUI();
+ }
+
+ public boolean arePreviewControlsVisible() {
+ return !mUIhidden;
+ }
+
+ public void initializeShutterButton() {
+ // Initialize shutter button.
+ mShutterButton.setImageResource(R.drawable.btn_new_shutter_panorama);
+ mShutterButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ //TODO: Any animation is needed?
+ }
+ });
+ mShutterButton.setOnShutterButtonListener(mController);
+ mShutterButton.setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * Enables or disables the shutter button.
+ */
+ public void enableShutter(boolean enabled) {
+ if (mShutterButton != null) {
+ mShutterButton.setEnabled(enabled);
+ }
+ }
+
+ public void overrideSettings(final String... keyvalues) {
+ }
+
+ public boolean onBackPressed() {
+ // In image capture mode, back button should:
+ // 1) if there is any popup, dismiss them, 2) otherwise, get out of
+ // image capture
+ if (mController.isImageCaptureIntent()) {
+ mController.onCaptureCancelled();
+ return true;
+ } else if (!mController.isCameraIdle()) {
+ // ignore backs while we're taking a picture
+ return true;
+ }
+ if (mSwitcher != null && mSwitcher.showsPopup()) {
+ mSwitcher.closePopup();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void onPreviewFocusChanged(boolean previewFocused) {
+ if (previewFocused) {
+ showUI();
+ } else {
+ hideUI();
+ }
+ setShowMenu(previewFocused);
+ }
+
+ private void setShowMenu(boolean show) {
+ if (mOnScreenIndicators != null) {
+ mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ public boolean collapseCameraControls() {
+ // TODO: Mode switcher should behave like a popup and should hide itself when there
+ // is a touch outside of it.
+ mSwitcher.closePopup();
+ return true;
+ }
+
+ public SurfaceHolder getSurfaceHolder() {
+ return mSurfaceHolder;
+ }
+
+ public void onResume() {
+ mPreviewProcessView.onResume();
+ mCameraControls.getPanoramaExitButton().setVisibility(View.VISIBLE);
+ mCameraControls.getPanoramaExitButton().setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ SettingsManager.getInstance().setValueIndex(SettingsManager.KEY_SCENE_MODE, SettingsManager.SCENE_MODE_AUTO_INT);
+ mActivity.onModuleSelected(ModuleSwitcher.CAPTURE_MODULE_INDEX);
+ }
+ });
+ }
+
+ public void onPause() {
+ collapseCameraControls();
+ mPreviewProcessView.onPause();
+ mCameraControls.getPanoramaExitButton().setVisibility(View.GONE);
+ mCameraControls.getPanoramaExitButton().setOnClickListener(null);
+ }
+
+ // focus UI implementation
+ private FocusIndicator getFocusIndicator() {
+ return null;
+ }
+
+ @Override
+ public void onFaceDetection(Face[] faces, CameraManager.CameraProxy camera) {
+ }
+
+ @Override
+ public void onDisplayChanged() {
+ Log.d(TAG, "Device flip detected.");
+ mCameraControls.checkLayoutFlip();
+ mController.updateCameraOrientation();
+ }
+
+ public void setOrientation(int orientation, boolean animation) {
+ mOrientation = orientation;
+ mCameraControls.setOrientation(orientation, animation);
+ mPreviewProcessView.setOrientation(orientation);
+ }
+
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ @Override
+ public void onErrorListener(int error) {
+
+ }
+}
diff --git a/src/com/android/camera/SettingsManager.java b/src/com/android/camera/SettingsManager.java
index 6e87ae7ad..20fbc2173 100644
--- a/src/com/android/camera/SettingsManager.java
+++ b/src/com/android/camera/SettingsManager.java
@@ -51,6 +51,7 @@ import com.android.camera.imageprocessor.filter.OptizoomFilter;
import com.android.camera.imageprocessor.filter.TrackingFocusFrameListener;
import com.android.camera.imageprocessor.filter.UbifocusFilter;
import com.android.camera.ui.ListMenu;
+import com.android.camera.ui.PanoCaptureProcessView;
import com.android.camera.util.SettingTranslation;
import org.codeaurora.snapcam.R;
@@ -80,6 +81,7 @@ public class SettingsManager implements ListMenu.SettingsListener {
public static final int SCENE_MODE_OPTIZOOM_INT = 101;
public static final int SCENE_MODE_UBIFOCUS_INT = 102;
public static final int SCENE_MODE_BESTPICTURE_INT = 103;
+ public static final int SCENE_MODE_PANORAMA_INT = 104;
public static final String SCENE_MODE_DUAL_STRING = "100";
public static final String KEY_CAMERA_SAVEPATH = "pref_camera2_savepath_key";
public static final String KEY_RECORD_LOCATION = "pref_camera2_recordlocation_key";
@@ -1019,6 +1021,7 @@ public class SettingsManager implements ListMenu.SettingsListener {
if (OptizoomFilter.isSupportedStatic()) modes.add(SCENE_MODE_OPTIZOOM_INT + "");
if (UbifocusFilter.isSupportedStatic() && cameraId == CaptureModule.BAYER_ID) modes.add(SCENE_MODE_UBIFOCUS_INT + "");
if (BestpictureFilter.isSupportedStatic() && cameraId == CaptureModule.BAYER_ID) modes.add(SCENE_MODE_BESTPICTURE_INT + "");
+ if (PanoCaptureProcessView.isSupportedStatic() && cameraId == CaptureModule.BAYER_ID) modes.add(SCENE_MODE_PANORAMA_INT + "");
for (int mode : sceneModes) {
modes.add("" + mode);
}
diff --git a/src/com/android/camera/ui/CameraControls.java b/src/com/android/camera/ui/CameraControls.java
index 48a5492e3..9039a9dce 100644
--- a/src/com/android/camera/ui/CameraControls.java
+++ b/src/com/android/camera/ui/CameraControls.java
@@ -53,6 +53,7 @@ public class CameraControls extends RotatableLayout {
private View mShutter;
private View mVideoShutter;
private View mSwitcher;
+ private View mExitPanorama;
private View mMenu;
private View mMute;
private View mFrontBackSwitcher;
@@ -84,9 +85,11 @@ public class CameraControls extends RotatableLayout {
private static final int INDICATOR_INDEX = 8;
private static final int MUTE_INDEX = 9;
private static final int VIDEO_SHUTTER_INDEX = 10;
+ private static final int EXIT_PANORAMA_INDEX = 11;
+ private static final int MAX_INDEX= 12;
private static final int ANIME_DURATION = 300;
- private float[][] mLocX = new float[4][11];
- private float[][] mLocY = new float[4][11];
+ private float[][] mLocX = new float[4][MAX_INDEX];
+ private float[][] mLocY = new float[4][MAX_INDEX];
private boolean mLocSet = false;
private boolean mHideRemainingPhoto = false;
private LinearLayout mRemainingPhotos;
@@ -131,6 +134,7 @@ public class CameraControls extends RotatableLayout {
mVideoShutter.setVisibility(View.INVISIBLE);
mMenu.setVisibility(View.INVISIBLE);
mMute.setVisibility(View.INVISIBLE);
+ mExitPanorama.setVisibility(View.INVISIBLE);
mIndicators.setVisibility(View.INVISIBLE);
mPreview.setVisibility(View.INVISIBLE);
isAnimating = false;
@@ -155,6 +159,7 @@ public class CameraControls extends RotatableLayout {
mVideoShutter.setVisibility(View.INVISIBLE);
mMenu.setVisibility(View.INVISIBLE);
mMute.setVisibility(View.INVISIBLE);
+ mExitPanorama.setVisibility(View.INVISIBLE);
mIndicators.setVisibility(View.INVISIBLE);
mPreview.setVisibility(View.INVISIBLE);
isAnimating = false;
@@ -162,6 +167,10 @@ public class CameraControls extends RotatableLayout {
}
};
+ public View getPanoramaExitButton() {
+ return mExitPanorama;
+ }
+
AnimatorListener inlistener = new AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
@@ -215,6 +224,7 @@ public class CameraControls extends RotatableLayout {
mSwitcher.setPressed(false);
mMenu.setPressed(false);
mMute.setPressed(false);
+ mExitPanorama.setPressed(false);
mFrontBackSwitcher.setPressed(false);
if(TsMakeupManager.HAS_TS_MAKEUP) {
mTsMakeupSwitcher.setPressed(false);
@@ -230,6 +240,7 @@ public class CameraControls extends RotatableLayout {
((ModuleSwitcher) mSwitcher).enableTouch(enable);
mMenu.setEnabled(enable);
mMute.setEnabled(enable);
+ mExitPanorama.setEnabled(enable);
mFrontBackSwitcher.setEnabled(enable);
if(TsMakeupManager.HAS_TS_MAKEUP) {
mTsMakeupSwitcher.setEnabled(enable);
@@ -264,10 +275,10 @@ public class CameraControls extends RotatableLayout {
mViewList.add(mMenu);
if (mMute.getVisibility() == View.VISIBLE)
mViewList.add(mMute);
+ if (mExitPanorama.getVisibility() == View.VISIBLE)
+ mViewList.add(mExitPanorama);
if (mIndicators.getVisibility() == View.VISIBLE)
mViewList.add(mIndicators);
- if (mMute.getVisibility() == View.VISIBLE)
- mViewList.add(mMute);
}
public void removeFromViewList(View view) {
@@ -291,6 +302,8 @@ public class CameraControls extends RotatableLayout {
}
mMenu = findViewById(R.id.menu);
mMute = findViewById(R.id.mute_button);
+ mExitPanorama = findViewById(R.id.exit_panorama);
+ mExitPanorama.setVisibility(View.GONE);
mIndicators = findViewById(R.id.on_screen_indicators);
mPreview = findViewById(R.id.preview_thumb);
mSceneModeSwitcher = findViewById(R.id.scene_mode_switcher);
@@ -371,6 +384,7 @@ public class CameraControls extends RotatableLayout {
toIndex(mVideoShutter, w, h, rotation, 3, 6, VIDEO_SHUTTER_INDEX);
toIndex(mMenu, w, h, rotation, 4, 0, MENU_INDEX);
toIndex(mMute, w, h, rotation, 3, 0, MUTE_INDEX);
+ toIndex(mExitPanorama, w, h, rotation, 0, 0, EXIT_PANORAMA_INDEX);
toIndex(mIndicators, w, h, rotation, 0, 6, INDICATOR_INDEX);
toIndex(mFrontBackSwitcher, w, h, rotation, 2, 0, FRONT_BACK_INDEX);
toIndex(mPreview, w, h, rotation, 0, 6, PREVIEW_INDEX);
@@ -481,6 +495,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.setX(mLocX[idx1][FILTER_MODE_INDEX] + x);
mMenu.setX(mLocX[idx1][MENU_INDEX] + x);
mMute.setX(mLocX[idx1][MUTE_INDEX] + x);
+ mExitPanorama.setX(mLocX[idx1][EXIT_PANORAMA_INDEX] + x);
mSwitcher.setX(mLocX[idx1][SWITCHER_INDEX] - x);
mShutter.setX(mLocX[idx1][SHUTTER_INDEX] - x);
mVideoShutter.setX(mLocX[idx1][VIDEO_SHUTTER_INDEX] - x);
@@ -497,6 +512,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.setY(mLocY[idx1][FILTER_MODE_INDEX] + y);
mMenu.setY(mLocY[idx1][MENU_INDEX] + y);
mMute.setY(mLocY[idx1][MUTE_INDEX] + y);
+ mExitPanorama.setY(mLocY[idx1][EXIT_PANORAMA_INDEX] + y);
mSwitcher.setY(mLocY[idx1][SWITCHER_INDEX] - y);
mShutter.setY(mLocY[idx1][SHUTTER_INDEX] - y);
mVideoShutter.setY(mLocY[idx1][VIDEO_SHUTTER_INDEX] - y);
@@ -534,6 +550,7 @@ public class CameraControls extends RotatableLayout {
mVideoShutter.animate().cancel();
mMenu.animate().cancel();
mMute.animate().cancel();
+ mExitPanorama.animate().cancel();
mIndicators.animate().cancel();
mPreview.animate().cancel();
mFrontBackSwitcher.animate().setListener(outlistener);
@@ -552,6 +569,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mMenu.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mMute.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
+ mExitPanorama.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
@@ -570,6 +588,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mMenu.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mMute.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
+ mExitPanorama.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
@@ -588,6 +607,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mMenu.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mMute.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
+ mExitPanorama.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
@@ -606,6 +626,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mMenu.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mMute.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
+ mExitPanorama.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
@@ -636,6 +657,7 @@ public class CameraControls extends RotatableLayout {
mVideoShutter.animate().cancel();
mMenu.animate().cancel();
mMute.animate().cancel();
+ mExitPanorama.animate().cancel();
mIndicators.animate().cancel();
mPreview.animate().cancel();
if (mViewList != null)
@@ -666,6 +688,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mMenu.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mMute.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
+ mExitPanorama.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
@@ -686,6 +709,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mMenu.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mMute.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
+ mExitPanorama.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
@@ -706,6 +730,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mMenu.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mMute.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
+ mExitPanorama.animate().translationYBy(-mSize).setDuration(ANIME_DURATION);
mSwitcher.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationYBy(mSize).setDuration(ANIME_DURATION);
@@ -726,6 +751,7 @@ public class CameraControls extends RotatableLayout {
mFilterModeSwitcher.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mMenu.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mMute.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
+ mExitPanorama.animate().translationXBy(-mSize).setDuration(ANIME_DURATION);
mSwitcher.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
mShutter.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
@@ -1001,7 +1027,7 @@ public class CameraControls extends RotatableLayout {
mSceneModeSwitcher, mFilterModeSwitcher, mFrontBackSwitcher,
TsMakeupManager.HAS_TS_MAKEUP ? mTsMakeupSwitcher : mHdrSwitcher,
mMenu, mShutter, mPreview, mSwitcher, mMute, mReviewRetakeButton,
- mReviewCancelButton, mReviewDoneButton
+ mReviewCancelButton, mReviewDoneButton, mExitPanorama
};
for (View v : views) {
if (v != null) {
diff --git a/src/com/android/camera/ui/ModuleSwitcher.java b/src/com/android/camera/ui/ModuleSwitcher.java
index 603f81d4d..d96775d14 100644
--- a/src/com/android/camera/ui/ModuleSwitcher.java
+++ b/src/com/android/camera/ui/ModuleSwitcher.java
@@ -54,6 +54,7 @@ public class ModuleSwitcher extends RotateImageView
public static final int LIGHTCYCLE_MODULE_INDEX = 3;
public static final int GCAM_MODULE_INDEX = 4;
public static final int CAPTURE_MODULE_INDEX = 5;
+ public static final int PANOCAPTURE_MODULE_INDEX = 6;
private boolean mTouchEnabled = true;
private boolean mIsVisible = true;
@@ -140,6 +141,8 @@ public class ModuleSwitcher extends RotateImageView
mCurrentIndex = i;
if (i == GCAM_MODULE_INDEX) {
setImageResource(R.drawable.ic_switch_camera);
+ } if (i== PANOCAPTURE_MODULE_INDEX) {
+ return;
} else {
setImageResource(mDrawIds[i]);
}
diff --git a/src/com/android/camera/ui/PanoCaptureProcessView.java b/src/com/android/camera/ui/PanoCaptureProcessView.java
new file mode 100644
index 000000000..724703b10
--- /dev/null
+++ b/src/com/android/camera/ui/PanoCaptureProcessView.java
@@ -0,0 +1,1097 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.camera.ui;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import com.android.camera.CameraActivity;
+import com.android.camera.PanoCaptureModule;
+import com.android.camera.exif.ExifInterface;
+import com.android.camera.util.CameraUtil;
+
+import org.codeaurora.snapcam.R;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Objects;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class PanoCaptureProcessView extends View implements SensorEventListener {
+ private static final int DEG_INIT_VALUE = 365;
+ private float mCurrDegX = DEG_INIT_VALUE;
+ private float mCurrDegY = DEG_INIT_VALUE;
+ private final static int OBJ_DEPTH = 800;
+ private RectF rectF = new RectF();
+ private CameraActivity mActivity;
+ private PanoCaptureModule mController;
+ private Matrix matrix = new Matrix();
+ private SensorManager mSensorManager;
+ private Sensor mRotationSensor;
+ private float[] mRots = new float[5];
+ private float[] mOldRots = new float[5];
+ private float[] mR = new float[9];
+ private float[] mRR = new float[9];
+ private float[] mOrients = new float[3];
+ private int mOrientation;
+ private int mPendingOrientation;
+ private Bitmap tmpBitmap;
+ private Paint mCenterRectPaint = new Paint();
+
+ public static int mPreviewThumbWidth;
+ public static int mPreviewThumbHeight;
+ private static float mPanoPreviewRatioToCamera;
+ public static int mFinalPictureWidth;
+ public static int mFinalPictureHeight;
+ private static float mFinalPictureRatioToCamera;
+ private Picture mPreviewPicture;
+ private int[] mAargbBuffer;
+ private byte[] mDataBuffer;
+ public static int MAX_PANO_FRAME = 6;
+ private static String TAG = "PanoramaCapture";
+ private Bitmap mTempBitmap;
+ private int mTempOrietnation;
+ private Picture mGuidePicture;
+ private Handler mHandler;
+ private final static int DIRECTION_GOT_LOST = -1;
+ private final static int DIRECTION_LEFTRIGHT = 0;
+ private final static int DIRECTION_UPDOWN = 1;
+ private int mDir = DIRECTION_LEFTRIGHT;
+ private boolean mShouldFinish = false;
+ private static final boolean DEBUG = false; //TODO: This has to be false before release
+ private BitmapArrayOutputStream mBitmapStream;
+ private static boolean mIsSupported = false;
+
+ private boolean mIsFrameProcessing = false;
+ enum PANO_STATUS {
+ INACTIVE,
+ ACTIVE_UNKNOWN,
+ ACTIVE_LEFT,
+ ACTIVE_RIGHT,
+ ACTIVE_UP,
+ ACTIVE_DOWN,
+
+ /* These two statuses below are for showing the background thread is running
+ while subsequent frame input will be ignored and passed.
+ */
+ COMPLETING,
+ OPENING
+ };
+ private PANO_STATUS mPanoStatus = PANO_STATUS.INACTIVE;
+ private Object mPreviewBitmapLock = new Object();
+ private static int DECISION_MARGIN;
+ private boolean mIsFirstBlend;
+ private PanoQueueProcessor mQueueProcessor;
+ private ProgressDialog mProgressDialog;
+ private String mCompleteSentence = "";
+ private String mProgressSentence = "";
+ private Paint mCompleteSentencePaint = new Paint();
+ private int mFinalDoneLength;
+
+ class Picture {
+ Bitmap bitmap;
+ Bitmap bitmapIn;
+ float xDeg;
+ float yDeg;
+ int xPos;
+ int yPos;
+ int leftIn;
+ int topIn;
+ int width;
+ int height;
+ Matrix mat;
+ RectF rF;
+ float[] pts;
+ Paint paintInAir = new Paint();
+ Paint paintFrameEdge = new Paint();
+
+ public Picture(Bitmap bm, float xdeg, float ydeg, int x, int y) {
+ init(bm, xdeg, ydeg, x, y);
+ }
+
+ public Picture(Bitmap bm, float xdeg, float ydeg, int x, int y, int w, int h) {
+ init(bm, xdeg, ydeg, x, y);
+ width = w;
+ height = h;
+ }
+
+ private void init(Bitmap bm, float xdeg, float ydeg, int x, int y) {
+ bitmap = bm;
+ xDeg = xdeg;
+ yDeg = ydeg;
+ xPos = x;
+ yPos = y;
+ mat = new Matrix();
+ rF = new RectF();
+ pts = new float[8];
+ paintInAir.setAlpha(124);
+ if(bm != null) {
+ width = bm.getWidth();
+ height = bm.getHeight();
+ }
+ paintFrameEdge.setColor(Color.WHITE);
+ paintFrameEdge.setStrokeWidth(2f);
+ paintFrameEdge.setStyle(Paint.Style.STROKE);
+ }
+
+ public void drawPictureInAir(Canvas canvas) {
+ float setha, x, y;
+
+ setha = ((xDeg - mCurrDegX) + 360) % 360;
+ if(90 <= setha && setha <= 270)
+ return;
+ x = (OBJ_DEPTH * (float) Math.sin(Math.toRadians(setha)));
+ setha = ((yDeg - mCurrDegY) + 360) % 360;
+ if(90 <= setha && setha <= 270)
+ return;
+ y = (OBJ_DEPTH * (float) Math.sin(Math.toRadians(setha)));
+
+ rF.left = canvas.getWidth()/2 + x - bitmap.getWidth()/2;
+ rF.right = canvas.getWidth()/2 + x + bitmap.getWidth()/2;
+ rF.top = canvas.getHeight()/2 + y - bitmap.getHeight()/2;
+ rF.bottom = canvas.getHeight()/2 + y + bitmap.getHeight()/2;
+ skew(rF, pts, x, y, canvas.getWidth() / 2, canvas.getHeight() / 2);
+ mat.reset();
+ mat.setPolyToPoly(new float[]{rF.left, rF.top,
+ rF.right, rF.top,
+ rF.right, rF.bottom,
+ rF.left, rF.bottom},
+ 0,
+ pts,
+ 0,
+ 4
+ );
+ canvas.translate(rF.left, rF.top);
+ canvas.drawBitmap(bitmap, mat, paintInAir);
+ }
+
+ public void drawGuideInAir(Canvas canvas) {
+ float setha, x, y;
+
+ setha = ((xDeg - mCurrDegX) + 360) % 360;
+ if(90 <= setha && setha <= 270)
+ return;
+ x = (OBJ_DEPTH * (float) Math.sin(Math.toRadians(setha)));
+ setha = ((yDeg - mCurrDegY) + 360) % 360;
+ if(90 <= setha && setha <= 270)
+ return;
+ y = (OBJ_DEPTH * (float) Math.sin(Math.toRadians(setha)));
+
+ rF.left = canvas.getWidth()/2 + x - width;
+ rF.right = canvas.getWidth()/2 + x + width;
+ rF.top = canvas.getHeight()/2 + y - height;
+ rF.bottom = canvas.getHeight()/2 + y + height;
+ skew(rF, pts, x, y, canvas.getWidth() / 2, canvas.getHeight() / 2);
+ for(int i=1; i < 4; i++) {
+ canvas.drawLine(pts[2*(i-1)], pts[2*(i-1)+1], pts[2*i], pts[2*i+1], paintFrameEdge);
+ }
+ canvas.drawLine(pts[0], pts[1], pts[6], pts[7], paintFrameEdge);
+ }
+
+ public void drawMasterPanoPreview(Canvas canvas) {
+ int bitmapWidth;
+ int bitmapHeight;
+ if(mPanoStatus == PANO_STATUS.ACTIVE_LEFT || mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) {
+ bitmapWidth = mPreviewPicture.bitmap.getWidth();
+ bitmapHeight = mPreviewPicture.bitmap.getHeight();
+ rectF.left = canvas.getWidth() / 2 - bitmapWidth / 2;
+ rectF.right = canvas.getWidth() / 2 + bitmapWidth / 2;
+ rectF.top = canvas.getHeight() * 4 / 5 - bitmapHeight;
+ rectF.bottom = canvas.getHeight() * 4 / 5;
+ canvas.drawBitmap(mPreviewPicture.bitmap, null, rectF, null);
+ canvas.drawRect(rectF, paintFrameEdge);
+
+ } else if(mPanoStatus == PANO_STATUS.ACTIVE_UP || mPanoStatus == PANO_STATUS.ACTIVE_DOWN) {
+ bitmapWidth = mPreviewPicture.bitmap.getWidth();
+ bitmapHeight = mPreviewPicture.bitmap.getHeight();
+ rectF.left = canvas.getWidth() / 4 - bitmapWidth / 2;
+ rectF.right = canvas.getWidth() / 4 + bitmapWidth / 2;
+ rectF.top = canvas.getHeight() / 2 - bitmapHeight / 2;
+ rectF.bottom = canvas.getHeight() / 2 + bitmapHeight / 2;
+ canvas.drawBitmap(mPreviewPicture.bitmap, null, rectF, null);
+ canvas.drawRect(rectF, paintFrameEdge);
+ }
+ if(mOrientation == 0 || mOrientation == 180) {
+ rectF.left += leftIn;
+ rectF.right = rectF.left + mPreviewThumbWidth;
+ rectF.top += topIn;
+ rectF.bottom = rectF.top + mPreviewThumbHeight;
+ } else {
+ rectF.left += leftIn;
+ rectF.right = rectF.left + mPreviewThumbHeight;
+ rectF.top += topIn;
+ rectF.bottom = rectF.top + mPreviewThumbWidth;
+ }
+ canvas.drawBitmap(mPreviewPicture.bitmapIn, null, rectF, null);
+ canvas.drawRect(rectF, paintFrameEdge);
+ }
+
+ private void skew(RectF src, float[] pts, float x, float y, float Wh, float Hh) {
+ float lh = src.height(); //Left height
+ float tw = src.width(); //Top width
+ float rh = lh; //Right height
+ float bw = tw; //Bottom width
+
+ if(x < 0) {
+ lh = lh * ((-x/Wh)/2f + 1);
+ } else {
+ rh = rh * ((x/Wh)/2f + 1);
+ }
+
+ if(y < 0) {
+ tw = tw * ((-y/Hh)/2f + 1);
+ } else {
+ bw = bw * ((y/Hh)/2f + 1);
+ }
+
+ //Left Top
+ pts[0] = src.centerX() - tw/2;
+ pts[1] = src.centerY() - lh/2;
+ //Right Top
+ pts[2] = src.centerX() + tw/2;
+ pts[3] = src.centerY() - rh/2;
+ //Right Bottom
+ pts[4] = src.centerX() + bw/2;
+ pts[5] = src.centerY() + rh/2;
+ //Left Bottom
+ pts[6] = src.centerX() - bw/2;
+ pts[7] = src.centerY() + lh/2;
+ }
+ }
+
+ public void setContext(CameraActivity activity, PanoCaptureModule contoller) {
+ mActivity = activity;
+ mController = contoller;
+ mSensorManager = (SensorManager) mActivity.getSystemService(Context.SENSOR_SERVICE);
+ mRotationSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
+ mCenterRectPaint.setColor(Color.CYAN);
+ mCenterRectPaint.setStrokeWidth(2f);
+ mCenterRectPaint.setStyle(Paint.Style.STROKE);
+ mCompleteSentencePaint.setColor(Color.WHITE);
+ mCompleteSentencePaint.setTextSize(60f);
+ mQueueProcessor = new PanoQueueProcessor();
+ mQueueProcessor.start();
+ mHandler = new Handler();
+ }
+
+ public void onPause() {
+ mSensorManager.unregisterListener(this, mRotationSensor);
+ if(mBitmapStream != null) {
+ try {
+ mBitmapStream.close();
+ } catch (IOException e) {
+ //Ignore
+ }
+ mBitmapStream = null;
+ }
+ }
+
+ public void onResume() {
+ mSensorManager.registerListener(this, mRotationSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ public void setPanoPreviewSize(int width, int height, int cameraWidth, int cameraHeight) {
+ mPreviewThumbWidth = width / (PanoCaptureProcessView.MAX_PANO_FRAME+2) / 2 * 2;
+ mPreviewThumbHeight = height / (PanoCaptureProcessView.MAX_PANO_FRAME+2) / 2 * 2;
+ mFinalPictureWidth = width / 2 * 2;
+ mFinalPictureHeight = height / 2 * 2;
+ mAargbBuffer = new int[mPreviewThumbWidth * mPreviewThumbHeight];
+ mDataBuffer = new byte[mPreviewThumbWidth * mPreviewThumbHeight * 3 / 2];
+
+ DECISION_MARGIN = (int)(0.2 * mPreviewThumbHeight);
+
+ mPanoPreviewRatioToCamera = (float)Math.min(mPreviewThumbWidth, mPreviewThumbHeight) /
+ (float)Math.min(cameraWidth, cameraHeight);
+ mFinalPictureRatioToCamera = (float)Math.min(mFinalPictureWidth, mFinalPictureHeight) /
+ (float)Math.min(cameraWidth, cameraHeight);
+ }
+
+ public PanoCaptureProcessView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if(mPanoStatus != PANO_STATUS.INACTIVE) {
+ canvas.rotate(-mOrientation, canvas.getWidth() / 2, canvas.getHeight() / 2);
+
+ if (mOrientation == 0 || mOrientation == 180) {
+ rectF.left = canvas.getWidth() / 2 - mPreviewThumbWidth;
+ rectF.right = canvas.getWidth() / 2 + mPreviewThumbWidth;
+ rectF.top = canvas.getHeight() / 2 - mPreviewThumbHeight;
+ rectF.bottom = canvas.getHeight() / 2 + mPreviewThumbHeight;
+ } else {
+ rectF.left = canvas.getWidth() / 2 - mPreviewThumbHeight;
+ rectF.right = canvas.getWidth() / 2 + mPreviewThumbHeight;
+ rectF.top = canvas.getHeight() / 2 - mPreviewThumbWidth;
+ rectF.bottom = canvas.getHeight() / 2 + mPreviewThumbWidth;
+ }
+
+ if(!mProgressSentence.equals("")) {
+ int textWidth = (int) mCompleteSentencePaint.measureText(mProgressSentence);
+ canvas.drawText(mProgressSentence, rectF.centerX() - textWidth / 2, canvas.getHeight()/4, mCompleteSentencePaint);
+ }
+
+ if(mPanoStatus == PANO_STATUS.COMPLETING) {
+ int textWidth = (int) mCompleteSentencePaint.measureText(mCompleteSentence);
+ canvas.drawText(mCompleteSentence, rectF.centerX() - textWidth / 2, rectF.centerY(), mCompleteSentencePaint);
+ } else {
+ //Draw Aiming rectangle at the center
+ canvas.drawRect(rectF, mCenterRectPaint);
+
+ //Draw the guide frames
+ if(mGuidePicture != null) {
+ canvas.save();
+ mGuidePicture.drawGuideInAir(canvas);
+ canvas.restore();
+ }
+
+ //Blended pano preview
+ synchronized (mPreviewBitmapLock) {
+ if (mPreviewPicture != null) {
+ mPreviewPicture.drawMasterPanoPreview(canvas);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e){
+ return true;
+ }
+
+ private void bitmapToDataNV21(Bitmap bitmap) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ int y = 0;
+ int u = w*h;
+ int a, R, G, B, Y, U, V;
+ int index = 0;
+
+ bitmap.getPixels(mAargbBuffer, 0, w, 0, 0, w, h);
+ for (int j = 0; j < h; j++) {
+ for (int i = 0; i < w; i++) {
+
+ a = (mAargbBuffer[index] & 0xff000000) >> 24;
+ R = (mAargbBuffer[index] & 0xff0000) >> 16;
+ G = (mAargbBuffer[index] & 0xff00) >> 8;
+ B = (mAargbBuffer[index] & 0xff) >> 0;
+
+ Y = ( ( 66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
+ U = ( ( -38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
+ V = ( ( 112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
+
+ mDataBuffer[y++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
+ if (j % 2 == 0 && index % 2 == 0) {
+ mDataBuffer[u++] = (byte)((V<0) ? 0 : ((V > 255) ? 255 : V));
+ mDataBuffer[u++] = (byte)((U<0) ? 0 : ((U > 255) ? 255 : U));
+ }
+ index ++;
+ }
+ }
+ }
+
+ class PanoQueueProcessor extends Thread {
+ private ArrayBlockingQueue<BitmapTask> queue;
+ private Object lock = new Object();
+ public PanoQueueProcessor() {
+ queue = new ArrayBlockingQueue<BitmapTask>(MAX_PANO_FRAME);
+ }
+
+ private void waitTillNotFull() {
+ while(true) {
+ if (queue.size() < MAX_PANO_FRAME) {
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ while(true) {
+ try {
+ BitmapTask bt = queue.take();
+ if(mShouldFinish)
+ continue;
+ synchronized (lock) {
+ doTask(bt);
+ }
+ } catch (InterruptedException e) {
+ //Ignore
+ }
+ }
+ }
+
+ public boolean isEmpty() {
+ synchronized (lock) {
+ if(!queue.isEmpty()) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ public void queueClear() {
+ this.interrupt();
+ queue.clear();
+ }
+
+ //This function is the only one running on UI thread.
+ public void addTask(Bitmap bitmap, int x, int y, int dir) {
+ waitTillNotFull();
+ BitmapTask bt = new BitmapTask(bitmap, x, y, dir);
+ queue.add(bt);
+ }
+
+ private void doTask(BitmapTask bitmapTask) {
+ if(mBitmapStream == null) {
+ mBitmapStream = new BitmapArrayOutputStream(1024*1204);
+ }
+ mBitmapStream.reset();
+ bitmapTask.bitmap.compress(Bitmap.CompressFormat.JPEG, 100, mBitmapStream);
+ int rtv = callNativeProcessKeyFrame(mBitmapStream.toByteArray(), mBitmapStream.size(),
+ bitmapTask.x, bitmapTask.y, 0, bitmapTask.dir);
+ if(rtv < 0) {
+ mShouldFinish = true;
+ stopPano(false, "The direction is changed. Stopping.");
+ Log.w(TAG, "Keyframe return value: "+rtv);
+ }
+ bitmapTask.clear();
+ }
+ }
+
+ class BitmapArrayOutputStream extends ByteArrayOutputStream {
+
+ public BitmapArrayOutputStream(int size) {
+ super(size);
+ }
+
+ @Override
+ public synchronized byte[] toByteArray() {
+ return buf;
+ }
+
+ @Override
+ public void close() throws IOException{
+ super.close();
+ buf = null;
+ }
+ }
+
+ class BitmapTask {
+ Bitmap bitmap;
+ int x;
+ int y;
+ int dir;
+ public BitmapTask(Bitmap orgBitmap, int x, int y, int dir) {
+ Bitmap newBitmap;
+ if (mOrientation == 0 || mOrientation == 180) {
+ newBitmap = Bitmap.createBitmap(mFinalPictureWidth, mFinalPictureHeight, Bitmap.Config.ARGB_8888);
+ } else {//if(mOrientation == 90 || mOrientation == 270)
+ newBitmap = Bitmap.createBitmap(mFinalPictureHeight, mFinalPictureWidth, Bitmap.Config.ARGB_8888);
+ }
+ rotateAndScale(orgBitmap, newBitmap, mFinalPictureRatioToCamera);
+ this.bitmap = newBitmap;
+ this.x = x;
+ this.y = y;
+ this.dir = dir;
+ }
+ public void clear() {
+ this.bitmap.recycle();
+ }
+ }
+
+ private void waitForQueueDone() {
+ while(true) {
+ if(mQueueProcessor.isEmpty()) return;
+ try {
+ Thread.sleep(10);
+ } catch(InterruptedException e) {
+ //Ignore
+ }
+ }
+ }
+
+ private void processPreviewFrame(boolean[] isKey, int[] framePos, int[] moveSpeed) {
+ if (callNativeProcessPreviewFrame(mDataBuffer, isKey, framePos, moveSpeed) < 0) {
+ Log.e(TAG, "Preview processing is failed.");
+ }
+ }
+
+ public boolean isPanoCompleting() {
+ return (mPanoStatus == PANO_STATUS.COMPLETING);
+ }
+
+ public boolean isFrameProcessing() {
+ return mIsFrameProcessing;
+ }
+
+ /*
+ * bitmap will be kept to use further.
+ */
+ public void onFrameAvailable(final Bitmap bitmap, final boolean isCancelling) {
+ if(mPanoStatus == PANO_STATUS.COMPLETING || mPanoStatus == PANO_STATUS.OPENING) {
+ return;
+ }
+ if(bitmap == null) {
+ if(isCancelling) {
+ mCompleteSentence = "Cancelling...";
+ } else {
+ mCompleteSentence = "Processing...";
+ }
+ mPanoStatus = PANO_STATUS.COMPLETING;
+ invalidate();
+ mHandler.post(new Runnable() {
+ public void run() {
+ if(mPreviewPicture != null) {
+ waitForQueueDone();
+ if(!isCancelling) {
+ int size = callNativeGetResultSize();
+ if (size <= 0) {
+ callNativeCancelPanorama();
+ } else {
+ byte[] jpegData = new byte[size];
+ callNativeCompletePanorama(jpegData, size);
+ int orient = 270;
+ if(mDir == DIRECTION_UPDOWN) {
+ orient = 0;
+ }
+ final Uri uri = mController.savePanorama(jpegData, mFinalPictureWidth*8, mFinalPictureHeight, orient);
+ Bitmap bm = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
+ final Bitmap thumbBitmap = CameraUtil.rotate(bm, orient);
+ if(uri != null) {
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.updateThumbnail(thumbBitmap);
+ mActivity.notifyNewMedia(uri);
+ }
+ });
+ } else {
+ Log.d(TAG, "Image uri is null, size : "+size+" jpegData: "+jpegData);
+ }
+ }
+ } else {
+ callNativeCancelPanorama();
+ mQueueProcessor.queueClear();
+ }
+ } else {
+ callNativeCancelPanorama();
+ }
+ synchronized (mPreviewBitmapLock) {
+ if(mPreviewPicture != null) {
+ mPreviewPicture.bitmap.recycle();
+ mPreviewPicture.bitmapIn.recycle();
+ }
+ mPreviewPicture = null;
+ mGuidePicture = null;
+ }
+ callNativeInstanceRelease();
+ mPanoStatus = PANO_STATUS.INACTIVE;
+ mShouldFinish = false;
+ mOrientation = mPendingOrientation;
+ }
+ });
+ return;
+ }
+ if(mPanoStatus == PANO_STATUS.INACTIVE) {
+ mPanoStatus = PANO_STATUS.OPENING;
+ mHandler.post(new Runnable() {
+ public void run() {
+ int width;
+ int height;
+ if (mOrientation == 0 || mOrientation == 180) {
+ width = mPreviewThumbWidth;
+ height = mPreviewThumbHeight;
+ } else {
+ width = mPreviewThumbHeight;
+ height = mPreviewThumbWidth;
+ }
+ if(callNativeInstanceInit(width, height, width, 0, 1) < 0) {
+ Log.e(TAG,"Failed to create panorama native instance");
+ mPanoStatus = PANO_STATUS.INACTIVE;
+ mOrientation = mPendingOrientation;
+ return;
+ }
+ mPanoStatus = PANO_STATUS.ACTIVE_UNKNOWN;
+ }
+ });
+ return;
+ }
+
+ if(mPanoStatus != PANO_STATUS.INACTIVE)
+ {
+ if(mIsFrameProcessing) {
+ return;
+ }
+ mIsFrameProcessing = true;
+ mHandler.post(new Runnable() {
+ public void run() {
+ Picture picture;
+ if (mTempBitmap == null || mTempOrietnation != mOrientation) {
+ if (mOrientation == 0 || mOrientation == 180) {
+ mTempBitmap = Bitmap.createBitmap(mPreviewThumbWidth, mPreviewThumbHeight, Bitmap.Config.ARGB_8888);
+ } else {//if(mOrientation == 90 || mOrientation == 270)
+ mTempBitmap = Bitmap.createBitmap(mPreviewThumbHeight, mPreviewThumbWidth, Bitmap.Config.ARGB_8888);
+ }
+ mTempOrietnation = mOrientation;
+ }
+ rotateAndScale(bitmap, mTempBitmap, mPanoPreviewRatioToCamera);
+ bitmapToDataNV21(mTempBitmap);
+ boolean[] isKey = new boolean[1];
+ int[] framePos = new int[3];
+ int[] moveSpeed = new int[1];
+ processPreviewFrame(isKey, framePos, moveSpeed);
+ if (framePos[2] == DIRECTION_GOT_LOST) {
+ mProgressSentence = mActivity.getResources().getString(R.string.panocapture_direction_is_not_determined);
+ } else {
+ mProgressSentence = "";
+ mDir = framePos[2];
+ }
+
+ if (isKey[0]) {
+ mQueueProcessor.addTask(bitmap, framePos[0], framePos[1], framePos[2]);
+ }
+ picture = new Picture(mTempBitmap, mCurrDegX, mCurrDegY, framePos[0], framePos[1]);
+ if (mPanoStatus == PANO_STATUS.ACTIVE_UNKNOWN) {
+ if (framePos[0] < -DECISION_MARGIN) {
+ mPanoStatus = PANO_STATUS.ACTIVE_RIGHT;
+ } else if (framePos[0] > DECISION_MARGIN) {
+ mPanoStatus = PANO_STATUS.ACTIVE_LEFT;
+ } else if (framePos[1] < -DECISION_MARGIN) {
+ mPanoStatus = PANO_STATUS.ACTIVE_DOWN;
+ } else if (framePos[1] > DECISION_MARGIN) {
+ mPanoStatus = PANO_STATUS.ACTIVE_UP;
+ }
+ }
+ if (mPreviewPicture == null && mPanoStatus != PANO_STATUS.ACTIVE_UNKNOWN) {
+ Picture masterPicture;
+ Bitmap masterBitmap;
+ Bitmap liveBitmap;
+ if (mPanoStatus == PANO_STATUS.ACTIVE_RIGHT || mPanoStatus == PANO_STATUS.ACTIVE_LEFT) {
+ if (mOrientation == 0 || mOrientation == 180) {
+ mFinalDoneLength = mPreviewThumbWidth * MAX_PANO_FRAME;
+ masterBitmap = Bitmap.createBitmap(mFinalDoneLength, mPreviewThumbHeight, Bitmap.Config.ARGB_8888);
+ liveBitmap = Bitmap.createBitmap(mPreviewThumbWidth, mPreviewThumbHeight, Bitmap.Config.ARGB_8888);
+ } else {//if(mOrientation == 90 || mOrientation == 270)
+ mFinalDoneLength = mPreviewThumbHeight * MAX_PANO_FRAME;
+ masterBitmap = Bitmap.createBitmap(mFinalDoneLength, mPreviewThumbWidth, Bitmap.Config.ARGB_8888);
+ liveBitmap = Bitmap.createBitmap(mPreviewThumbHeight, mPreviewThumbWidth, Bitmap.Config.ARGB_8888);
+ }
+ } else { //UP or DOWN
+ if (mOrientation == 0 || mOrientation == 180) {
+ mFinalDoneLength = mPreviewThumbHeight * MAX_PANO_FRAME;
+ masterBitmap = Bitmap.createBitmap(mPreviewThumbWidth, mFinalDoneLength, Bitmap.Config.ARGB_8888);
+ liveBitmap = Bitmap.createBitmap(mPreviewThumbWidth, mPreviewThumbHeight, Bitmap.Config.ARGB_8888);
+ } else {//if(mOrientation == 90 || mOrientation == 270)
+ mFinalDoneLength = mPreviewThumbWidth * MAX_PANO_FRAME;
+ masterBitmap = Bitmap.createBitmap(mPreviewThumbHeight, mFinalDoneLength, Bitmap.Config.ARGB_8888);
+ liveBitmap = Bitmap.createBitmap(mPreviewThumbHeight, mPreviewThumbWidth, Bitmap.Config.ARGB_8888);
+ }
+ }
+ mGuidePicture = new Picture(null, mCurrDegX, mCurrDegY, 0, 0, liveBitmap.getWidth(), liveBitmap.getHeight());
+ masterPicture = new Picture(masterBitmap, mCurrDegX, mCurrDegY, 0, 0, 0, 0);
+ synchronized (mPreviewBitmapLock) {
+ mPreviewPicture = masterPicture;
+ mPreviewPicture.bitmapIn = liveBitmap;
+ }
+ mIsFirstBlend = true;
+ }
+ if (mPreviewPicture != null) {
+ blendToPreviewPicture(picture, isKey[0], mIsFirstBlend);
+ if (isAllTaken()) {
+ stopPano(false, null);
+ }
+ mIsFirstBlend = false;
+ }
+ mIsFrameProcessing = false;
+ }
+ });
+ }
+ }
+
+ private void stopPano(final boolean isCancelling, final String message) {
+ if(message != null) {
+ mProgressSentence = message;
+ Log.w(TAG, message);
+ }
+ mHandler.post(new Runnable() {
+ public void run() {
+ mController.changePanoStatus(false, isCancelling);
+ }
+ });
+ }
+
+ private boolean isAllTaken() {
+ if(mFinalDoneLength == 0) {
+ return false;
+ }
+ if(mPanoStatus == PANO_STATUS.ACTIVE_LEFT || mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) {
+ if(mPreviewPicture.width >= mFinalDoneLength) {
+ return true;
+ }
+ } else if(mPanoStatus == PANO_STATUS.ACTIVE_UP || mPanoStatus == PANO_STATUS.ACTIVE_DOWN) {
+ if(mPreviewPicture.height >= mFinalDoneLength) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void blendToPreviewPicture(Picture pic2, boolean isKey, boolean isFirst) {
+ Canvas canvas;
+ canvas = new Canvas(mPreviewPicture.bitmapIn);
+ canvas.drawBitmap(pic2.bitmap, 0, 0, null);
+ Picture pic1 = mPreviewPicture;
+ if(mPanoStatus == PANO_STATUS.ACTIVE_RIGHT || mPanoStatus == PANO_STATUS.ACTIVE_LEFT) {
+ int gap = pic2.xPos - pic1.xPos;
+ pic1.topIn = -pic2.yPos;
+ if((gap > 0 && mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) ||
+ gap < 0 && mPanoStatus == PANO_STATUS.ACTIVE_LEFT) {
+ return;
+ }
+ gap = pic2.width - Math.abs(gap);
+ if(isFirst) {
+ gap = 0;
+ }
+ int newWidth = pic1.width + pic2.width - gap;
+
+ if(mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) {
+ pic1.leftIn = newWidth - pic2.width;
+ } else {
+ pic1.leftIn = pic1.bitmap.getWidth() - newWidth;
+ }
+ if(pic1.leftIn < 0) {
+ pic1.leftIn = 0;
+ }
+ if(pic1.leftIn > pic1.bitmap.getWidth() - pic2.bitmap.getWidth()) {
+ pic1.leftIn = pic1.bitmap.getWidth() - pic2.bitmap.getWidth();
+ }
+
+ if(isKey || isFirst) {
+ canvas = new Canvas(pic1.bitmap);
+ canvas.drawBitmap(pic2.bitmap, pic1.leftIn, 0, null);
+ int overlapS, overlapE;
+ if (mPanoStatus == PANO_STATUS.ACTIVE_RIGHT) {
+ overlapS = newWidth - pic2.width;
+ overlapE = newWidth - pic2.width + gap;
+ } else {
+ overlapS = pic1.bitmap.getWidth() - newWidth + pic2.width - gap;
+ overlapE = pic1.bitmap.getWidth() - newWidth + pic2.width;
+ }
+ for (int i = overlapS; i < overlapE; i++) {
+ if (i >= pic1.bitmap.getWidth() || i - overlapS >= pic2.bitmap.getWidth())
+ break;
+ for (int j = 0; j < pic1.height; j++) {
+ if (j >= pic1.bitmap.getHeight() || j >= pic2.bitmap.getHeight())
+ break;
+ int iC1 = pic1.bitmap.getPixel(i, j);
+ int iC2 = pic2.bitmap.getPixel(i - overlapS, j);
+ int blendAlpha = (overlapE - i) / gap;
+ int or = blendAlpha * Color.red(iC1) + (1 - blendAlpha) * Color.red(iC2);
+ int og = blendAlpha * Color.green(iC1) + (1 - blendAlpha) * Color.green(iC2);
+ int ob = blendAlpha * Color.blue(iC1) + (1 - blendAlpha) * Color.blue(iC2);
+ int pixel = Color.argb(255, or, og, ob);
+ pic1.bitmap.setPixel(i, j, pixel);
+ }
+ }
+ }
+ pic1.width = newWidth;
+ } else { //UP or DOWN
+ int gap = pic2.yPos - pic1.yPos;
+ pic1.leftIn = -pic2.xPos;
+ if((gap > 0 && mPanoStatus == PANO_STATUS.ACTIVE_DOWN) ||
+ gap < 0 && mPanoStatus == PANO_STATUS.ACTIVE_UP) {
+ return;
+ }
+ gap = pic2.height - Math.abs(gap);
+ if(isFirst) {
+ gap = 0;
+ }
+ int newHeight = pic1.height + pic2.height - gap;
+
+ if(mPanoStatus == PANO_STATUS.ACTIVE_DOWN) {
+ pic1.topIn = newHeight - pic2.height;
+ } else {
+ pic1.topIn = pic1.bitmap.getHeight() - newHeight;
+ }
+ if(pic1.topIn < 0) {
+ pic1.topIn = 0;
+ }
+ if(pic1.topIn > pic1.bitmap.getHeight() - pic2.bitmap.getHeight()) {
+ pic1.topIn = pic1.bitmap.getHeight() - pic2.bitmap.getHeight();
+ }
+ if(isKey || isFirst) {
+ canvas = new Canvas(pic1.bitmap);
+ canvas.drawBitmap(pic2.bitmap, 0, pic1.topIn, null);
+ int overlapS, overlapE;
+ if (mPanoStatus == PANO_STATUS.ACTIVE_DOWN) {
+ overlapS = newHeight - pic2.height;
+ overlapE = newHeight - pic2.height + gap;
+ } else {
+ overlapS = pic1.bitmap.getHeight() - newHeight + pic2.height - gap;
+ overlapE = pic1.bitmap.getHeight() - newHeight + pic2.height;
+ }
+ for (int i = overlapS; i < overlapE; i++) {
+ if (i >= pic1.bitmap.getHeight() || i - overlapS >= pic2.bitmap.getHeight())
+ break;
+ for (int j = 0; j < pic1.width; j++) {
+ if (j >= pic1.bitmap.getWidth() || j >= pic2.bitmap.getWidth())
+ break;
+ int iC1 = pic1.bitmap.getPixel(j, i);
+ int iC2 = pic2.bitmap.getPixel(j, i - overlapS);
+ int blendAlpha = (overlapE - i) / gap;
+ int or = blendAlpha * Color.red(iC1) + (1 - blendAlpha) * Color.red(iC2);
+ int og = blendAlpha * Color.green(iC1) + (1 - blendAlpha) * Color.green(iC2);
+ int ob = blendAlpha * Color.blue(iC1) + (1 - blendAlpha) * Color.blue(iC2);
+ int pixel = Color.argb(255, or, og, ob);
+ pic1.bitmap.setPixel(j, i, pixel);
+ }
+ }
+ }
+ pic1.height = newHeight;
+ }
+ pic1.xPos = pic2.xPos;
+ pic1.yPos = pic2.yPos;
+ }
+
+ private void rotateAndScale(Bitmap srcBitmap, Bitmap dstBitmap, float ratio) {
+ Canvas canvas = new Canvas(dstBitmap);
+ matrix.reset();
+ if(mOrientation == 0 || mOrientation == 270) {
+ matrix.postRotate((90 + mOrientation + 360) % 360, srcBitmap.getHeight() / 2, srcBitmap.getHeight() / 2);
+ } else if (mOrientation == 180){
+ matrix.postRotate((90 + mOrientation + 180 + 360) % 360, srcBitmap.getHeight() / 2, srcBitmap.getHeight() / 2);
+ matrix.postRotate(180, srcBitmap.getHeight() / 2, srcBitmap.getWidth() / 2);
+ } else if(mOrientation == 90) {
+ matrix.postRotate((90 + mOrientation + 180 + 360) % 360, srcBitmap.getHeight() / 2, srcBitmap.getHeight() / 2);
+ matrix.postRotate(180, srcBitmap.getWidth() / 2, srcBitmap.getHeight() / 2);
+ }
+ matrix.postScale(ratio, ratio);
+ canvas.drawBitmap(srcBitmap, matrix, null);
+ }
+
+ public void setOrientation(int orientation) {
+ if(mPanoStatus != PANO_STATUS.INACTIVE) {
+ mPendingOrientation = orientation;
+ return;
+ }
+ mOrientation = mPendingOrientation = orientation;
+ }
+
+ private boolean isPortrait() {
+ if(mOrientation == 0 || mOrientation == 180) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ switch (event.sensor.getType()) {
+ case Sensor.TYPE_ROTATION_VECTOR:
+ System.arraycopy(event.values, 0, mOldRots, 0, event.values.length);
+ SensorManager.getRotationMatrixFromVector(mR, mOldRots);
+ if(isPortrait()) {
+ SensorManager.remapCoordinateSystem(mR,
+ SensorManager.AXIS_X, SensorManager.AXIS_Z,
+ mRR);
+ } else {
+ SensorManager.remapCoordinateSystem(mR,
+ SensorManager.AXIS_Z, SensorManager.AXIS_X,
+ mRR);
+ }
+ SensorManager.getOrientation(mRR, mOrients);
+ mCurrDegX = (((float) Math.toDegrees(mOrients[0]) + 360) % 360);
+ mCurrDegY = (((float) Math.toDegrees(mOrients[1]) + 360) % 360);
+ if(!isPortrait()) {
+ mCurrDegX = (mCurrDegX + 180) % 360;
+ mCurrDegY = (-mCurrDegY + 360) % 360;
+ }
+ invalidate();
+ break;
+ default:
+ return;
+ }
+ }
+
+ private void lowPassFilteredCopy( float[] a, float[] b) {
+ for ( int i=0; i < 3; i++ ) {
+ b[i] = b[i] + 0.45f * (a[i] - b[i]);
+ }
+ }
+
+ private void highPassFilteredCopy( float[] a, float[] b, float[] c, boolean isValid ) {
+ if(!isValid) {
+ System.arraycopy(a, 0, b, 0, a.length);
+ return;
+ }
+
+ for ( int i=0; i < a.length; i++ ) {
+ b[i] = 1.2f*(b[i] + a[i] - c[i]);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+ }
+
+ private int callNativeInstanceInit(int width, int height, int stride, int orientation, int colorFormat) {
+ if(DEBUG) {
+ Log.d(TAG, "native instance init");
+ }
+ int rtv = nativeInstanceInit(width, height, stride, orientation, colorFormat);
+ if(DEBUG) {
+ Log.d(TAG, "native instance init done");
+ }
+ return rtv;
+ }
+
+ private int callNativeInstanceRelease() {
+ if(DEBUG) {
+ Log.d(TAG, "native instance release");
+ }
+ int rtv = nativeInstanceRelease();
+ if(DEBUG) {
+ Log.d(TAG, "native instance release done");
+ }
+ return rtv;
+ }
+
+ private int callNativeProcessPreviewFrame(byte[] frameData ,boolean[] isKey , int[] framePosition , int[] moveSpeed) {
+ if(DEBUG) {
+ Log.d(TAG, "native process preview frame");
+ }
+ int rtv = nativeProcessPreviewFrame(frameData, isKey, framePosition, moveSpeed);
+ if(DEBUG) {
+ Log.d(TAG, "native process preview frame done");
+ }
+ return rtv;
+ }
+
+ private int callNativeProcessKeyFrame(byte[] jpegInData, int dataSize, int x, int y, int orientation, int direction) {
+ if(DEBUG) {
+ Log.d(TAG, "native process key frame");
+ }
+ int rtv = nativeProcessKeyFrame(jpegInData, dataSize, x, y, orientation, direction);
+ if(DEBUG) {
+ Log.d(TAG, "native process key frame done");
+ }
+ return rtv;
+ }
+
+ private int callNativeCancelPanorama() {
+ if(DEBUG) {
+ Log.d(TAG, "native cancel panorama");
+ }
+ int rtv = nativeCancelPanorama();
+ if(DEBUG) {
+ Log.d(TAG, "native cancel panorama done");
+ }
+ return rtv;
+ }
+
+ private int callNativeGetResultSize() {
+ if(DEBUG) {
+ Log.d(TAG, "native getResultSize");
+ }
+ int rtv = nativeGetResultSize();
+ if(DEBUG) {
+ Log.d(TAG, "native getResultSize done");
+ }
+ return rtv;
+ }
+
+ private int callNativeCompletePanorama(byte[] jpegOutData, int size) {
+ if(DEBUG) {
+ Log.d(TAG, "native complete panorama");
+ }
+ int rtv = nativeCompletePanorama(jpegOutData, size);
+ if(DEBUG) {
+ Log.d(TAG, "native complete panorama done");
+ }
+ return rtv;
+ }
+
+ public static boolean isSupportedStatic() {
+ return mIsSupported;
+ }
+
+ private native int nativeInstanceInit(int width, int height, int stride, int orientation, int colorFormat);
+ private native int nativeInstanceRelease();
+ private native int nativeProcessPreviewFrame(byte[] frameData ,boolean[] isKey , int[] framePosition , int[] moveSpeed);
+ private native int nativeProcessKeyFrame(byte[] jpegInData, int dataSize, int x, int y, int orientation, int direction);
+ private native int nativeCancelPanorama();
+ private native int nativeGetResultSize();
+ private native int nativeCompletePanorama(byte[] jpegOutData, int size);
+
+ static {
+ try {
+ mIsSupported = true;
+ System.loadLibrary("jni_panorama");
+ } catch(UnsatisfiedLinkError e) {
+ Log.e(TAG, e.toString());
+ mIsSupported = false;
+ }
+ }
+}