summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml2
-rw-r--r--res/layout/capture_module.xml4
-rw-r--r--res/layout/photo_module.xml2
-rw-r--r--res/layout/video_module.xml6
-rw-r--r--res/values/qcomarrays.xml20
-rw-r--r--res/values/qcomstrings.xml26
-rw-r--r--res/xml/camera_preferences.xml15
-rw-r--r--src/com/android/camera/CameraActivity.java10
-rw-r--r--src/com/android/camera/CameraSettings.java4
-rw-r--r--src/com/android/camera/CaptureMenu.java60
-rw-r--r--src/com/android/camera/CaptureModule.java1717
-rw-r--r--src/com/android/camera/CaptureUI.java99
-rw-r--r--src/com/android/camera/FocusStateListener.java66
-rw-r--r--src/com/android/camera/MediaSaveService.java127
-rw-r--r--src/com/android/camera/PhotoController.java2
-rw-r--r--src/com/android/camera/PhotoMenu.java14
-rw-r--r--src/com/android/camera/PhotoModule.java70
-rw-r--r--src/com/android/camera/PhotoUI.java67
-rw-r--r--src/com/android/camera/PreviewGestures.java3
-rw-r--r--src/com/android/camera/VideoMenu.java3
-rw-r--r--src/com/android/camera/VideoModule.java17
-rw-r--r--src/com/android/camera/VideoUI.java29
-rw-r--r--src/com/android/camera/exif/ExifTag.java20
-rw-r--r--src/com/android/camera/exif/JpegHeader.java3
-rw-r--r--src/com/android/camera/exif/OrderedDataOutputStream.java2
-rw-r--r--src/com/android/camera/mpo/MpoData.java184
-rw-r--r--src/com/android/camera/mpo/MpoIfdData.java126
-rw-r--r--src/com/android/camera/mpo/MpoImageData.java248
-rw-r--r--src/com/android/camera/mpo/MpoInterface.java143
-rw-r--r--src/com/android/camera/mpo/MpoOutputStream.java333
-rw-r--r--src/com/android/camera/mpo/MpoTag.java163
-rw-r--r--src/com/android/camera/ui/CameraControls.java15
-rw-r--r--src/com/android/camera/ui/ZoomRenderer.java31
-rw-r--r--src/com/android/camera/util/CameraUtil.java29
-rw-r--r--src/com/android/camera/util/ClearSightNativeEngine.java351
35 files changed, 3601 insertions, 410 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 13e4f5d6f..ac727e2a2 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4,7 +4,7 @@
package="org.codeaurora.snapcam">
<uses-sdk
- android:minSdkVersion="21"
+ android:minSdkVersion="23"
android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
diff --git a/res/layout/capture_module.xml b/res/layout/capture_module.xml
index c6adf0239..a68af5e08 100644
--- a/res/layout/capture_module.xml
+++ b/res/layout/capture_module.xml
@@ -40,8 +40,8 @@
<com.android.camera.ui.AutoFitSurfaceView
android:id="@+id/mdp_preview_content2"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ android:layout_width="300dp"
+ android:layout_height="300dp" />
</FrameLayout>
<View
diff --git a/res/layout/photo_module.xml b/res/layout/photo_module.xml
index 10017192b..9c5b2c0d0 100644
--- a/res/layout/photo_module.xml
+++ b/res/layout/photo_module.xml
@@ -65,7 +65,7 @@
android:id="@+id/autohdr_view"
android:layout_width="200dip"
android:layout_height="200dip"
- android:layout_marginTop="15dip"
+ android:layout_marginTop="50dip"
android:layout_marginLeft="15dip" />
</RelativeLayout>
<ImageView
diff --git a/res/layout/video_module.xml b/res/layout/video_module.xml
index 1c1db4444..20d7be1d2 100644
--- a/res/layout/video_module.xml
+++ b/res/layout/video_module.xml
@@ -26,8 +26,7 @@
<SurfaceView
android:id="@+id/mdp_preview_content"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone" />
+ android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
@@ -43,8 +42,7 @@
android:id="@+id/preview_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@android:color/black"
- android:visibility="gone" />
+ android:background="@android:color/black" />
<View
android:id="@+id/flash_overlay"
android:layout_width="match_parent"
diff --git a/res/values/qcomarrays.xml b/res/values/qcomarrays.xml
index a4c463735..d3218bdaf 100644
--- a/res/values/qcomarrays.xml
+++ b/res/values/qcomarrays.xml
@@ -863,5 +863,25 @@
<item>@string/pref_camera_dual_camera_value_bayer</item>
<item>@string/pref_camera_dual_camera_value_mono</item>
</string-array>
+
+ <string-array name="pref_camera_mono_preview_entries" translatable="true">
+ <item>@string/pref_camera_mono_preview_entry_on</item>
+ <item>@string/pref_camera_mono_preview_entry_off</item>
+ </string-array>
+
+ <string-array name="pref_camera_mono_preview_entryvalues" translatable="false">
+ <item>@string/pref_camera_mono_preview_value_on</item>
+ <item>@string/pref_camera_mono_preview_value_off</item>
+ </string-array>
+
+ <string-array name="pref_camera_clearsight_entries" translatable="true">
+ <item>@string/pref_camera_clearsight_entry_on</item>
+ <item>@string/pref_camera_clearsight_entry_off</item>
+ </string-array>
+
+ <string-array name="pref_camera_clearsight_entryvalues" translatable="false">
+ <item>@string/pref_camera_clearsight_value_on</item>
+ <item>@string/pref_camera_clearsight_value_off</item>
+ </string-array>
</resources>
diff --git a/res/values/qcomstrings.xml b/res/values/qcomstrings.xml
index 0e21bb5e6..ea90cf72a 100644
--- a/res/values/qcomstrings.xml
+++ b/res/values/qcomstrings.xml
@@ -277,7 +277,7 @@
<string name="pref_camera_cds_default" translatable="false">on</string>
<!-- Default video cds mode setting-->
- <string name="pref_camera_video_cds_default" translatable="false">on</string>
+ <string name="pref_camera_video_cds_default" translatable="false">off</string>
<!-- Settings menu, setting title text for tnr mode-->
<string name="pref_camera_tnr_title">TNR mode</string>
@@ -289,7 +289,7 @@
<string name="pref_camera_tnr_default" translatable="false">off</string>
<!-- Default video tnr mode setting-->
- <string name="pref_camera_video_tnr_default" translatable="false">off</string>
+ <string name="pref_camera_video_tnr_default" translatable="false">on</string>
<!-- Default face detection setting. -->
<string name="pref_camera_facedetection_default" translatable="false">off</string>
@@ -953,9 +953,8 @@
<string name="pref_camera_camera2_value_enable">enable</string>
<string name="pref_camera_camera2_value_disable">disable</string>
-
<string name="pref_camera_dual_camera_title">Dual Camera Mode</string>
- <string name="pref_camera_dual_camera_default">bayer</string>
+ <string name="pref_camera_dual_camera_default">dual</string>
<string name="pref_camera_dual_camera_entry_dual">Dual-camera Linked</string>
<string name="pref_camera_dual_camera_entry_bayer">Single Bayer Camera</string>
<string name="pref_camera_dual_camera_entry_mono">Single Mono Camera</string>
@@ -963,5 +962,24 @@
<string name="pref_camera_dual_camera_value_dual">dual</string>
<string name="pref_camera_dual_camera_value_bayer">bayer</string>
<string name="pref_camera_dual_camera_value_mono">mono</string>
+
+ <string name="pref_camera_mono_preview_title">Mono Preview</string>
+ <string name="pref_camera_mono_preview_default">off</string>
+ <string name="pref_camera_mono_preview_entry_on">On</string>
+ <string name="pref_camera_mono_preview_entry_off">Off</string>
+
+ <string name="pref_camera_mono_preview_value_on">on</string>
+ <string name="pref_camera_mono_preview_value_off">off</string>
+
+ <string name="pref_camera_clearsight_title">ClearSight</string>
+ <string name="pref_camera_clearsight_default" translatable="false">off</string>
+ <string name="pref_camera_clearsight_entry_on">On</string>
+ <string name="pref_camera_clearsight_entry_off">Off</string>
+
+ <string name="pref_camera_clearsight_value_on" translatable="false">on</string>
+ <string name="pref_camera_clearsight_value_off" translatable="false">off</string>
+
+ <string name="clearsight_capture_success">ClearSight capture successful</string>
+ <string name="clearsight_capture_fail">ClearSight capture failed</string>
</resources>
diff --git a/res/xml/camera_preferences.xml b/res/xml/camera_preferences.xml
index 028ff4dbf..5fafb855b 100644
--- a/res/xml/camera_preferences.xml
+++ b/res/xml/camera_preferences.xml
@@ -379,4 +379,19 @@
camera:title="@string/pref_camera_dual_camera_title"
camera:entries="@array/pref_camera_dual_camera_entries"
camera:entryValues="@array/pref_camera_dual_camera_entryvalues" />
+
+ <ListPreference
+ camera:defaultValue="@string/pref_camera_mono_preview_default"
+ camera:entries="@array/pref_camera_mono_preview_entries"
+ camera:entryValues="@array/pref_camera_mono_preview_entryvalues"
+ camera:key="pref_camera_mono_preview_key"
+ camera:title="@string/pref_camera_mono_preview_title" />
+
+ <ListPreference
+ camera:defaultValue="@string/pref_camera_clearsight_default"
+ camera:entries="@array/pref_camera_clearsight_entries"
+ camera:entryValues="@array/pref_camera_clearsight_entryvalues"
+ camera:key="pref_camera_clearsight_key"
+ camera:title="@string/pref_camera_clearsight_title" />
+
</PreferenceGroup>
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 80387a946..fafb40839 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -1813,7 +1813,9 @@ public class CameraActivity extends Activity
public void onModuleSelected(int moduleIndex) {
if (moduleIndex == 0 && CAMERA_2_ON) moduleIndex = ModuleSwitcher.CAPTURE_MODULE_INDEX;
if (mCurrentModuleIndex == moduleIndex) {
- return;
+ if (mCurrentModuleIndex != ModuleSwitcher.CAPTURE_MODULE_INDEX) {
+ return;
+ }
}
CameraHolder.instance().keep();
closeModule(mCurrentModule);
@@ -1840,7 +1842,6 @@ public class CameraActivity extends Activity
mCameraVideoModuleRootView.setVisibility(View.GONE);
mCameraPanoModuleRootView.setVisibility(View.GONE);
mCameraCaptureModuleRootView.setVisibility(View.GONE);
- mCameraRootFrame.removeAllViews();
mCurrentModuleIndex = moduleIndex;
switch (moduleIndex) {
case ModuleSwitcher.VIDEO_MODULE_INDEX:
@@ -1851,7 +1852,6 @@ public class CameraActivity extends Activity
mVideoModule.reinit();
}
mCurrentModule = mVideoModule;
- mCameraRootFrame.addView(mCameraVideoModuleRootView);
mCameraVideoModuleRootView.setVisibility(View.VISIBLE);
break;
@@ -1863,7 +1863,6 @@ public class CameraActivity extends Activity
mPhotoModule.reinit();
}
mCurrentModule = mPhotoModule;
- mCameraRootFrame.addView(mCameraPhotoModuleRootView);
mCameraPhotoModuleRootView.setVisibility(View.VISIBLE);
break;
@@ -1873,7 +1872,6 @@ public class CameraActivity extends Activity
mPanoModule.init(this, mCameraPanoModuleRootView);
}
mCurrentModule = mPanoModule;
- mCameraRootFrame.addView(mCameraPanoModuleRootView);
mCameraPanoModuleRootView.setVisibility(View.VISIBLE);
break;
@@ -1883,7 +1881,6 @@ public class CameraActivity extends Activity
mCaptureModule.init(this, mCameraCaptureModuleRootView);
}
mCurrentModule = mCaptureModule;
- mCameraRootFrame.addView(mCameraCaptureModuleRootView);
mCameraCaptureModuleRootView.setVisibility(View.VISIBLE);
break;
case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX: //Unused module for now
@@ -1897,7 +1894,6 @@ public class CameraActivity extends Activity
mPhotoModule.reinit();
}
mCurrentModule = mPhotoModule;
- mCameraRootFrame.addView(mCameraPhotoModuleRootView);
mCameraPhotoModuleRootView.setVisibility(View.VISIBLE);
break;
}
diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java
index 48d161d7c..a12ce5de0 100644
--- a/src/com/android/camera/CameraSettings.java
+++ b/src/com/android/camera/CameraSettings.java
@@ -248,6 +248,8 @@ public class CameraSettings {
public static final String KEY_CAMERA2 = "pref_camera_camera2_key";
public static final String KEY_DUAL_CAMERA = "pref_camera_dual_camera_key";
+ public static final String KEY_MONO_PREVIEW = "pref_camera_mono_preview_key";
+ public static final String KEY_CLEARSIGHT = "pref_camera_clearsight_key";
public static final String KEY_REFOCUS_PROMPT = "refocus-prompt";
@@ -280,7 +282,7 @@ public class CameraSettings {
//video encoders
VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.H263, "h263");
VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.H264, "h264");
- VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.H265, "h265");
+ // VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.H265, "h265");
VIDEO_ENCODER_TABLE.put(MediaRecorder.VideoEncoder.MPEG_4_SP, "m4v");
//video qualities
diff --git a/src/com/android/camera/CaptureMenu.java b/src/com/android/camera/CaptureMenu.java
index dbc8132be..8b8f5c357 100644
--- a/src/com/android/camera/CaptureMenu.java
+++ b/src/com/android/camera/CaptureMenu.java
@@ -26,6 +26,7 @@ import android.content.SharedPreferences;
import android.graphics.Rect;
import android.preference.PreferenceManager;
import android.text.TextUtils;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -91,14 +92,26 @@ public class CaptureMenu extends MenuController
mOtherKeys1 = new String[]{
CameraSettings.KEY_FLASH_MODE,
+ CameraSettings.KEY_RECORD_LOCATION,
+ CameraSettings.KEY_JPEG_QUALITY,
+ CameraSettings.KEY_CAMERA_SAVEPATH,
+ CameraSettings.KEY_WHITE_BALANCE,
CameraSettings.KEY_CAMERA2,
- CameraSettings.KEY_DUAL_CAMERA
+ CameraSettings.KEY_DUAL_CAMERA,
+ CameraSettings.KEY_CLEARSIGHT
};
+ //Todo: 2nd string to contain only developer settings
mOtherKeys2 = new String[]{
CameraSettings.KEY_FLASH_MODE,
+ CameraSettings.KEY_RECORD_LOCATION,
+ CameraSettings.KEY_JPEG_QUALITY,
+ CameraSettings.KEY_CAMERA_SAVEPATH,
+ CameraSettings.KEY_WHITE_BALANCE,
CameraSettings.KEY_CAMERA2,
- CameraSettings.KEY_DUAL_CAMERA
+ CameraSettings.KEY_DUAL_CAMERA,
+ CameraSettings.KEY_CLEARSIGHT,
+ CameraSettings.KEY_MONO_PREVIEW
};
}
@@ -106,9 +119,8 @@ public class CaptureMenu extends MenuController
@Override
// Hit when an item in a popup gets selected
public void onListPrefChanged(ListPreference pref) {
- animateFadeOut(mListSubMenu, 2);
onSettingChanged(pref);
- ((ListMenu) mListMenu).resetHighlight();
+ closeView();
}
public boolean handleBackKey() {
@@ -384,6 +396,14 @@ public class CaptureMenu extends MenuController
listMenu.initialize(mPreferenceGroup, keys);
mListMenu = listMenu;
+ ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_DUAL_CAMERA);
+ if (!pref.getValue().equals("dual")) {
+ setPreference(CameraSettings.KEY_MONO_PREVIEW, "off");
+ mListMenu.setPreferenceEnabled(CameraSettings.KEY_MONO_PREVIEW, false);
+ setPreference(CameraSettings.KEY_CLEARSIGHT, "off");
+ mListMenu.setPreferenceEnabled(CameraSettings.KEY_CLEARSIGHT, false);
+ }
+
if (mListener != null) {
mListener.onSharedPreferenceChanged();
}
@@ -516,18 +536,32 @@ public class CaptureMenu extends MenuController
@Override
public void onSettingChanged(ListPreference pref) {
super.onSettingChanged(pref);
- if (same(pref, CameraSettings.KEY_CAMERA2, "enable")) {
+ String key = pref.getKey();
+ String value = pref.getValue();
+ Log.d(TAG, "" + key + " " + value);
+ //Todo: restructure by using switch and create function for each case
+ if (key.equals(CameraSettings.KEY_CAMERA2)) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(mActivity);
- prefs.edit().putBoolean(CameraSettings.KEY_CAMERA2, true).apply();
- CameraActivity.CAMERA_2_ON = true;
+ if (value.equals("enable")) {
+ prefs.edit().putBoolean(CameraSettings.KEY_CAMERA2, true).apply();
+ CameraActivity.CAMERA_2_ON = true;
+ mActivity.onModuleSelected(ModuleSwitcher.CAPTURE_MODULE_INDEX);
+ } else if (value.equals("disable")) {
+ prefs.edit().putBoolean(CameraSettings.KEY_CAMERA2, false).apply();
+ CameraActivity.CAMERA_2_ON = false;
+ mActivity.onModuleSelected(ModuleSwitcher.PHOTO_MODULE_INDEX);
+ }
+ } else if (key.equals(CameraSettings.KEY_DUAL_CAMERA)) {
+ boolean changeMode = CaptureModule.setMode(value);
+ if (changeMode) mActivity.onModuleSelected(ModuleSwitcher.CAPTURE_MODULE_INDEX);
+ } else if (key.equals(CameraSettings.KEY_MONO_PREVIEW)) {
+ if (value.equals("on")) {
+ } else if (value.equals("off")) {
+ }
+ } else if (key.equals(CameraSettings.KEY_CLEARSIGHT)) {
+ // restart module to re-create sessions and callbacks
mActivity.onModuleSelected(ModuleSwitcher.CAPTURE_MODULE_INDEX);
- } else if (notSame(pref, CameraSettings.KEY_CAMERA2, "enable")) {
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(mActivity);
- prefs.edit().putBoolean(CameraSettings.KEY_CAMERA2, false).apply();
- CameraActivity.CAMERA_2_ON = false;
- mActivity.onModuleSelected(ModuleSwitcher.PHOTO_MODULE_INDEX);
}
}
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index 604a705f3..382e691b1 100644
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -19,54 +19,78 @@
package com.android.camera;
-import android.annotation.TargetApi;
-import android.app.Activity;
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import org.codeaurora.snapcam.R;
+
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.YuvImage;
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.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.CameraProfile;
import android.media.Image;
+import android.media.Image.Plane;
import android.media.ImageReader;
+import android.media.ImageWriter;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
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.ui.RotateTextToast;
+import com.android.camera.util.CameraUtil;
+import com.android.camera.util.ClearSightNativeEngine;
+
+public class CaptureModule implements CameraModule, PhotoController,
+ MediaSaveService.Listener {
+ public static final int DUAL_MODE = 0;
+ public static final int BAYER_MODE = 1;
+ public static final int MONO_MODE = 2;
+ private static final int OPEN_CAMERA = 0;
+ private static final int MAX_NUM_CAM = 3;
+ private static final long TIMESTAMP_THRESHOLD_NS = 10*1000000; // 10 ms
-import org.codeaurora.snapcam.R;
-
-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.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-public class CaptureModule implements CameraModule, PhotoController {
- private static final int NUMCAM = 1;
/**
* Conversion from screen rotation to JPEG orientation.
*/
@@ -91,7 +115,18 @@ public class CaptureModule implements CameraModule, PhotoController {
* Camera state: Picture was taken.
*/
private static final int STATE_PICTURE_TAKEN = 4;
+ //Todo: Read ids from the device dynamically
+ private static final int BAYER_ID = 0;
+ private static final int MONO_ID = 1;
private static final String TAG = "SnapCam_CaptureModule";
+ private static int MODE = DUAL_MODE;
+
+ private static final int MSG_START_CAPTURE = 0;
+ private static final int MSG_NEW_IMG = 1;
+ private static final int MSG_NEW_RESULT = 2;
+ private static final int MSG_SAVE = 4;
+
+ private static final int NUM_IMAGES_TO_BURST = 4;
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
@@ -100,60 +135,74 @@ public class CaptureModule implements CameraModule, PhotoController {
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
+ MeteringRectangle[][] mAFRegions = new MeteringRectangle[MAX_NUM_CAM][];
+ CaptureRequest.Key<Byte> BayerMonoLinkEnableKey =
+ new CaptureRequest.Key<>("org.codeaurora.qcamera3.dualcam_link_meta_data.enable",
+ Byte.class);
+ CaptureRequest.Key<Byte> BayerMonoLinkMainKey =
+ new CaptureRequest.Key<>("org.codeaurora.qcamera3.dualcam_link_meta_data.is_main",
+ Byte.class);
+ CaptureRequest.Key<Integer> BayerMonoLinkSessionIdKey =
+ new CaptureRequest.Key<>("org.codeaurora.qcamera3.dualcam_link_meta_data" +
+ ".related_camera_id", Integer.class);
+
+ CaptureResult.Key<Byte> OTP_CALIB_BLOB =
+ new CaptureResult.Key<>("org.codeaurora.qcamera3.dualcam_calib_meta_data.dualcam_calib_meta_data_blob",
+ Byte.class);
+
+ private int mLastResultAFState = -1;
+ private Rect[] mCropRegion = new Rect[MAX_NUM_CAM];
+ private boolean mAutoFocusSupported;
+ // The degrees of the device rotated clockwise from its natural orientation.
+ private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+ private int mJpegQuality;
+ private Map<String, String> mSettings = new HashMap<String, String>();
+ private boolean mFirstTimeInitialized;
+ private boolean mInitialized = false;
+ private boolean mIsLinked = false;
private long mCaptureStartTime;
+ private boolean mPaused = true;
private boolean mSurfaceReady = false;
- private boolean mCameraOpened = false;
- private CameraDevice[] mCameraDevice = new CameraDevice[NUMCAM];
- private String[] mCameraId = new String[NUMCAM];
+ private boolean[] mCameraOpened = new boolean[MAX_NUM_CAM];
+ private CameraDevice[] mCameraDevice = new CameraDevice[MAX_NUM_CAM];
+ private String[] mCameraId = new String[MAX_NUM_CAM];
private CaptureUI mUI;
private CameraActivity mActivity;
private PreferenceGroup mPreferenceGroup;
private ComboPreferences mPreferences;
- private CaptureRequest.Builder[] mCaptureBuilder = new CaptureRequest.Builder[NUMCAM];
- private final CameraPreference.OnPreferenceChangedListener prefListener = new
- CameraPreference.OnPreferenceChangedListener() {
- @Override
- public void onSharedPreferenceChanged(ListPreference pref) {
- applyPreference(0, pref);
- mUI.overrideSettings(pref.getKey(), null);
- }
-
- @Override
- public void onSharedPreferenceChanged() {
- }
-
- @Override
- public void onRestorePreferencesClicked() {
- }
-
- @Override
- public void onOverriddenPreferencesClicked() {
- }
-
- @Override
- public void onCameraPickerClicked(int cameraId) {
- }
- };
+ private CameraCharacteristics[] mCharacteristics = new CameraCharacteristics[MAX_NUM_CAM];
+ private List<Integer> mCharacteristicsIndex;
+ private float mZoomValue = 1f;
+ private FocusStateListener mFocusStateListener;
+ private LocationManager mLocationManager;
/**
* A {@link CameraCaptureSession } for camera preview.
*/
- private CameraCaptureSession[] mCaptureSession = new CameraCaptureSession[NUMCAM];
+ private CameraCaptureSession[] mCaptureSession = new CameraCaptureSession[MAX_NUM_CAM];
/**
* An additional thread for running tasks that shouldn't block the UI.
*/
private HandlerThread mCameraThread;
private HandlerThread mImageAvailableThread;
private HandlerThread mCallbackThread;
+ private HandlerThread mImageProcessThread;
+ private HandlerThread mImageReprocessThread;
+
/**
* A {@link Handler} for running tasks in the background.
*/
private Handler mCameraHandler;
private Handler mImageAvailableHandler;
private Handler mCallbackHandler;
+ private ImageProcessHandler mImageProcessHandler;
+ private ImageReprocessHandler mImageReprocessHandler;
+
/**
* An {@link ImageReader} that handles still image capture.
*/
- private ImageReader[] mImageReader = new ImageReader[NUMCAM];
+ private ImageReader[] mImageReader = new ImageReader[MAX_NUM_CAM];
+ private ImageReader[] mReprocessImageReader = new ImageReader[MAX_NUM_CAM];
+ private ImageWriter[] mImageWriter = new ImageWriter[MAX_NUM_CAM];
private NamedImages mNamedImages;
private ContentResolver mContentResolver;
private MediaSaveService.OnMediaSavedListener mOnMediaSavedListener =
@@ -165,47 +214,482 @@ public class CaptureModule implements CameraModule, PhotoController {
}
}
};
- /**
- * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
- * still image is ready to be saved.
- */
- private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
- = new ImageReader.OnImageAvailableListener() {
+
+ private class ReprocessableImage {
+ final Image mImage;
+ final TotalCaptureResult mCaptureResult;
+
+ ReprocessableImage(Image image, TotalCaptureResult result) {
+ mImage = image;
+ mCaptureResult = result;
+ }
+ }
+
+ private abstract class ImageAvailableListener implements ImageReader.OnImageAvailableListener {
+ int mCamId;
+
+ ImageAvailableListener(int cameraId) {
+ mCamId = cameraId;
+ }
+ }
+
+ private abstract class CameraCaptureCallback extends CameraCaptureSession.CaptureCallback {
+ int mCamId;
+
+ CameraCaptureCallback(int cameraId) {
+ mCamId = cameraId;
+ }
+ }
+
+ private class ImageProcessHandler extends Handler {
+ private ArrayDeque<ReprocessableImage> mBayerFrames = new ArrayDeque<ReprocessableImage>(
+ NUM_IMAGES_TO_BURST);
+ private ArrayDeque<ReprocessableImage> mMonoFrames = new ArrayDeque<ReprocessableImage>(
+ NUM_IMAGES_TO_BURST);
+ private ArrayDeque<TotalCaptureResult> mBayerCaptureResults = new ArrayDeque<TotalCaptureResult>(
+ NUM_IMAGES_TO_BURST);
+ private ArrayDeque<TotalCaptureResult> mMonoCaptureResults = new ArrayDeque<TotalCaptureResult>(
+ NUM_IMAGES_TO_BURST);
+ private ArrayDeque<Image> mBayerImages = new ArrayDeque<Image>(
+ NUM_IMAGES_TO_BURST);
+ private ArrayDeque<Image> mMonoImages = new ArrayDeque<Image>(
+ NUM_IMAGES_TO_BURST);
+ private int[] mNumImagesToProcess = new int[MAX_NUM_CAM];
+
+ public ImageProcessHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_CAPTURE:
+ mNumImagesToProcess[msg.arg1] = msg.arg2;
+ break;
+ case MSG_NEW_IMG:
+ processNewImg(msg);
+ break;
+ case MSG_NEW_RESULT:
+ processNewCaptureResult(msg);
+ break;
+ case MSG_SAVE:
+ processSaveImage(msg);
+ break;
+ }
+ }
+
+ private void processNewImg(Message msg) {
+ Image image = (Image) msg.obj;
+
+ ArrayDeque<Image> imageQueue;
+ ArrayDeque<TotalCaptureResult> resultQueue;
+ ArrayDeque<ReprocessableImage> reprocQueue;
+ // push image onto queue
+ if (msg.arg1 == BAYER_ID) {
+ imageQueue = mBayerImages;
+ resultQueue = mBayerCaptureResults;
+ reprocQueue = mBayerFrames;
+ } else {
+ imageQueue = mMonoImages;
+ resultQueue = mMonoCaptureResults;
+ reprocQueue = mMonoFrames;
+ }
+
+ imageQueue.add(image);
+
+ Log.d(TAG, "processNewImg - cam: " + msg.arg1 + " num imgs: "
+ + imageQueue.size() + " num results: " + resultQueue.size());
+ if (imageQueue.isEmpty() == resultQueue.isEmpty()) {
+ Image headImage = imageQueue.poll();
+ TotalCaptureResult headResult = resultQueue.poll();
+ reprocQueue.add(new ReprocessableImage(headImage, headResult));
+ checkForValidFramePair();
+ mNumImagesToProcess[msg.arg1]--;
+ if (mNumImagesToProcess[BAYER_ID] == 0
+ && mNumImagesToProcess[MONO_ID] == 0) {
+ ClearSightNativeEngine.getInstance().reset();
+ processReprocess();
+ }
+ }
+ }
+
+ private void processNewCaptureResult(Message msg) {
+ if (msg.arg2 == 1) {
+ // capture failed
+ mNumImagesToProcess[msg.arg1]--;
+ } else {
+ TotalCaptureResult result = (TotalCaptureResult) msg.obj;
+ ArrayDeque<Image> imageQueue;
+ ArrayDeque<TotalCaptureResult> resultQueue;
+ ArrayDeque<ReprocessableImage> reprocQueue;
+ // push image onto queue
+ if (msg.arg1 == BAYER_ID) {
+ imageQueue = mBayerImages;
+ resultQueue = mBayerCaptureResults;
+ reprocQueue = mBayerFrames;
+ } else {
+ imageQueue = mMonoImages;
+ resultQueue = mMonoCaptureResults;
+ reprocQueue = mMonoFrames;
+ }
+
+ resultQueue.add(result);
+
+ Log.d(TAG, "processNewCaptureResult - cam: " + msg.arg1
+ + " num imgs: " + imageQueue.size() + " num results: "
+ + resultQueue.size());
+ if (imageQueue.isEmpty() == resultQueue.isEmpty()) {
+ Image headImage = imageQueue.poll();
+ TotalCaptureResult headResult = resultQueue.poll();
+ reprocQueue.add(new ReprocessableImage(headImage,
+ headResult));
+ checkForValidFramePair();
+ mNumImagesToProcess[msg.arg1]--;
+ if (mNumImagesToProcess[BAYER_ID] == 0
+ && mNumImagesToProcess[MONO_ID] == 0) {
+ ClearSightNativeEngine.getInstance().reset();
+ processReprocess();
+ }
+ }
+ }
+ }
+
+ private void checkForValidFramePair() {
+ // if we have images from both
+ // as we just added an image onto one of the queues
+ // this condition is only true when both are not empty
+ Log.d(TAG,
+ "checkForValidFramePair - num bayer frames: "
+ + mBayerFrames.size() + " num mono frames: "
+ + mMonoFrames.size());
+
+ if (mBayerFrames.isEmpty() == mMonoFrames.isEmpty()) {
+ // peek oldest pair of images
+ ReprocessableImage bayer = mBayerFrames.peek();
+ ReprocessableImage mono = mMonoFrames.peek();
+
+ Log.d(TAG,
+ "checkForValidFramePair - bayer ts: "
+ + bayer.mImage.getTimestamp() + " mono ts: "
+ + mono.mImage.getTimestamp());
+ Log.d(TAG,
+ "checkForValidFramePair - difference: "
+ + Math.abs(bayer.mImage.getTimestamp()
+ - mono.mImage.getTimestamp()));
+ // if timestamps are within threshold, keep frames
+ if (Math.abs(bayer.mImage.getTimestamp()
+ - mono.mImage.getTimestamp()) > TIMESTAMP_THRESHOLD_NS) {
+ Log.d(TAG, "checkForValidFramePair - toss pair");
+ // no match, toss
+ bayer = mBayerFrames.poll();
+ mono = mMonoFrames.poll();
+ bayer.mImage.close();
+ mono.mImage.close();
+ }
+ }
+ }
+
+ private void releaseBayerFrames() {
+ for (ReprocessableImage reprocImg : mBayerFrames) {
+ reprocImg.mImage.close();
+ }
+
+ mBayerFrames.clear();
+ }
+
+ private void releaseMonoFrames() {
+ for (ReprocessableImage reprocImg : mMonoFrames) {
+ reprocImg.mImage.close();
+ }
+
+ mMonoFrames.clear();
+ }
+
+ private void processReprocess() {
+ if (mBayerFrames.size() != mMonoFrames.size()
+ || mBayerFrames.isEmpty()) {
+ Log.d(TAG, "processReprocess - frame size mismatch or empty");
+ releaseBayerFrames();
+ releaseMonoFrames();
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ RotateTextToast.makeText(mActivity, R.string.clearsight_capture_fail,
+ Toast.LENGTH_SHORT).show();
+ unlockFocus(BAYER_ID);
+ unlockFocus(MONO_ID);
+ }
+ });
+ return;
+ } else {
+ sendReprocessRequests(BAYER_ID);
+ sendReprocessRequests(MONO_ID);
+ }
+ }
+
+ private void sendReprocessRequests(final int camId) {
+ try {
+ ArrayDeque<ReprocessableImage> frameQueue;
+ if (camId == BAYER_ID) {
+ frameQueue = mBayerFrames;
+ } else {
+ frameQueue = mMonoFrames;
+ }
+ Log.d(TAG, "sendReprocessRequests - start cam: " + camId
+ + " num frames: " + frameQueue.size());
+
+ ArrayList<CaptureRequest> reprocRequests = new ArrayList<CaptureRequest>(
+ frameQueue.size());
+ while (!frameQueue.isEmpty()) {
+ ReprocessableImage reprocImg = frameQueue.poll();
+
+ CaptureRequest.Builder reprocRequest = mCameraDevice[camId]
+ .createReprocessCaptureRequest(reprocImg.mCaptureResult);
+ reprocRequest.addTarget(mReprocessImageReader[camId]
+ .getSurface());
+ reprocRequests.add(reprocRequest.build());
+
+ mImageWriter[camId].queueInputImage(reprocImg.mImage);
+ }
+
+ mImageReprocessHandler.obtainMessage(MSG_START_CAPTURE, camId,
+ reprocRequests.size()).sendToTarget();
+ mCaptureSession[camId].captureBurst(reprocRequests,
+ new CameraCaptureCallback(camId) {
+ @Override
+ public void onCaptureCompleted(
+ CameraCaptureSession session,
+ CaptureRequest request,
+ TotalCaptureResult result) {
+ super.onCaptureCompleted(session, request, result);
+ Log.d(TAG, "reprocess - onCaptureCompleted: "
+ + mCamId);
+ // TODO: parse OTP Calib data to be used in final CS
+ // result.get(OTP_CALIB_BLOB);
+ }
+
+ @Override
+ public void onCaptureFailed(
+ CameraCaptureSession session,
+ CaptureRequest request,
+ CaptureFailure failure) {
+ super.onCaptureFailed(session, request, failure);
+ Log.d(TAG, "reprocess - onCaptureFailed: "
+ + mCamId);
+ mImageReprocessHandler.obtainMessage(
+ MSG_NEW_RESULT, mCamId, 1)
+ .sendToTarget();
+ }
+ }, mCameraHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void processSaveImage(Message msg) {
+ Image image = (Image) msg.obj;
+ if (image.getFormat() == ImageFormat.JPEG) {
+ mCaptureStartTime = System.currentTimeMillis();
+ mNamedImages.nameNewImage(mCaptureStartTime);
+ NamedEntity name = mNamedImages.getNextNameEntity();
+ String title = (name == null) ? null : name.title;
+ long date = (name == null) ? -1 : name.date;
+
+ ByteBuffer buffer = image.getPlanes()[0].getBuffer();
+ byte[] bytes = new byte[buffer.remaining()];
+ buffer.get(bytes);
+
+ mActivity.getMediaSaveService().addImage(bytes, title, date,
+ null, image.getWidth(), image.getHeight(), 0, null,
+ mOnMediaSavedListener, mContentResolver, "jpeg");
+ } else {
+ Log.w(TAG, "processSaveImage - image format incorrect: " + image.getFormat());
+ }
+ image.close();
+ }
+ };
+
+ private class ImageReprocessHandler extends Handler {
+ private int[] mNumImagesToProcess = new int[MAX_NUM_CAM];
+
+ public ImageReprocessHandler(Looper looper) {
+ super(looper);
+ }
@Override
- public void onImageAvailable(ImageReader reader) {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_CAPTURE:
+ mNumImagesToProcess[msg.arg1] = msg.arg2;
+ break;
+ case MSG_NEW_IMG:
+ processNewImg(msg);
+ break;
+ case MSG_NEW_RESULT:
+ processNewCaptureResult(msg);
+ break;
+ }
+ }
+
+ private void processNewImg(Message msg) {
+ Image image = (Image) msg.obj;
+ boolean isBayer = (msg.arg1 == BAYER_ID);
+
+ Log.d(TAG, "reprocess - processNewImg");
+ if (!ClearSightNativeEngine.getInstance()
+ .hasReferenceImage(isBayer)) {
+ // reference not yet set
+ ClearSightNativeEngine.getInstance().setReferenceImage(isBayer,
+ image);
+ } else {
+ // if ref images set, register this image
+ ClearSightNativeEngine.getInstance().registerImage(isBayer,
+ image);
+ }
+
+ mNumImagesToProcess[msg.arg1]--;
+
+ Log.d(TAG, "reprocess - processNewImg, cam: " + msg.arg1
+ + " count: " + mNumImagesToProcess[msg.arg1]);
+
+ if (mNumImagesToProcess[BAYER_ID] == 0
+ && mNumImagesToProcess[MONO_ID] == 0) {
+ processClearSight();
+ }
+ }
+
+ private void processNewCaptureResult(Message msg) {
+ if (msg.arg2 == 1) {
+ // capture failed
+ mNumImagesToProcess[msg.arg1]--;
+ }
+
+ Log.d(TAG, "reprocess - processNewCaptureResult, cam: " + msg.arg1
+ + " count: " + mNumImagesToProcess[msg.arg1]);
+
+ if (mNumImagesToProcess[BAYER_ID] == 0
+ && mNumImagesToProcess[MONO_ID] == 0) {
+ processClearSight();
+ }
+ }
+
+ private void processClearSight() {
+ Log.d(TAG, "reprocess - processClearSight, bayercount: "
+ + mNumImagesToProcess[BAYER_ID] + " mono count: "
+ + mNumImagesToProcess[MONO_ID]);
+
mCaptureStartTime = System.currentTimeMillis();
mNamedImages.nameNewImage(mCaptureStartTime);
NamedEntity name = mNamedImages.getNextNameEntity();
String title = (name == null) ? null : name.title;
long date = (name == null) ? -1 : name.date;
- Image mImage = reader.acquireNextImage();
- ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
- byte[] bytes = new byte[buffer.remaining()];
- buffer.get(bytes);
-
- mActivity.getMediaSaveService().addImage(
- bytes, title, date, null, reader.getWidth(), reader.getHeight(),
- 0, null, mOnMediaSavedListener, mContentResolver, "jpeg");
+ ClearSightNativeEngine.ClearsightImage csImage = ClearSightNativeEngine
+ .getInstance().processImage();
+ if (csImage != null) {
+ Log.d(TAG, "reprocess - processClearSight success");
+
+ mActivity.getMediaSaveService().addMpoImage(
+ csImage,
+ createYuvImage(ClearSightNativeEngine.getInstance()
+ .getReferenceImage(true)),
+ createYuvImage(ClearSightNativeEngine.getInstance()
+ .getReferenceImage(false)), null, null, title,
+ date, null, 0, mOnMediaSavedListener, mContentResolver,
+ "jpeg");
+
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ RotateTextToast.makeText(mActivity, R.string.clearsight_capture_success,
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ } else {
+ Log.d(TAG, "reprocess - processClearSight fail");
+ Image bayerRef = ClearSightNativeEngine.getInstance()
+ .getReferenceImage(true);
+ Image monoRef = ClearSightNativeEngine.getInstance()
+ .getReferenceImage(false);
+ if (bayerRef != null && monoRef != null) {
+ Log.d(TAG, "reprocess - saving with bayer + mono mpo");
+ mActivity.getMediaSaveService().addMpoImage(null,
+ createYuvImage(bayerRef), createYuvImage(monoRef),
+ null, null, title, date, null, 0,
+ mOnMediaSavedListener, mContentResolver, "jpeg");
+ } else {
+ Log.d(TAG, "reprocess - bayer + mono images not available");
+ }
}
+ unlockFocus(BAYER_ID);
+ unlockFocus(MONO_ID);
+ ClearSightNativeEngine.getInstance().reset();
+ }
};
+ private void saveDebugImage(byte[] data, int width, int height,
+ boolean isReproc) {
+ mCaptureStartTime = System.currentTimeMillis();
+ mNamedImages.nameNewImage(mCaptureStartTime);
+ NamedEntity name = mNamedImages.getNextNameEntity();
+ String title = (name == null) ? null : name.title;
+ long date = (name == null) ? -1 : name.date;
+
+ if (isReproc) {
+ title += "_reproc";
+ }
+
+ mActivity.getMediaSaveService().addImage(data, title, date, null,
+ width, height, 0, null, mOnMediaSavedListener,
+ mContentResolver, "jpeg");
+ }
+
+ private void saveDebugImage(YuvImage image, boolean isReproc) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ image.compressToJpeg(
+ new Rect(0, 0, image.getWidth(), image.getHeight()), 100, baos);
+
+ saveDebugImage(baos.toByteArray(), image.getWidth(), image.getHeight(),
+ isReproc);
+ }
+
+ private void saveDebugImage(Image image, boolean isReproc) {
+ saveDebugImage(createYuvImage(image), isReproc);
+ }
+
+ private YuvImage createYuvImage(Image image) {
+ if (image == null) {
+ Log.d(TAG, "createYuvImage - invalid param");
+ return null;
+ }
+ Plane[] planes = image.getPlanes();
+ ByteBuffer yBuffer = planes[0].getBuffer();
+ ByteBuffer vuBuffer = planes[2].getBuffer();
+ int sizeY = yBuffer.capacity();
+ int sizeVU = vuBuffer.capacity();
+ byte[] data = new byte[sizeY + sizeVU];
+ yBuffer.rewind();
+ yBuffer.get(data, 0, sizeY);
+ vuBuffer.rewind();
+ vuBuffer.get(data, sizeY, sizeVU);
+ int[] strides = new int[] { planes[0].getRowStride(),
+ planes[2].getRowStride() };
+
+ return new YuvImage(data, ImageFormat.NV21, image.getWidth(),
+ image.getHeight(), strides);
+ }
+
/**
* {@link CaptureRequest.Builder} for the camera preview
*/
- private CaptureRequest.Builder[] mPreviewRequestBuilder = new CaptureRequest.Builder[NUMCAM];
- /**
- * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
- */
- private CaptureRequest[] mPreviewRequest = new CaptureRequest[NUMCAM];
+ private CaptureRequest.Builder[] mPreviewRequestBuilder = new CaptureRequest.Builder[MAX_NUM_CAM];
/**
* The current state of camera state for taking pictures.
*
* @see #mCaptureCallback
*/
- private int mState = STATE_PREVIEW;
+ private int[] mState = new int[MAX_NUM_CAM];
/**
* A {@link Semaphore} make sure the camera open callback happens first before closing the
* camera.
@@ -218,23 +702,28 @@ public class CaptureModule implements CameraModule, PhotoController {
= new CameraCaptureSession.CaptureCallback() {
private void process(CaptureResult result) {
- switch (mState) {
+ int id = (int) result.getRequest().getTag();
+
+ switch (mState[id]) {
case STATE_PREVIEW: {
- // We have nothing to do when the camera preview is working normally.
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);
+ // AF_PASSIVE is added for continous auto focus mode
if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
- CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
+ CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState ||
+ CaptureRequest.CONTROL_AF_STATE_PASSIVE_FOCUSED == afState ||
+ CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED == afState) {
// CONTROL_AE_STATE can be null on some devices
- Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
- if (aeState == null ||
- aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
- mState = STATE_PICTURE_TAKEN;
- captureStillPicture(0);
+ if (aeState == null || (aeState == CaptureResult
+ .CONTROL_AE_STATE_CONVERGED) && isFlashOff()) {
+ mState[id] = STATE_PICTURE_TAKEN;
+ captureStillPicture(id);
} else {
- runPrecaptureSequence(0);
+ runPrecaptureSequence(id);
}
}
break;
@@ -245,7 +734,7 @@ public class CaptureModule implements CameraModule, PhotoController {
if (aeState == null ||
aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
- mState = STATE_WAITING_NON_PRECAPTURE;
+ mState[id] = STATE_WAITING_NON_PRECAPTURE;
}
break;
}
@@ -253,8 +742,8 @@ public class CaptureModule implements CameraModule, PhotoController {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
- mState = STATE_PICTURE_TAKEN;
- captureStillPicture(0);
+ mState[id] = STATE_PICTURE_TAKEN;
+ captureStillPicture(id);
}
break;
}
@@ -265,6 +754,8 @@ public class CaptureModule implements CameraModule, PhotoController {
public void onCaptureProgressed(CameraCaptureSession session,
CaptureRequest request,
CaptureResult partialResult) {
+ int id = (int) partialResult.getRequest().getTag();
+ if (id == getMainCameraId()) updateFocusStateChange(partialResult);
process(partialResult);
}
@@ -272,63 +763,209 @@ public class CaptureModule implements CameraModule, PhotoController {
public void onCaptureCompleted(CameraCaptureSession session,
CaptureRequest request,
TotalCaptureResult result) {
+ int id = (int) result.getRequest().getTag();
+ if (id == getMainCameraId()) updateFocusStateChange(result);
process(result);
}
-
};
- private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
+ private final CameraPreference.OnPreferenceChangedListener prefListener = new
+ CameraPreference.OnPreferenceChangedListener() {
+ @Override
+ public void onSharedPreferenceChanged(ListPreference pref) {
+ if (mPaused) return;
+ if (CameraSettings.KEY_CAMERA_SAVEPATH.equals(pref.getKey())) {
+ Storage.setSaveSDCard(
+ mPreferences.getString(CameraSettings.KEY_CAMERA_SAVEPATH, "0")
+ .equals("1"));
+ mActivity.updateStorageSpaceAndHint();
+ }
- @Override
- public void onOpened(CameraDevice cameraDevice) {
- int id = Integer.parseInt(cameraDevice.getId());
+ switch (MODE) {
+ case BAYER_MODE:
+ applyPreference(0, pref);
+ break;
+ case MONO_MODE:
+ applyPreference(1, pref);
+ break;
+ case DUAL_MODE:
+ applyPreference(0, pref);
+ applyPreference(1, pref);
+ }
+ mUI.overrideSettings(pref.getKey(), null);
+ }
- mCameraOpenCloseLock.release();
+ @Override
+ public void onSharedPreferenceChanged() {
+ if (mPaused) return;
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+ }
- PreferenceInflater inflater = new PreferenceInflater(mActivity);
- PreferenceGroup group =
- (PreferenceGroup) inflater.inflate(R.xml.camera_preferences);
- mPreferenceGroup = group;
- mActivity.runOnUiThread(new Runnable() {
@Override
- public void run() {
- mUI.onCameraOpened(mPreferenceGroup, prefListener);
+ public void onRestorePreferencesClicked() {
}
+ @Override
+ public void onOverriddenPreferencesClicked() {
+ }
- });
+ @Override
+ public void onCameraPickerClicked(int cameraId) {
+ }
+ };
+ private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
+
+ @Override
+ public void onOpened(CameraDevice cameraDevice) {
+ int id = Integer.parseInt(cameraDevice.getId());
+ Log.d(TAG, "onOpened " + id);
+ mCameraOpenCloseLock.release();
+ if (mPaused) {
+ return;
+ }
+ if (MODE == DUAL_MODE && id == BAYER_ID) {
+ Message msg = Message.obtain();
+ msg.what = OPEN_CAMERA;
+ msg.arg1 = MONO_ID;
+ mCameraHandler.sendMessage(msg);
+ }
+ if (!mInitialized) {
+ mInitialized = true;
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mUI.onCameraOpened(mCharacteristics, mCharacteristicsIndex,
+ mPreferenceGroup, prefListener);
+ }
+ });
+ }
mCameraDevice[id] = cameraDevice;
- mCameraOpened = true;
+ mCameraOpened[id] = true;
createSession(id);
}
@Override
public void onDisconnected(CameraDevice cameraDevice) {
- mCameraOpenCloseLock.release();
+ int id = Integer.parseInt(cameraDevice.getId());
+ Log.d(TAG, "onDisconnected " + id);
cameraDevice.close();
mCameraDevice = null;
+ mCameraOpenCloseLock.release();
}
@Override
public void onError(CameraDevice cameraDevice, int error) {
int id = Integer.parseInt(cameraDevice.getId());
- mCameraOpenCloseLock.release();
+ Log.d(TAG, "onError " + id + error);
cameraDevice.close();
- mCameraDevice = null;
+ mCameraDevice[id] = null;
+ mCameraOpenCloseLock.release();
if (null != mActivity) {
mActivity.finish();
}
}
+ @Override
+ public void onClosed(CameraDevice cameraDevice) {
+ int id = Integer.parseInt(cameraDevice.getId());
+ Log.d(TAG, "onClosed " + id);
+ mCameraDevice[id] = null;
+ mCameraOpenCloseLock.release();
+ }
+
};
+ public static boolean setMode(String value) {
+ int mode = DUAL_MODE;
+ switch (value) {
+ case "dual":
+ mode = DUAL_MODE;
+ ClearSightNativeEngine.createInstance();
+ break;
+ case "bayer":
+ mode = BAYER_MODE;
+ break;
+ case "mono":
+ mode = MONO_MODE;
+ break;
+ }
+ if (MODE == mode) return false;
+ MODE = mode;
+ return true;
+ }
+
+ public static int getQualityNumber(String jpegQuality) {
+ try {
+ int qualityPercentile = Integer.parseInt(jpegQuality);
+ if (qualityPercentile >= 0 && qualityPercentile <= 100)
+ return qualityPercentile;
+ else
+ return 85;
+ } catch (NumberFormatException nfe) {
+ //chosen quality is not a number, continue
+ }
+ int value = 0;
+ switch (jpegQuality) {
+ case "superfine":
+ value = CameraProfile.QUALITY_HIGH;
+ break;
+ case "fine":
+ value = CameraProfile.QUALITY_MEDIUM;
+ break;
+ case "normal":
+ value = CameraProfile.QUALITY_LOW;
+ break;
+ default:
+ return 85;
+ }
+ return CameraProfile.getJpegEncodingQualityParameter(value);
+ }
+
+ private void initializeFirstTime() {
+ if (mFirstTimeInitialized || mPaused) {
+ return;
+ }
+
+ //Todo: test record location. Jack to provide instructions
+ // Initialize location service.
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+
+ mUI.initializeFirstTime();
+ MediaSaveService s = mActivity.getMediaSaveService();
+ // We set the listener only when both service and shutterbutton
+ // are initialized.
+ if (s != null) {
+ s.setListener(this);
+ }
+
+ mNamedImages = new NamedImages();
+ mFirstTimeInitialized = true;
+ }
+
+ private void initializeSecondTime() {
+ // Start location update if needed.
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+ MediaSaveService s = mActivity.getMediaSaveService();
+ if (s != null) {
+ s.setListener(this);
+ }
+ mNamedImages = new NamedImages();
+ mUI.initializeSecondTime();
+ }
+
private void createSession(final int id) {
- if (!mCameraOpened || !mSurfaceReady) return;
+ if (mPaused || !mCameraOpened[id] || !mSurfaceReady) return;
List<Surface> list = new LinkedList<Surface>();
mUI.hidePreviewCover();
try {
Surface surface;
- if (id == 0) {
+ if (id == BAYER_ID || (id == MONO_ID && MODE == MONO_MODE)) {
SurfaceHolder sh = mUI.getSurfaceHolder();
if (sh == null) {
return;
@@ -344,139 +981,292 @@ public class CaptureModule implements CameraModule, PhotoController {
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder[id] = mCameraDevice[id].createCaptureRequest(CameraDevice
.TEMPLATE_PREVIEW);
+ mPreviewRequestBuilder[id].setTag(id);
mPreviewRequestBuilder[id].addTarget(surface);
- // This is the CaptureRequest.Builder that we use to take a picture.
- mCaptureBuilder[id] =
- mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
- mCaptureBuilder[id].addTarget(mImageReader[id].getSurface());
-
- // Use the same AE and AF modes as the preview.
- mCaptureBuilder[id].set(CaptureRequest.CONTROL_AF_MODE,
- CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
- mCaptureBuilder[id].set(CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
-
- list.add(surface);
- list.add(mImageReader[id].getSurface());
- // Here, we create a CameraCaptureSession for camera preview.
- mCameraDevice[id].createCaptureSession(list,
+ CameraCaptureSession.StateCallback captureSessionCallback =
new CameraCaptureSession.StateCallback() {
- @Override
- public void onConfigured(CameraCaptureSession cameraCaptureSession) {
- // The camera is already closed
- if (null == mCameraDevice[id]) {
- return;
- }
- // When the session is ready, we start displaying the preview.
- mCaptureSession[id] = cameraCaptureSession;
- try {
- // Auto focus should be continuous for camera preview.
- mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_MODE,
- CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
- // Flash is automatically enabled when necessary.
- mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
-
- // Finally, we start displaying the camera preview.
- mPreviewRequest[id] = mPreviewRequestBuilder[id].build();
- mCaptureSession[id].setRepeatingRequest(mPreviewRequest[id],
- mCaptureCallback, mCameraHandler);
- applyAllSettings(0);
- } catch (CameraAccessException e) {
- e.printStackTrace();
- }
+ @Override
+ public void onConfigured(CameraCaptureSession cameraCaptureSession) {
+ // The camera is already closed
+ if (mPaused || null == mCameraDevice[id]) {
+ return;
+ }
+ // When the session is ready, we start displaying the preview.
+ mCaptureSession[id] = cameraCaptureSession;
+ initializePreviewConfiguration(id);
+ try {
+ if (MODE == DUAL_MODE) {
+ linkBayerMono(id);
+ mIsLinked = true;
}
+ // Finally, we start displaying the camera preview.
+ mCaptureSession[id].setRepeatingRequest(mPreviewRequestBuilder[id]
+ .build(), mCaptureCallback, mCameraHandler);
- @Override
- public void onConfigureFailed(
- CameraCaptureSession cameraCaptureSession) {
+ // For Clearsight
+ if(mCaptureSession[id].isReprocessable()) {
+ mImageWriter[id] = ImageWriter.newInstance(cameraCaptureSession.getInputSurface(), NUM_IMAGES_TO_BURST);
}
- }, null
- );
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
+ Log.d(TAG, "cameracapturesession - onConfigureFailed");
+ }
+
+ @Override
+ public void onClosed(CameraCaptureSession session) {
+ Log.d(TAG, "cameracapturesession - onClosed");
+ }
+ };
+
+ list.add(surface);
+ list.add(mImageReader[id].getSurface());
+
+ ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CLEARSIGHT);
+ if(pref.getValue().equals(mActivity.getString(R.string.pref_camera_clearsight_value_on))) {
+ list.add(mReprocessImageReader[id].getSurface());
+ // Here, we create a CameraCaptureSession for camera preview.
+ mCameraDevice[id].createReprocessableCaptureSession(
+ new InputConfiguration(mImageReader[id].getWidth(),
+ mImageReader[id].getHeight(), mImageReader[id].getImageFormat()),
+ list, captureSessionCallback, null);
+ } else {
+ // Here, we create a CameraCaptureSession for camera preview.
+ mCameraDevice[id].createCaptureSession(list, captureSessionCallback, null);
+ }
} catch (CameraAccessException e) {
}
}
@Override
public void init(CameraActivity activity, View parent) {
- mCameraOpened = false;
+ Log.d(TAG, "init");
+ for (int i = 0; i < MAX_NUM_CAM; i++) {
+ mCameraOpened[i] = false;
+ }
mSurfaceReady = false;
+
mActivity = activity;
+ for (int i = 0; i < MAX_NUM_CAM; i++) {
+ mState[i] = STATE_PREVIEW;
+ }
mPreferences = new ComboPreferences(mActivity);
CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal(), activity);
- mPreferences.setLocalId(mActivity, 0);
+ mPreferences.setLocalId(mActivity, BAYER_ID);
CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+ PreferenceInflater inflater = new PreferenceInflater(mActivity);
+ PreferenceGroup group =
+ (PreferenceGroup) inflater.inflate(R.xml.camera_preferences);
+ mPreferenceGroup = group;
+
+ ListPreference pref = group.findPreference(CameraSettings.KEY_DUAL_CAMERA);
+ setMode(pref.getValue());
+
mContentResolver = mActivity.getContentResolver();
mUI = new CaptureUI(activity, this, parent);
mUI.initializeControlByIntent();
- mNamedImages = new NamedImages();
+ mFocusStateListener = new FocusStateListener(mUI);
+ mLocationManager = new LocationManager(mActivity, mUI);
+ Storage.setSaveSDCard(
+ mPreferences.getString(CameraSettings.KEY_CAMERA_SAVEPATH, "0").equals("1"));
}
/**
* Initiate a still image capture.
*/
private void takePicture() {
- lockFocus(0);
+ Log.d(TAG, "takePicture");
+ switch (MODE) {
+ case DUAL_MODE:
+ lockFocus(BAYER_ID);
+ lockFocus(MONO_ID);
+ break;
+ case BAYER_MODE:
+ lockFocus(BAYER_ID);
+ break;
+ case MONO_MODE:
+ lockFocus(MONO_ID);
+ break;
+ }
}
/**
* Lock the focus as the first step for a still image capture.
*/
private void lockFocus(int id) {
+ Log.d(TAG, "lockFocus " + id);
try {
- // This is how to tell the camera to lock focus.
- mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_TRIGGER,
- CameraMetadata.CONTROL_AF_TRIGGER_START);
- // Tell #mCaptureCallback to wait for the lock.
- mState = STATE_WAITING_LOCK;
- mCaptureSession[id].capture(mPreviewRequestBuilder[id].build(), mCaptureCallback,
- mCameraHandler);
+ CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice
+ .TEMPLATE_PREVIEW);
+ builder.setTag(id);
+ builder.addTarget(getPreviewSurface(id));
+
+ builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
+ builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
+ applyWhiteBalance(builder);
+ applyZoom(builder, id);
+ mState[id] = STATE_WAITING_LOCK;
+ mCaptureSession[id].capture(builder.build(), mCaptureCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
+ private void autoFocusTrigger(int id) {
+ Log.d(TAG, "autoFocusTrigger " + id);
+ try {
+ CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice
+ .TEMPLATE_PREVIEW);
+ builder.setTag(id);
+ builder.addTarget(getPreviewSurface(id));
+
+ builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO);
+ builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
+ applyWhiteBalance(builder);
+ applyZoom(builder, id);
+ applyAFRegions(builder, id);
+ mCaptureSession[id].capture(builder.build(), mCaptureCallback, mCameraHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void linkBayerMono(int id) {
+ Log.d(TAG, "linkBayerMono " + id);
+ if (id == BAYER_ID) {
+ mPreviewRequestBuilder[id].set(BayerMonoLinkEnableKey, (byte) 1);
+ mPreviewRequestBuilder[id].set(BayerMonoLinkMainKey, (byte) 1);
+ mPreviewRequestBuilder[id].set(BayerMonoLinkSessionIdKey, MONO_ID);
+ } else if (id == MONO_ID) {
+ mPreviewRequestBuilder[id].set(BayerMonoLinkEnableKey, (byte) 1);
+ mPreviewRequestBuilder[id].set(BayerMonoLinkMainKey, (byte) 0);
+ mPreviewRequestBuilder[id].set(BayerMonoLinkSessionIdKey, BAYER_ID);
+ }
+ }
+
+ public void unLinkBayerMono(int id) {
+ Log.d(TAG, "unlinkBayerMono " + id);
+ if (id == BAYER_ID) {
+ mPreviewRequestBuilder[id].set(BayerMonoLinkEnableKey, (byte) 0);
+ } else if (id == MONO_ID) {
+ mPreviewRequestBuilder[id].set(BayerMonoLinkEnableKey, (byte) 0);
+ }
+ }
+
+
/**
* Capture a still picture. This method should be called when we get a response in
* {@link #mCaptureCallback} from both {@link #lockFocus()}.
*/
private void captureStillPicture(final int id) {
+ Log.d(TAG, "captureStillPicture " + id);
try {
- if (null == mActivity || null == mCameraDevice) {
+ if (null == mActivity || null == mCameraDevice[id]) {
return;
}
- // Orientation
- int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
- mCaptureBuilder[id].set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
- List<CaptureRequest> burstList = new ArrayList<CaptureRequest>();
+ ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CLEARSIGHT);
+ final boolean csEnabled = pref.getValue().equals(
+ mActivity.getString(R.string.pref_camera_clearsight_value_on));
+ CaptureRequest.Builder captureBuilder;
+ CameraCaptureSession.CaptureCallback captureCallback;
+
+ if(csEnabled) {
+ captureBuilder = mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+
+ // Orientation
+ // int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
+ // captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
+
+ captureCallback = new CameraCaptureSession.CaptureCallback() {
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session,
+ CaptureRequest request,
+ TotalCaptureResult result) {
+ Log.d(TAG, "captureStillPicture onCaptureCompleted: " + id);
+ result.dumpToLog();
+ mImageProcessHandler.obtainMessage(MSG_NEW_RESULT,
+ id, 0, result).sendToTarget();
+ }
+
+ @Override
+ public void onCaptureFailed(CameraCaptureSession session,
+ CaptureRequest request,
+ CaptureFailure result) {
+ Log.d(TAG, "captureStillPicture onCaptureFailed: " + id);
+ mImageProcessHandler.obtainMessage(MSG_NEW_RESULT,
+ id, 1, result).sendToTarget();
+ }
- for (int i = 0; i < 1; i++) {
- burstList.add(mCaptureBuilder[id].build());
+ @Override
+ public void onCaptureSequenceCompleted(CameraCaptureSession session, int
+ sequenceId, long frameNumber) {
+ Log.d(TAG, "captureStillPicture onCaptureSequenceCompleted: " + id);
+ }
+ };
+ } else {
+ // No Clearsight
+ captureBuilder = mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+
+ // Orientation
+ int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
+ captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
+ captureCallback = new CameraCaptureSession.CaptureCallback() {
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session,
+ CaptureRequest request,
+ TotalCaptureResult result) {
+ Log.d(TAG, "captureStillPicture onCaptureCompleted: " + id);
+ }
+
+ @Override
+ public void onCaptureFailed(CameraCaptureSession session,
+ CaptureRequest request,
+ CaptureFailure result) {
+ Log.d(TAG, "captureStillPicture onCaptureFailed: " + id);
+ }
+
+ @Override
+ public void onCaptureSequenceCompleted(CameraCaptureSession session, int
+ sequenceId, long frameNumber) {
+ Log.d(TAG, "captureStillPicture onCaptureSequenceCompleted: " + id);
+ unlockFocus(id);
+ }
+ };
}
- CameraCaptureSession.CaptureCallback CaptureCallback
- = new CameraCaptureSession.CaptureCallback() {
+ captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ captureBuilder.addTarget(getPreviewSurface(id));
+ captureBuilder.addTarget(mImageReader[id].getSurface());
+ captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
+ captureBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+ applyCaptureSettings(captureBuilder, id);
- @Override
- public void onCaptureCompleted(CameraCaptureSession session,
- CaptureRequest request,
- TotalCaptureResult result) {
- }
+ mCaptureSession[id].stopRepeating();
- @Override
- public void onCaptureSequenceCompleted(CameraCaptureSession session, int
- sequenceId, long frameNumber) {
- unlockFocus(id);
+ if(csEnabled) {
+ List<CaptureRequest> burstList = new ArrayList<CaptureRequest>();
+ for (int i = 0; i < NUM_IMAGES_TO_BURST; i++) {
+ burstList.add(captureBuilder.build());
}
-
- };
- mCaptureSession[id].captureBurst(burstList, CaptureCallback, mCallbackHandler);
+ mImageProcessHandler.obtainMessage(MSG_START_CAPTURE, id, burstList.size()).sendToTarget();
+ mCaptureSession[id].captureBurst(burstList, captureCallback, mCallbackHandler);
+ } else {
+ mCaptureSession[id].capture(captureBuilder.build(), captureCallback, mCallbackHandler);
+ }
} catch (CameraAccessException e) {
Log.d(TAG, "Capture still picture has failed");
e.printStackTrace();
@@ -488,14 +1278,21 @@ public class CaptureModule implements CameraModule, PhotoController {
* we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}.
*/
private void runPrecaptureSequence(int id) {
+ Log.d(TAG, "runPrecaptureSequence");
try {
- // This is how to tell the camera to trigger.
- mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice
+ .TEMPLATE_PREVIEW);
+ builder.setTag(id);
+ builder.addTarget(getPreviewSurface(id));
+ builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+ builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
- // Tell #mCaptureCallback to wait for the precapture sequence to be set.
- mState = STATE_WAITING_PRECAPTURE;
- mCaptureSession[id].capture(mPreviewRequestBuilder[id].build(), mCaptureCallback,
- mCameraHandler);
+ // Applying flash only to capture does not work. Need to apply flash here.
+ applyFlash(builder);
+ applyWhiteBalance(builder);
+ applyZoom(builder, id);
+ mState[id] = STATE_WAITING_PRECAPTURE;
+ mCaptureSession[id].capture(builder.build(), mCaptureCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
@@ -508,33 +1305,74 @@ public class CaptureModule implements CameraModule, PhotoController {
* @param height The height of available size for camera preview
*/
private void setUpCameraOutputs() {
- Activity activity = mActivity;
- CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
+ CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try {
String[] cameraIdList = manager.getCameraIdList();
- for (int i = 0; i < NUMCAM; i++) {
+
+ for (int i = 0; i < cameraIdList.length; i++) {
String cameraId = cameraIdList[i];
CameraCharacteristics characteristics
- = manager.getCameraCharacteristics(cameraId);
-
+ = manager.getCameraCharacteristics(cameraId);
+ mCharacteristics[i] = characteristics;
+ mCharacteristicsIndex.add(i);
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
+ Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ Log.d(TAG, "flash : " + (available == null ? false : available));
+ mCameraId[i] = cameraId;
- // For still image captures, we use the largest available size.
- Size largest = Collections.max(
- Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
- new CompareSizesByArea());
+ ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CLEARSIGHT);
+ if(pref.getValue().equals(mActivity.getString(R.string.pref_camera_clearsight_value_on))) {
+ // For still image captures, we use the largest available size.
+ Size largest = Collections.max(
+ Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)),
+ new CompareSizesByArea());
- mImageReader[i] = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
- ImageFormat.JPEG, 10);
- mImageReader[i].setOnImageAvailableListener(
- mOnImageAvailableListener, mImageAvailableHandler);
- mCameraId[i] = cameraId;
- //return;
+ mImageReader[i] = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
+ ImageFormat.YUV_420_888, NUM_IMAGES_TO_BURST);
+ mImageReader[i].setOnImageAvailableListener(new ImageAvailableListener(i) {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Log.d(TAG, "image available for cam: " + mCamId);
+ mImageProcessHandler.obtainMessage(
+ MSG_NEW_IMG, mCamId, 0, reader.acquireNextImage()).sendToTarget();
+ }
+ }, mImageAvailableHandler);
+
+ mReprocessImageReader[i] = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
+ ImageFormat.YUV_420_888, NUM_IMAGES_TO_BURST);
+ mReprocessImageReader[i].setOnImageAvailableListener(new ImageAvailableListener(i) {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Log.d(TAG, "reprocessed image available for cam: " + mCamId);
+ mImageReprocessHandler.obtainMessage(
+ MSG_NEW_IMG, mCamId, 0, reader.acquireNextImage()).sendToTarget();
+ }
+ }, mImageAvailableHandler);
+ } else {
+ // No Clearsight
+ // For still image captures, we use the largest available size.
+ Size largest = Collections.max(
+ Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
+ new CompareSizesByArea());
+
+ mImageReader[i] = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
+ ImageFormat.JPEG, 3);
+ mImageReader[i].setOnImageAvailableListener(new ImageAvailableListener(i) {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Log.d(TAG, "image available for cam: " + mCamId);
+ mImageProcessHandler.obtainMessage(
+ MSG_SAVE, mCamId, 0, reader.acquireNextImage()).sendToTarget();
+ }
+ }, mImageAvailableHandler);
+ }
}
+ mAutoFocusSupported = CameraUtil.isAutoFocusSupported(mCharacteristics,
+ mCharacteristicsIndex);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
@@ -546,45 +1384,81 @@ public class CaptureModule implements CameraModule, PhotoController {
* finished.
*/
private void unlockFocus(int id) {
+ Log.d(TAG, "unlockFocus " + id);
try {
- // Reset the auto-focus trigger
- mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_TRIGGER,
- CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
- mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
- mCaptureSession[id].capture(mPreviewRequestBuilder[id].build(), mCaptureCallback,
- mCameraHandler);
- // After this, the camera will go back to the normal state of preview.
- mState = STATE_PREVIEW;
- mCaptureSession[id].setRepeatingRequest(mPreviewRequest[id], mCaptureCallback,
- mCameraHandler);
+ CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice
+ .TEMPLATE_PREVIEW);
+ builder.setTag(id);
+ builder.addTarget(getPreviewSurface(id));
+ builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest
+ .CONTROL_MODE_AUTO);
+ builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest
+ .CONTROL_AF_MODE_AUTO);
+ builder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
+ //Todo: Create applyCommonSettings function for settings applied everytime
+ applyWhiteBalance(builder);
+ applyZoom(builder, id);
+ mCaptureSession[id].capture(builder.build(), mCaptureCallback, mCameraHandler);
+ mState[id] = STATE_PREVIEW;
+ mCaptureSession[id].setRepeatingRequest(mPreviewRequestBuilder[id].build(),
+ mCaptureCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
+
/**
* Closes the current {@link CameraDevice}.
*/
private void closeCamera() {
+ Log.d(TAG, "closeCamera");
try {
mCameraOpenCloseLock.acquire();
- for (int i = 0; i < NUMCAM; i++) {
+ for (int i = 0; i < MAX_NUM_CAM; i++) {
if (null != mCaptureSession[i]) {
+ if (mIsLinked) {
+ unLinkBayerMono(i);
+ try {
+ mCaptureSession[i].capture(mPreviewRequestBuilder[i].build(), null,
+ mCameraHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
mCaptureSession[i].close();
mCaptureSession[i] = null;
}
- if (null != mCameraDevice[i]) {
- mCameraDevice[i].close();
- mCameraDevice[i] = null;
- mCameraOpened = false;
- }
if (null != mImageReader[i]) {
mImageReader[i].close();
mImageReader[i] = null;
}
+ if (null != mReprocessImageReader[i]) {
+ mReprocessImageReader[i].close();
+ mReprocessImageReader[i] = null;
+ }
+ if (null != mImageWriter[i]) {
+ mImageWriter[i].close();
+ mImageWriter[i] = null;
+ }
+ }
+ for (int i = 0; i < MAX_NUM_CAM; i++) {
+ if (null != mCameraDevice[i]) {
+ mCameraDevice[i].close();
+ mCameraDevice[i] = null;
+ mCameraOpened[i] = false;
+ }
}
+ /* no need to set this in the callback and handle asynchronously. This is the same
+ reason as why we release the semaphore here, not in camera close callback function
+ as we don't have to protect the case where camera open() gets called during camera
+ close(). The low level framework/HAL handles the synchronization for open()
+ happens after close() */
+ mIsLinked = false;
} catch (InterruptedException e) {
+ mCameraOpenCloseLock.release();
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
} finally {
mCameraOpenCloseLock.release();
@@ -601,10 +1475,16 @@ public class CaptureModule implements CameraModule, PhotoController {
mImageAvailableThread.start();
mCallbackThread = new HandlerThread("CameraCallback");
mCallbackThread.start();
+ mImageProcessThread = new HandlerThread("CameraImageProcess");
+ mImageProcessThread.start();
+ mImageReprocessThread = new HandlerThread("CameraImageReprocess");
+ mImageReprocessThread.start();
- mCameraHandler = new Handler(mCameraThread.getLooper());
+ mCameraHandler = new MyCameraHandler(mCameraThread.getLooper());
mImageAvailableHandler = new Handler(mImageAvailableThread.getLooper());
mCallbackHandler = new Handler(mCallbackThread.getLooper());
+ mImageProcessHandler = new ImageProcessHandler(mImageProcessThread.getLooper());
+ mImageReprocessHandler = new ImageReprocessHandler(mImageReprocessThread.getLooper());
}
/**
@@ -614,6 +1494,8 @@ public class CaptureModule implements CameraModule, PhotoController {
mCameraThread.quitSafely();
mImageAvailableThread.quitSafely();
mCallbackThread.quitSafely();
+ mImageProcessThread.quitSafely();
+ mImageReprocessThread.quitSafely();
try {
mCameraThread.join();
mCameraThread = null;
@@ -635,15 +1517,34 @@ public class CaptureModule implements CameraModule, PhotoController {
} catch (InterruptedException e) {
e.printStackTrace();
}
+ try {
+ mImageProcessThread.join();
+ mImageProcessThread = null;
+ mImageProcessHandler = null;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ try {
+ mImageReprocessThread.join();
+ mImageReprocessThread = null;
+ mImageReprocessHandler = null;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
}
private void openCamera(int id) {
+ if (mPaused) {
+ return;
+ }
+ Log.d(TAG, "openCamera " + id);
CameraManager manager;
try {
manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
mCameraId[id] = manager.getCameraIdList()[id];
- if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
+ if (!mCameraOpenCloseLock.tryAcquire(5000, TimeUnit.MILLISECONDS)) {
Log.d(TAG, "Time out waiting to lock camera opening.");
+ throw new RuntimeException("Time out waiting to lock camera opening");
}
manager.openCamera(mCameraId[id], mStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
@@ -655,35 +1556,57 @@ public class CaptureModule implements CameraModule, PhotoController {
@Override
public void onPreviewFocusChanged(boolean previewFocused) {
-
+ mUI.onPreviewFocusChanged(previewFocused);
}
@Override
public void onPauseBeforeSuper() {
-
+ mPaused = true;
}
@Override
public void onPauseAfterSuper() {
+ Log.d(TAG, "onPause");
mUI.showPreviewCover();
- stopBackgroundThread();
+ if (mLocationManager != null) mLocationManager.recordLocation(false);
closeCamera();
+ stopBackgroundThread();
mUI.onPause();
}
@Override
public void onResumeBeforeSuper() {
-
+ mPaused = false;
}
@Override
public void onResumeAfterSuper() {
- setUpCameraOutputs();
- openCamera(0);
+ Log.d(TAG, "onResume " + MODE);
+ mCharacteristicsIndex = new ArrayList<>();
startBackgroundThread();
+ setUpCameraOutputs();
+ readInitialValues();
+ Message msg = Message.obtain();
+ msg.what = OPEN_CAMERA;
+ switch (MODE) {
+ case DUAL_MODE:
+ case BAYER_MODE:
+ msg.arg1 = BAYER_ID;
+ mCameraHandler.sendMessage(msg);
+ break;
+ case MONO_MODE:
+ msg.what = OPEN_CAMERA;
+ msg.arg1 = MONO_ID;
+ mCameraHandler.sendMessage(msg);
+ break;
+ }
+ if (!mFirstTimeInitialized) {
+ initializeFirstTime();
+ } else {
+ initializeSecondTime();
+ }
mUI.hidePreviewCover();
mUI.enableShutter(true);
- mUI.initializeFirstTime();
}
@Override
@@ -708,7 +1631,7 @@ public class CaptureModule implements CameraModule, PhotoController {
@Override
public boolean onBackPressed() {
- return false;
+ return mUI.onBackPressed();
}
@Override
@@ -727,13 +1650,31 @@ public class CaptureModule implements CameraModule, PhotoController {
}
@Override
+ public void onZoomChanged(float requestedZoom) {
+ mZoomValue = requestedZoom;
+
+ switch (MODE) {
+ case DUAL_MODE:
+ applyZoomAndUpdate(BAYER_ID);
+ applyZoomAndUpdate(MONO_ID);
+ break;
+ case BAYER_MODE:
+ applyZoomAndUpdate(BAYER_ID);
+ break;
+ case MONO_MODE:
+ applyZoomAndUpdate(MONO_ID);
+ break;
+ }
+ }
+
+ @Override
public boolean isImageCaptureIntent() {
return false;
}
@Override
public boolean isCameraIdle() {
- return false;
+ return true;
}
@Override
@@ -768,7 +1709,42 @@ public class CaptureModule implements CameraModule, PhotoController {
@Override
public void onSingleTapUp(View view, int x, int y) {
+ if (mPaused || mCameraDevice == null || !mFirstTimeInitialized || !mAutoFocusSupported ||
+ !isStatePreview()) {
+ return;
+ }
+ mUI.setFocusPosition(x, y);
+ mUI.onFocusStarted();
+ switch (MODE) {
+ case DUAL_MODE:
+ triggerFocusAtPoint(x, y, BAYER_ID);
+ triggerFocusAtPoint(x, y, MONO_ID);
+ break;
+ case BAYER_MODE:
+ triggerFocusAtPoint(x, y, BAYER_ID);
+ break;
+ case MONO_MODE:
+ triggerFocusAtPoint(x, y, MONO_ID);
+ break;
+ }
+ }
+ private int getMainCameraId() {
+ switch (MODE) {
+ case DUAL_MODE:
+ case BAYER_MODE:
+ return BAYER_ID;
+ case MONO_MODE:
+ return MONO_ID;
+ }
+ return 0;
+ }
+
+ private boolean isStatePreview() {
+ for (int i = 0; i < mState.length; i++) {
+ if (mState[i] != STATE_PREVIEW) return false;
+ }
+ return true;
}
@Override
@@ -793,13 +1769,37 @@ public class CaptureModule implements CameraModule, PhotoController {
@Override
public void enableRecordingLocation(boolean enable) {
+ Log.d(TAG, "CaptureModule enableRecordingLocation " + enable);
+ setLocationPreference(enable ? RecordLocationPreference.VALUE_ON
+ : RecordLocationPreference.VALUE_OFF);
+ }
+ private void setLocationPreference(String value) {
+ mPreferences.edit()
+ .putString(CameraSettings.KEY_RECORD_LOCATION, value)
+ .apply();
+ prefListener.onSharedPreferenceChanged();
}
@Override
public void onPreviewUIReady() {
+ if (mPaused) {
+ return;
+ }
+ Log.d(TAG, "onPreviewUIReady");
mSurfaceReady = true;
- createSession(0);
+ switch (MODE) {
+ case DUAL_MODE:
+ createSession(BAYER_ID);
+ createSession(MONO_ID);
+ break;
+ case BAYER_MODE:
+ createSession(BAYER_ID);
+ break;
+ case MONO_MODE:
+ createSession(MONO_ID);
+ break;
+ }
}
@Override
@@ -829,7 +1829,15 @@ public class CaptureModule implements CameraModule, PhotoController {
@Override
public void onOrientationChanged(int orientation) {
-
+ // We keep the last known orientation. So if the user first orient
+ // the camera then point the camera to floor or sky, we still have
+ // the correct orientation.
+ if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+ int oldOrientation = mOrientation;
+ mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
+ if (oldOrientation != mOrientation) {
+ mUI.setOrientation(mOrientation, true);
+ }
}
@Override
@@ -839,7 +1847,9 @@ public class CaptureModule implements CameraModule, PhotoController {
@Override
public void onMediaSaveServiceConnected(MediaSaveService s) {
-
+ if (mFirstTimeInitialized) {
+ s.setListener(this);
+ }
}
@Override
@@ -854,7 +1864,15 @@ public class CaptureModule implements CameraModule, PhotoController {
@Override
public void onSwitchSavePath() {
-
+ if (mUI.mMenuInitialized) {
+ mUI.setPreference(CameraSettings.KEY_CAMERA_SAVEPATH, "1");
+ } else {
+ mPreferences.edit()
+ .putString(CameraSettings.KEY_CAMERA_SAVEPATH, "1")
+ .apply();
+ }
+ RotateTextToast.makeText(mActivity, R.string.on_switch_save_path_to_sdcard,
+ Toast.LENGTH_SHORT).show();
}
@Override
@@ -872,23 +1890,225 @@ public class CaptureModule implements CameraModule, PhotoController {
}
+ private boolean isFlashOff() {
+ return readSetting(CameraSettings.KEY_FLASH_MODE).equals("off");
+ }
+
+ private void initializePreviewConfiguration(int id) {
+ mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_MODE, CaptureRequest
+ .CONTROL_MODE_AUTO);
+ mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest
+ .CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest
+ .CONTROL_AF_TRIGGER_IDLE);
+ mPreviewRequestBuilder[id].set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest
+ .CONTROL_AE_MODE_ON);
+ mPreviewRequestBuilder[id].set(CaptureRequest.FLASH_MODE, CaptureRequest
+ .FLASH_MODE_OFF);
+ applyWhiteBalance(mPreviewRequestBuilder[id]);
+ applyZoom(mPreviewRequestBuilder[id], id);
+ }
+
+ private void readInitialValues() {
+ for (int i = 0; i < mPreferenceGroup.size(); i++) {
+ CameraPreference pref = mPreferenceGroup.get(i);
+ if (pref instanceof ListPreference) {
+ ListPreference listPref = (ListPreference) pref;
+ storeSetting(listPref.getKey(), listPref.getValue());
+ }
+ }
+ }
+
+ public Rect cropRegionForZoom(int id) {
+ Log.d(TAG, "cropRegionForZoom " + id);
+ Rect activeRegion = mCharacteristics[id].get(CameraCharacteristics
+ .SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ Rect cropRegion = new Rect();
+
+ int xCenter = activeRegion.width() / 2;
+ int yCenter = activeRegion.height() / 2;
+ int xDelta = (int) (activeRegion.width() / (2 * mZoomValue));
+ int yDelta = (int) (activeRegion.height() / (2 * mZoomValue));
+ cropRegion.set(xCenter - xDelta, yCenter - yDelta, xCenter + xDelta, yCenter + yDelta);
+ mCropRegion[id] = cropRegion;
+ return mCropRegion[id];
+ }
+
+ private void applyZoom(CaptureRequest.Builder request, int id) {
+ request.set(CaptureRequest.SCALER_CROP_REGION, cropRegionForZoom(id));
+ }
+
+ private void applyCaptureSettings(CaptureRequest.Builder request, int id) {
+ applyFlash(request);
+ applyWhiteBalance(request);
+ applyJpegQuality(request);
+ applyZoom(request, id);
+ }
+
private void applyPreference(int cameraId, ListPreference pref) {
- if (pref.getKey().equals(CameraSettings.KEY_FLASH_MODE)) {
- if (pref.getValue().equals("on")) {
- mCaptureBuilder[cameraId].set(CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
- } else if (pref.getValue().equals("auto")) {
- mCaptureBuilder[cameraId].set(CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
- } else if (pref.getValue().equals("off")) {
- mCaptureBuilder[cameraId].set(CaptureRequest.CONTROL_AE_MODE,
- CaptureRequest.CONTROL_AE_MODE_ON);
+ boolean updatePreview = false;
+ String key = pref.getKey();
+ String value = pref.getValue();
+ storeSetting(key, value);
+ switch (key) {
+ //Todo: CreateUISettings file and add UI preference settings to there
+ case CameraSettings.KEY_JPEG_QUALITY:
+ mJpegQuality = getQualityNumber(value);
+ break;
+ case CameraSettings.KEY_WHITE_BALANCE:
+ updatePreview = true;
+ applyWhiteBalance(mPreviewRequestBuilder[cameraId]);
+ break;
+ }
+ if (updatePreview) {
+ try {
+ mCaptureSession[cameraId].setRepeatingRequest(mPreviewRequestBuilder[cameraId]
+ .build(), mCaptureCallback, mCameraHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
}
}
}
- private void applyAllSettings(int cameraId) {
+ private void applyZoomAndUpdate(int id) {
+ applyZoom(mPreviewRequestBuilder[id], id);
+ try {
+ mCaptureSession[id].setRepeatingRequest(mPreviewRequestBuilder[id]
+ .build(), mCaptureCallback, mCameraHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void storeSetting(String key, String value) {
+ mSettings.put(key, value);
+ }
+
+ private String readSetting(String key) {
+ return mSettings.get(key);
+ }
+
+ private void applyJpegQuality(CaptureRequest.Builder request) {
+ request.set(CaptureRequest.JPEG_QUALITY, (byte) mJpegQuality);
+ }
+
+ private void applyAFRegions(CaptureRequest.Builder request, int id) {
+ request.set(CaptureRequest.CONTROL_AF_REGIONS, mAFRegions[id]);
+ }
+
+ private void applyWhiteBalance(CaptureRequest.Builder request) {
+ String value = readSetting(CameraSettings.KEY_WHITE_BALANCE);
+ switch (value) {
+ case "incandescent":
+ request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest
+ .CONTROL_AWB_MODE_INCANDESCENT);
+ break;
+ case "fluorescent":
+ request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest
+ .CONTROL_AWB_MODE_FLUORESCENT);
+ break;
+ case "auto":
+ request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest
+ .CONTROL_AWB_MODE_AUTO);
+ break;
+ case "daylight":
+ request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest
+ .CONTROL_AWB_MODE_DAYLIGHT);
+ break;
+ case "cloudy-daylight":
+ request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest
+ .CONTROL_AWB_MODE_CLOUDY_DAYLIGHT);
+ break;
+ }
+ }
+
+ private Surface getPreviewSurface(int id) {
+ if (MODE == DUAL_MODE && id == MONO_ID) {
+ return mUI.getSurfaceHolder2().getSurface();
+ } else {
+ return mUI.getSurfaceHolder().getSurface();
+ }
+ }
+
+ private void applyFlash(CaptureRequest.Builder request, String value) {
+ switch (value) {
+ case "on":
+ request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest
+ .CONTROL_AE_MODE_ON_ALWAYS_FLASH);
+ request.set(CaptureRequest.FLASH_MODE, CaptureRequest
+ .FLASH_MODE_SINGLE);
+ break;
+ case "auto":
+ request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest
+ .CONTROL_AE_MODE_ON_AUTO_FLASH);
+ break;
+ case "off":
+ request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest
+ .CONTROL_AE_MODE_ON);
+ request.set(CaptureRequest.FLASH_MODE, CaptureRequest
+ .FLASH_MODE_OFF);
+ break;
+ }
+ }
+
+ private void applyFlash(CaptureRequest.Builder request) {
+ String value = readSetting(CameraSettings.KEY_FLASH_MODE);
+ applyFlash(request, value);
+ }
+
+ @Override
+ public void onQueueStatus(boolean full) {
+ mUI.enableShutter(!full);
+ }
+
+ public void triggerFocusAtPoint(float x, float y, int id) {
+ Point p;
+ if (id == getMainCameraId()) {
+ p = mUI.getSurfaceViewSize();
+ } else {
+ p = mUI.getSurfaceView2Size();
+ }
+ int width = p.x;
+ int height = p.y;
+ x = x / width;
+ y = y / height;
+ mAFRegions[id] = afRectangle(x, y, mCropRegion[id]);
+ autoFocusTrigger(id);
+ }
+ private MeteringRectangle[] afRectangle(float x, float y, Rect cropRegion) {
+ int side = Math.max(cropRegion.width(), cropRegion.height()) / 8;
+ int xCenter = (int) (cropRegion.left + x * cropRegion.width());
+ int yCenter = (int) (cropRegion.top + y * cropRegion.height());
+ Rect meteringRegion = new Rect(xCenter - side / 2, yCenter - side / 2, xCenter +
+ side / 2, yCenter + side / 2);
+
+ meteringRegion.left = CameraUtil.clamp(meteringRegion.left, cropRegion.left, cropRegion
+ .right);
+ meteringRegion.top = CameraUtil.clamp(meteringRegion.top, cropRegion.top, cropRegion
+ .bottom);
+ meteringRegion.right = CameraUtil.clamp(meteringRegion.right, cropRegion.left, cropRegion
+ .right);
+ meteringRegion.bottom = CameraUtil.clamp(meteringRegion.bottom, cropRegion.top,
+ cropRegion.bottom);
+ MeteringRectangle[] meteringRectangle = new MeteringRectangle[1];
+ meteringRectangle[0] = new MeteringRectangle(meteringRegion, 1);
+ return meteringRectangle;
+ }
+
+ private void updateFocusStateChange(CaptureResult result) {
+ final int resultAFState = result.get(CaptureResult.CONTROL_AF_STATE);
+
+ // Report state change when AF state has changed.
+ if (resultAFState != mLastResultAFState && mFocusStateListener != null) {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mFocusStateListener.onFocusStatusUpdate(resultAFState);
+ }
+ });
+ }
+ mLastResultAFState = resultAFState;
}
/**
@@ -904,4 +2124,23 @@ public class CaptureModule implements CameraModule, PhotoController {
}
}
+
+ private class MyCameraHandler extends Handler {
+
+ public MyCameraHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case OPEN_CAMERA: {
+ int id = msg.arg1;
+ openCamera(id);
+ break;
+ }
+ }
+ }
+ }
+
}
diff --git a/src/com/android/camera/CaptureUI.java b/src/com/android/camera/CaptureUI.java
index 9095cd188..a6accae7b 100644
--- a/src/com/android/camera/CaptureUI.java
+++ b/src/com/android/camera/CaptureUI.java
@@ -24,8 +24,8 @@ import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.drawable.AnimationDrawable;
-import android.hardware.Camera;
import android.hardware.Camera.Face;
+import android.hardware.camera2.CameraCharacteristics;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -70,7 +70,7 @@ public class CaptureUI implements PieListener,
CameraManager.CameraFaceDetectionCallback {
private static final String TAG = "SnapCam_CaptureUI";
- private boolean mMenuInitialized = false;
+ public boolean mMenuInitialized = false;
private boolean surface1created = false;
private boolean surface2created = false;
private CameraActivity mActivity;
@@ -111,8 +111,6 @@ public class CaptureUI implements PieListener,
private OnScreenIndicators mOnScreenIndicators;
private PieRenderer mPieRenderer;
private ZoomRenderer mZoomRenderer;
- private int mZoomMax;
- private List<Integer> mZoomRatios;
private int mPreviewWidth = 0;
private int mPreviewHeight = 0;
private int mOriginalPreviewWidth = 0;
@@ -189,16 +187,12 @@ public class CaptureUI implements PieListener,
mPreviewCover = mRootView.findViewById(R.id.preview_cover);
// display the view
mSurfaceView = (SurfaceView) mRootView.findViewById(R.id.mdp_preview_content);
- mSurfaceView.setVisibility(View.VISIBLE);
mSurfaceView2 = (SurfaceView) mRootView.findViewById(R.id.mdp_preview_content2);
- //mSurfaceView2.setVisibility(View.VISIBLE);
+ mSurfaceView2.setZOrderMediaOverlay(true);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
- //mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceHolder2 = mSurfaceView2.getHolder();
mSurfaceHolder2.addCallback(callback);
- //mSurfaceHolder2.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- mSurfaceView.addOnLayoutChangeListener(mLayoutListener);
Log.v(TAG, "Using mdp_preview_content (MDP path)");
mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
@@ -284,7 +278,15 @@ public class CaptureUI implements PieListener,
mRootView.findViewById(R.id.on_screen_indicators));
}
- public void onCameraOpened(PreferenceGroup prefGroup, OnPreferenceChangedListener listener) {
+ public void onCameraOpened(CameraCharacteristics[] characteristics,
+ List<Integer> characteristicsIndex, PreferenceGroup prefGroup,
+ OnPreferenceChangedListener listener) {
+ if (mPieRenderer == null) {
+ mPieRenderer = new PieRenderer(mActivity);
+ mPieRenderer.setPieListener(this);
+ mRenderOverlay.addRenderer(mPieRenderer);
+ }
+
if (mMenu == null) {
mMenu = new CaptureMenu(mActivity, this);
mMenu.setListener(listener);
@@ -304,8 +306,11 @@ public class CaptureUI implements PieListener,
}
mGestures.setCaptureMenu(mMenu);
+ mGestures.setZoomEnabled(CameraUtil.isZoomSupported(characteristics, characteristicsIndex));
mGestures.setRenderOverlay(mRenderOverlay);
mRenderOverlay.requestLayout();
+
+ initializeZoom(characteristics, characteristicsIndex);
mActivity.setPreviewGestures(mGestures);
}
@@ -355,31 +360,32 @@ public class CaptureUI implements PieListener,
mShutterButton.setVisibility(View.VISIBLE);
}
- public void doShutterAnimation() {
- AnimationDrawable frameAnimation = (AnimationDrawable) mShutterButton.getDrawable();
- frameAnimation.stop();
- frameAnimation.start();
- }
-
// called from onResume every other time
- public void initializeSecondTime(Camera.Parameters params) {
- initializeZoom(params);
+ public void initializeSecondTime() {
if (mMenu != null) {
mMenu.reloadPreferences();
}
}
- public void initializeZoom(Camera.Parameters params) {
- if ((params == null) || !params.isZoomSupported()
- || (mZoomRenderer == null)) return;
- mZoomMax = params.getMaxZoom();
- mZoomRatios = params.getZoomRatios();
- // Currently we use immediate zoom for fast zooming to get better UX and
- // there is no plan to take advantage of the smooth zoom.
+ public void doShutterAnimation() {
+ AnimationDrawable frameAnimation = (AnimationDrawable) mShutterButton.getDrawable();
+ frameAnimation.stop();
+ frameAnimation.start();
+ }
+
+ public void initializeZoom(CameraCharacteristics[] characteristics,
+ List<Integer> characteristicsIndex) {
+ if ((characteristics == null) || !CameraUtil.isZoomSupported(characteristics,
+ characteristicsIndex) || (mZoomRenderer == null))
+ return;
if (mZoomRenderer != null) {
- mZoomRenderer.setZoomMax(mZoomMax);
- mZoomRenderer.setZoom(params.getZoom());
- mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
+ float zoomMax = Float.MAX_VALUE;
+ for (int i = 0; i < characteristicsIndex.size(); i++) {
+ zoomMax = Math.min(characteristics[characteristicsIndex.get(i)].get
+ (CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM), zoomMax);
+ }
+ mZoomRenderer.setZoomMax(zoomMax);
+ mZoomRenderer.setZoom(1f);
mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
}
}
@@ -671,6 +677,14 @@ public class CaptureUI implements PieListener,
}
}
+ public void hideSurfaceView() {
+ mSurfaceView.setVisibility(View.INVISIBLE);
+ }
+
+ public void showSurfaceView() {
+ mSurfaceView.setVisibility(View.VISIBLE);
+ }
+
public void onPause() {
// Clear UI.
collapseCameraControls();
@@ -678,7 +692,7 @@ public class CaptureUI implements PieListener,
// focus UI implementation
private FocusIndicator getFocusIndicator() {
- return null;
+ return mPieRenderer;
}
@Override
@@ -689,6 +703,10 @@ public class CaptureUI implements PieListener,
public void clearFaces() {
}
+ public void setPreference(String key, String value) {
+ mMenu.setPreference(key, value);
+ }
+
@Override
public void clearFocus() {
FocusIndicator indicator = getFocusIndicator();
@@ -768,16 +786,28 @@ public class CaptureUI implements PieListener,
}
}
+ public Point getSurfaceViewSize() {
+ Point point = new Point();
+ if (mSurfaceView != null) point.set(mSurfaceView.getWidth(), mSurfaceView.getHeight());
+ return point;
+ }
+
+ public Point getSurfaceView2Size() {
+ Point point = new Point();
+ if (mSurfaceView2 != null) point.set(mSurfaceView2.getWidth(), mSurfaceView2.getHeight());
+ return point;
+ }
+
public int getOrientation() {
return mOrientation;
}
private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
@Override
- public void onZoomValueChanged(int index) {
- int newZoom = mController.onZoomChanged(index);
+ public void onZoomValueChanged(float mZoomValue) {
+ mController.onZoomChanged(mZoomValue);
if (mZoomRenderer != null) {
- mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
+ mZoomRenderer.setZoom(mZoomValue);
}
}
@@ -795,6 +825,11 @@ public class CaptureUI implements PieListener,
mPieRenderer.setBlockFocus(false);
}
}
+
+ @Override
+ public void onZoomValueChanged(int index) {
+
+ }
}
}
diff --git a/src/com/android/camera/FocusStateListener.java b/src/com/android/camera/FocusStateListener.java
new file mode 100644
index 000000000..42edd5c34
--- /dev/null
+++ b/src/com/android/camera/FocusStateListener.java
@@ -0,0 +1,66 @@
+/*
+ * 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.hardware.camera2.CaptureResult;
+
+public class FocusStateListener {
+ private CaptureUI mUI;
+
+ public FocusStateListener(CaptureUI ui) {
+ mUI = ui;
+ }
+
+ public void onFocusStatusUpdate(int focusState) {
+ switch (focusState) {
+ case CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN:
+ mUI.onFocusStarted();
+ break;
+ case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
+ mUI.onFocusSucceeded(true);
+ break;
+ case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
+ mUI.onFocusFailed(true);
+ break;
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
+ mUI.onFocusSucceeded(true);
+ break;
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
+ mUI.onFocusStarted();
+ break;
+ case CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED:
+ mUI.onFocusFailed(true);
+ break;
+ case CaptureResult.CONTROL_AF_STATE_INACTIVE:
+ mUI.clearFocus();
+ break;
+ }
+ }
+}
diff --git a/src/com/android/camera/MediaSaveService.java b/src/com/android/camera/MediaSaveService.java
index 46abe0560..8570b16fd 100644
--- a/src/com/android/camera/MediaSaveService.java
+++ b/src/com/android/camera/MediaSaveService.java
@@ -16,11 +16,18 @@
package com.android.camera;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.nio.ByteOrder;
+
import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.hardware.camera2.TotalCaptureResult;
import android.location.Location;
import android.net.Uri;
import android.os.AsyncTask;
@@ -32,6 +39,10 @@ import com.android.camera.PhotoModule;
import com.android.camera.exif.ExifInterface;
import java.io.File;
+import com.android.camera.mpo.MpoData;
+import com.android.camera.mpo.MpoImageData;
+import com.android.camera.mpo.MpoInterface;
+import com.android.camera.util.ClearSightNativeEngine.ClearsightImage;
/*
* Service for saving images in the background thread.
@@ -87,6 +98,31 @@ public class MediaSaveService extends Service {
return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
}
+ public void addMpoImage(final ClearsightImage csImage,
+ final YuvImage bayerImg, final YuvImage monoImg,
+ TotalCaptureResult bayerResult, TotalCaptureResult monoResult,
+ String title, long date, Location loc, int orientation,
+ OnMediaSavedListener l, ContentResolver resolver,
+ String pictureFormat) {
+ if (isQueueFull()) {
+ Log.e(TAG, "Cannot add image when the queue is full");
+ return;
+ }
+
+ MpoSaveTask t = new MpoSaveTask(csImage, bayerImg, monoImg,
+ bayerResult, monoResult, title, date, loc, orientation, l,
+ resolver, pictureFormat);
+
+ long size = (csImage == null ? 0
+ : csImage.getDataLength())
+ + bayerImg.getYuvData().length + monoImg.getYuvData().length;
+ mMemoryUse += size;
+ if (isQueueFull()) {
+ onQueueFull();
+ }
+ t.execute();
+ }
+
public void addImage(final byte[] data, String title, long date, Location loc,
int width, int height, int orientation, ExifInterface exif,
OnMediaSavedListener l, ContentResolver resolver, String pictureFormat) {
@@ -141,6 +177,97 @@ public class MediaSaveService extends Service {
if (mListener != null) mListener.onQueueStatus(false);
}
+ private class MpoSaveTask extends AsyncTask<Void, Void, Uri> {
+ private ClearsightImage csImage;
+ private YuvImage bayerImage;
+ private YuvImage monoImage;
+ private String title;
+ private long date;
+ private Location loc;
+ private int width, height;
+ private int orientation;
+ private TotalCaptureResult bayerResult;
+ private TotalCaptureResult monoResult;
+ private ContentResolver resolver;
+ private OnMediaSavedListener listener;
+ private String pictureFormat;
+
+ public MpoSaveTask(ClearsightImage csImage, YuvImage bayerImg,
+ YuvImage monoImg, TotalCaptureResult bayerResult,
+ TotalCaptureResult monoResult, String title, long date,
+ Location loc, int orientation, OnMediaSavedListener listener,
+ ContentResolver resolver, String pictureFormat) {
+ this.csImage = csImage;
+ this.bayerImage = bayerImg;
+ this.monoImage = monoImg;
+ this.title = title;
+ this.date = date;
+ this.loc = loc;
+ this.width = bayerImg.getWidth();
+ this.height = bayerImg.getHeight();
+ this.orientation = orientation;
+ this.bayerResult = bayerResult;
+ this.monoResult = monoResult;
+ this.resolver = resolver;
+ this.listener = listener;
+ this.pictureFormat = pictureFormat;
+ }
+
+ @Override
+ protected Uri doInBackground(Void... v) {
+ // encode jpeg and add exif for all images
+ MpoData mpo = new MpoData();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ bayerImage.compressToJpeg(new Rect(0, 0, bayerImage.getWidth(),
+ bayerImage.getHeight()), 100, baos);
+ MpoImageData bayer = new MpoImageData(baos.toByteArray(),
+ ByteOrder.BIG_ENDIAN);
+
+ baos.reset();
+ monoImage.compressToJpeg(new Rect(0, 0, monoImage.getWidth(),
+ monoImage.getHeight()), 100, baos);
+ MpoImageData mono = new MpoImageData(baos.toByteArray(),
+ ByteOrder.BIG_ENDIAN);
+
+ if (csImage == null) {
+ mpo.addAuxiliaryMpoImage(mono);
+ mpo.setPrimaryMpoImage(bayer);
+ } else {
+ MpoImageData cs = new MpoImageData(csImage.compressToJpeg(),
+ ByteOrder.BIG_ENDIAN);
+
+ mpo.addAuxiliaryMpoImage(bayer);
+ mpo.addAuxiliaryMpoImage(mono);
+ mpo.setPrimaryMpoImage(cs);
+ }
+
+ // combine to single mpo
+ String path = Storage.generateFilepath(title, pictureFormat);
+ int size = MpoInterface.writeMpo(mpo, path);
+ // Try to get the real image size after add exif.
+ File f = new File(path);
+ if (f.exists() && f.isFile()) {
+ size = (int) f.length();
+ }
+ return Storage.addImage(resolver, title, date, loc, orientation,
+ size, path, width, height, pictureFormat);
+ }
+
+ @Override
+ protected void onPostExecute(Uri uri) {
+ if (listener != null)
+ listener.onMediaSaved(uri);
+ boolean previouslyFull = isQueueFull();
+ long size = (csImage == null ? 0
+ : csImage.getDataLength())
+ + bayerImage.getYuvData().length
+ + monoImage.getYuvData().length;
+ mMemoryUse -= size;
+ if (isQueueFull() != previouslyFull)
+ onQueueAvailable();
+ }
+ }
+
private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
private byte[] data;
private String title;
diff --git a/src/com/android/camera/PhotoController.java b/src/com/android/camera/PhotoController.java
index b08f8c088..887f0d375 100644
--- a/src/com/android/camera/PhotoController.java
+++ b/src/com/android/camera/PhotoController.java
@@ -37,6 +37,8 @@ public interface PhotoController extends OnShutterButtonListener {
// returns the actual set zoom value
public int onZoomChanged(int requestedZoom);
+ public void onZoomChanged(float requestedZoom);
+
public boolean isImageCaptureIntent();
public boolean isCameraIdle();
diff --git a/src/com/android/camera/PhotoMenu.java b/src/com/android/camera/PhotoMenu.java
index b3c6117fa..b9ecdcc8d 100644
--- a/src/com/android/camera/PhotoMenu.java
+++ b/src/com/android/camera/PhotoMenu.java
@@ -16,6 +16,7 @@
package com.android.camera;
+import java.util.HashSet;
import java.util.Locale;
import android.animation.Animator;
@@ -117,6 +118,7 @@ public class PhotoMenu extends MenuController
private MakeupLevelListener mMakeupListener;
private MakeupHandler mHandler = new MakeupHandler();
private static final int MAKEUP_MESSAGE_ID = 0;
+ private HashSet<View> mWasVisibleSet = new HashSet<View>();
public PhotoMenu(CameraActivity activity, PhotoUI ui, MakeupLevelListener makeupListener) {
super(activity);
@@ -1509,7 +1511,17 @@ public class PhotoMenu extends MenuController
}
mSceneModeSwitcher.setVisibility(status);
mFilterModeSwitcher.setVisibility(status);
- mCameraSwitcher.setVisibility(status);
+ if(status == View.INVISIBLE) {
+ if(mCameraSwitcher.getVisibility() == View.VISIBLE) {
+ mWasVisibleSet.add(mCameraSwitcher);
+ }
+ mCameraSwitcher.setVisibility(status);
+ } else {
+ if(mWasVisibleSet.contains(mCameraSwitcher)) {
+ mCameraSwitcher.setVisibility(status);
+ mWasVisibleSet.remove(mCameraSwitcher);
+ }
+ }
mPreviewThumbnail.setVisibility(status);
}
}
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 589f55152..4d2ec6ad8 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -696,6 +696,7 @@ public class PhotoModule
private void switchCamera() {
if (mPaused) return;
+ mUI.applySurfaceChange(PhotoUI.SURFACE_STATUS.HIDE);
Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
mCameraId = mPendingSwitchCameraId;
mPendingSwitchCameraId = -1;
@@ -735,6 +736,7 @@ public class PhotoModule
mFocusManager.setParameters(mInitialParams);
setupPreview();
+ mUI.applySurfaceChange(PhotoUI.SURFACE_STATUS.SURFACE_VIEW);
// reset zoom value index
mZoomValue = 0;
resizeForPreviewAspectRatio();
@@ -1126,23 +1128,26 @@ public class PhotoModule
for (int i =0;i<3;i++) {
metadata[i] = byteToInt( (byte []) data, i*4);
}
- if (metadata[2] == 1) {
- mAutoHdrEnable = true;
- mActivity.runOnUiThread(new Runnable() {
- public void run() {
- if (mDrawAutoHDR != null)
- mDrawAutoHDR.AutoHDR();
- }
- });
- }
- else {
- mAutoHdrEnable = false;
- mActivity.runOnUiThread(new Runnable() {
- public void run() {
- if (mDrawAutoHDR != null)
- mDrawAutoHDR.AutoHDR();
- }
- });
+ /* Checking if the meta data is for auto HDR */
+ if (metadata[0] == 3) {
+ if (metadata[2] == 1) {
+ mAutoHdrEnable = true;
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ if (mDrawAutoHDR != null)
+ mDrawAutoHDR.AutoHDR();
+ }
+ });
+ }
+ else {
+ mAutoHdrEnable = false;
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ if (mDrawAutoHDR != null)
+ mDrawAutoHDR.AutoHDR();
+ }
+ });
+ }
}
}
}
@@ -2364,6 +2369,7 @@ public class PhotoModule
@Override
public void onResumeAfterSuper() {
mLastPhotoTakenWithRefocus = false;
+ mUI.showSurfaceView();
// Add delay on resume from lock screen only, in order to to speed up
// the onResume --> onPause --> onResume cycle from lock screen.
// Don't do always because letting go of thread can cause delay.
@@ -2372,17 +2378,17 @@ public class PhotoModule
|| MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
Log.v(TAG, "On resume, from lock screen.");
+ // Check if there is a need to take a snapshot without
+ // waiting for the shutter click
+ if (isInstantCaptureEnabled()) {
+ mInstantCaptureSnapShot = true;
+ }
+
// Note: onPauseAfterSuper() will delete this runnable, so we will
// at most have 1 copy queued up.
mHandler.postDelayed(new Runnable() {
public void run() {
onResumeTasks();
-
- // Check if there is a need to take a snapshot without
- // waiting for the shutter click
- if (isInstantCaptureEnabled()) {
- mInstantCaptureSnapShot = true;
- }
}
}, ON_RESUME_TASKS_DELAY_MSEC);
} else {
@@ -2425,6 +2431,8 @@ public class PhotoModule
mOpenCameraThread.start();
}
+ mUI.applySurfaceChange(PhotoUI.SURFACE_STATUS.SURFACE_VIEW);
+
mJpegPictureCallbackTime = 0;
mZoomValue = 0;
@@ -2463,6 +2471,8 @@ public class PhotoModule
@Override
public void onPauseBeforeSuper() {
mPaused = true;
+ mUI.applySurfaceChange(PhotoUI.SURFACE_STATUS.HIDE);
+
Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (gsensor != null) {
mSensorManager.unregisterListener(this, gsensor);
@@ -2491,6 +2501,7 @@ public class PhotoModule
public void onPauseAfterSuper() {
Log.v(TAG, "On pause.");
mUI.showPreviewCover();
+ mUI.hideSurfaceView();
try {
if (mOpenCameraThread != null) {
@@ -2564,12 +2575,6 @@ public class PhotoModule
// we will update focus manager with proper UI.
if (mFocusManager != null && mUI != null) {
mFocusManager.setPhotoUI(mUI);
-
- View root = mUI.getRootView();
- // These depend on camera parameters.
- int width = root.getWidth();
- int height = root.getHeight();
- mFocusManager.setPreviewSize(width, height);
}
}
@@ -4657,6 +4662,11 @@ public class PhotoModule
}
@Override
+ public void onZoomChanged(float requestedZoom) {
+
+ }
+
+ @Override
public int getCameraState() {
return mCameraState;
}
@@ -4976,7 +4986,7 @@ class DrawAutoHDR extends View{
AutoHDRPaint.setStyle(Paint.Style.STROKE);
AutoHDRPaint.setColor(Color.MAGENTA);
AutoHDRPaint.setStrokeWidth(1);
- AutoHDRPaint.setTextSize(16);
+ AutoHDRPaint.setTextSize(32);
AutoHDRPaint.setAlpha (255);
canvas.drawText("HDR On",200,100,AutoHDRPaint);
}
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index 7e3659f0f..c8e01fea0 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -160,26 +160,15 @@ public class PhotoUI implements PieListener,
private int mOrientation;
private float mScreenBrightness = 0.0f;
+ public enum SURFACE_STATUS {
+ HIDE,
+ SURFACE_VIEW;
+ }
+
public interface SurfaceTextureSizeChangedListener {
public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight);
}
- 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) {
- tryToCloseSubList();
-
- Camera.Parameters parameters = ((PhotoModule)mController).getParameters();
- if(parameters != null) {
- Camera.Size size = parameters.getPreviewSize();
- if (size != null) {
- setAspectRatio((float) size.width / size.height);
- }
- }
- }
- };
-
public CameraControls getCameraControls() {
return mCameraControls;
}
@@ -233,6 +222,14 @@ public class PhotoUI implements PieListener,
}
}
+ public synchronized void applySurfaceChange(SURFACE_STATUS status) {
+ if(status == SURFACE_STATUS.HIDE) {
+ mSurfaceView.setVisibility(View.GONE);
+ return;
+ }
+ mSurfaceView.setVisibility(View.VISIBLE);
+ }
+
public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
mActivity = activity;
mController = controller;
@@ -246,11 +243,8 @@ public class PhotoUI implements PieListener,
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- mSurfaceView.addOnLayoutChangeListener(mLayoutListener);
Log.v(TAG, "Using mdp_preview_content (MDP path)");
-
- View surfaceContainer = mRootView.findViewById(R.id.preview_container);
- surfaceContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ mSurfaceView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight,
@@ -258,26 +252,13 @@ public class PhotoUI implements PieListener,
int width = right - left;
int height = bottom - top;
+ tryToCloseSubList();
+
if (mMaxPreviewWidth == 0 && mMaxPreviewHeight == 0) {
mMaxPreviewWidth = width;
mMaxPreviewHeight = height;
}
- int orientation = mActivity.getResources().getConfiguration().orientation;
- if ((orientation == Configuration.ORIENTATION_PORTRAIT && width > height)
- || (orientation == Configuration.ORIENTATION_LANDSCAPE && width < height)) {
- // The screen has rotated; swap SurfaceView width & height
- // to ensure correct preview
- int oldWidth = width;
- width = height;
- height = oldWidth;
- Log.d(TAG, "Swapping SurfaceView width & height dimensions");
- if (mMaxPreviewWidth != 0 && mMaxPreviewHeight != 0) {
- int temp = mMaxPreviewWidth;
- mMaxPreviewWidth = mMaxPreviewHeight;
- mMaxPreviewHeight = temp;
- }
- }
if (mOrientationResize != mPrevOrientationResize
|| mAspectRatioResize) {
layoutPreview(mAspectRatio);
@@ -478,9 +459,6 @@ public class PhotoUI implements PieListener,
if (mFaceView != null) {
mFaceView.setLayoutParams(lp);
}
-
- mController.onScreenSizeChanged((int) mSurfaceTextureUncroppedWidth,
- (int) mSurfaceTextureUncroppedHeight);
}
public void setSurfaceTextureSizeChangedListener(SurfaceTextureSizeChangedListener listener) {
@@ -597,6 +575,7 @@ public class PhotoUI implements PieListener,
});
if (mController.isImageCaptureIntent()) {
hideSwitcher();
+ mCameraControls.hideRemainingPhotoCnt();
mSwitcher.setSwitcherVisibility(false);
ViewGroup cameraControls = (ViewGroup) mRootView.findViewById(R.id.camera_controls);
mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
@@ -1148,6 +1127,11 @@ public class PhotoUI implements PieListener,
mPieRenderer.setBlockFocus(false);
}
}
+
+ @Override
+ public void onZoomValueChanged(float value) {
+
+ }
}
@Override
@@ -1176,6 +1160,13 @@ public class PhotoUI implements PieListener,
return mSurfaceHolder;
}
+ public void hideSurfaceView() {
+ mSurfaceView.setVisibility(View.INVISIBLE);
+ }
+
+ public void showSurfaceView() {
+ mSurfaceView.setVisibility(View.VISIBLE);
+ }
// Countdown timer
private void initializeCountDown() {
diff --git a/src/com/android/camera/PreviewGestures.java b/src/com/android/camera/PreviewGestures.java
index 00f7d02b5..4f26240ea 100644
--- a/src/com/android/camera/PreviewGestures.java
+++ b/src/com/android/camera/PreviewGestures.java
@@ -107,8 +107,9 @@ public class PreviewGestures
else if (mCaptureMenu != null && !mCaptureMenu.isMenuBeingShown())
mCaptureMenu.openFirstLevel();
return true;
+ } else {
+ return onSingleTapUp(e2);
}
- return false;
}
private boolean isLeftSwipe(int orientation, int deltaX, int deltaY) {
diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java
index 302dd47f0..670572774 100644
--- a/src/com/android/camera/VideoMenu.java
+++ b/src/com/android/camera/VideoMenu.java
@@ -729,8 +729,7 @@ public class VideoMenu extends MenuController
mPrevSavedVideoCDS = cds;
}
- if ((tnr != null) && !mActivity.getString(R.string.
- pref_camera_video_tnr_default).equals(tnr)) {
+ if ((tnr != null) && !tnr.equals("off")) {
mListMenu.setPreferenceEnabled(
CameraSettings.KEY_VIDEO_CDS_MODE,false);
mListMenu.overrideSettings(
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 4724bd203..b2175d7b2 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -315,7 +315,7 @@ public class VideoModule implements CameraModule,
VIDEO_ENCODER_TABLE.put("h263", MediaRecorder.VideoEncoder.H263);
VIDEO_ENCODER_TABLE.put("h264", MediaRecorder.VideoEncoder.H264);
- VIDEO_ENCODER_TABLE.put("h265", MediaRecorder.VideoEncoder.H265);
+ // VIDEO_ENCODER_TABLE.put("h265", MediaRecorder.VideoEncoder.H265);
VIDEO_ENCODER_TABLE.put("m4v", MediaRecorder.VideoEncoder.MPEG_4_SP);
VIDEO_ENCODER_TABLE.putDefault(MediaRecorder.VideoEncoder.DEFAULT);
@@ -903,7 +903,8 @@ public class VideoModule implements CameraModule,
public boolean is4KEnabled() {
if (mProfile.quality == CamcorderProfile.QUALITY_2160P ||
- mProfile.quality == CamcorderProfile.QUALITY_4KDCI) {
+ mProfile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_2160P ||
+ mProfile.quality == CamcorderProfile.QUALITY_4KDCI ) {
return true;
} else {
return false;
@@ -1094,6 +1095,8 @@ public class VideoModule implements CameraModule,
mUI.enableShutter(true);
}
+ mUI.applySurfaceChange(VideoUI.SURFACE_STATUS.SURFACE_VIEW);
+
mUI.initDisplayChangeListener();
// Initializing it here after the preview is started.
mUI.initializeZoom(mParameters);
@@ -1279,6 +1282,7 @@ public class VideoModule implements CameraModule,
if(mWasMute != mIsMute) {
setMute(mWasMute, false);
}
+ mUI.applySurfaceChange(VideoUI.SURFACE_STATUS.HIDE);
}
@Override
@@ -1657,12 +1661,13 @@ public class VideoModule implements CameraModule,
long duration = 0L;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- retriever.setDataSource(mCurrentVideoFilename);
+
try {
+ retriever.setDataSource(mCurrentVideoFilename);
duration = Long.valueOf(retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_DURATION));
- } catch (NumberFormatException e) {
- Log.e(TAG, "cannot retrieve duration metadata");
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "cannot access the file");
}
retriever.release();
@@ -2716,6 +2721,7 @@ public class VideoModule implements CameraModule,
}
Log.d(TAG, "Start to switch camera.");
+ mUI.applySurfaceChange(VideoUI.SURFACE_STATUS.HIDE);
mCameraId = mPendingSwitchCameraId;
mPendingSwitchCameraId = -1;
setCameraId(mCameraId);
@@ -2727,6 +2733,7 @@ public class VideoModule implements CameraModule,
CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
openCamera();
readVideoPreferences();
+ mUI.applySurfaceChange(VideoUI.SURFACE_STATUS.SURFACE_VIEW);
startPreview();
initializeVideoSnapshot();
resizeForPreviewAspectRatio();
diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java
index 9a882e946..c99c8c8f5 100644
--- a/src/com/android/camera/VideoUI.java
+++ b/src/com/android/camera/VideoUI.java
@@ -130,13 +130,10 @@ public class VideoUI implements PieRenderer.PieListener,
private float mSurfaceTextureUncroppedWidth;
private float mSurfaceTextureUncroppedHeight;
- 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) {
- tryToCloseSubList();
- }
- };
+ public enum SURFACE_STATUS {
+ HIDE,
+ SURFACE_VIEW;
+ }
public void showPreviewCover() {
mPreviewCover.setVisibility(View.VISIBLE);
@@ -177,6 +174,14 @@ public class VideoUI implements PieRenderer.PieListener,
}
}
+ public synchronized void applySurfaceChange(SURFACE_STATUS status) {
+ if(status == SURFACE_STATUS.HIDE) {
+ mSurfaceView.setVisibility(View.GONE);
+ return;
+ }
+ mSurfaceView.setVisibility(View.VISIBLE);
+ }
+
public VideoUI(CameraActivity activity, VideoController controller, View parent) {
mActivity = activity;
mController = controller;
@@ -190,9 +195,7 @@ public class VideoUI implements PieRenderer.PieListener,
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- mSurfaceView.addOnLayoutChangeListener(mLayoutListener);
Log.v(TAG, "Using mdp_preview_content (MDP path)");
-
View surfaceContainer = mRootView.findViewById(R.id.preview_container);
surfaceContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
@@ -202,6 +205,7 @@ public class VideoUI implements PieRenderer.PieListener,
int width = right - left;
int height = bottom - top;
+ tryToCloseSubList();
if (mMaxPreviewWidth == 0 && mMaxPreviewHeight == 0) {
mMaxPreviewWidth = width;
mMaxPreviewHeight = height;
@@ -1108,14 +1112,17 @@ public class VideoUI implements PieRenderer.PieListener,
mPieRenderer.setBlockFocus(false);
}
}
+
+ @Override
+ public void onZoomValueChanged(float value) {
+
+ }
}
// SurfaceHolder callbacks
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.v(TAG, "surfaceChanged: width = " + width + ", height = " + height);
- // Make sure preview cover is hidden if preview data is available.
- hidePreviewCover();
}
@Override
diff --git a/src/com/android/camera/exif/ExifTag.java b/src/com/android/camera/exif/ExifTag.java
index 1d50316dd..fcf0f5c29 100644
--- a/src/com/android/camera/exif/ExifTag.java
+++ b/src/com/android/camera/exif/ExifTag.java
@@ -85,7 +85,7 @@ public class ExifTag {
TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
}
- static final int SIZE_UNDEFINED = 0;
+ public static final int SIZE_UNDEFINED = 0;
// Exif TagId
private final short mTagId;
@@ -124,7 +124,7 @@ public class ExifTag {
}
// Use builtTag in ExifInterface instead of constructor.
- ExifTag(short tagId, short type, int componentCount, int ifd,
+ public ExifTag(short tagId, short type, int componentCount, int ifd,
boolean hasDefinedComponentCount) {
mTagId = tagId;
mDataType = type;
@@ -163,7 +163,7 @@ public class ExifTag {
return mIfd;
}
- protected void setIfd(int ifdId) {
+ public void setIfd(int ifdId) {
mIfd = ifdId;
}
@@ -785,7 +785,7 @@ public class ExifTag {
* @exception IllegalArgumentException if the data type is
* {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
*/
- protected long getValueAt(int index) {
+ public long getValueAt(int index) {
if (mValue instanceof long[]) {
return ((long[]) mValue)[index];
} else if (mValue instanceof byte[]) {
@@ -812,7 +812,7 @@ public class ExifTag {
/*
* Get the converted ascii byte. Used by ExifOutputStream.
*/
- protected byte[] getStringByte() {
+ public byte[] getStringByte() {
return (byte[]) mValue;
}
@@ -822,7 +822,7 @@ public class ExifTag {
* @exception IllegalArgumentException If the type is NOT
* {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
*/
- protected Rational getRational(int index) {
+ public Rational getRational(int index) {
if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
throw new IllegalArgumentException("Cannot get RATIONAL value from "
+ convertTypeToString(mDataType));
@@ -833,7 +833,7 @@ public class ExifTag {
/**
* Equivalent to getBytes(buffer, 0, buffer.length).
*/
- protected void getBytes(byte[] buf) {
+ public void getBytes(byte[] buf) {
getBytes(buf, 0, buf.length);
}
@@ -847,7 +847,7 @@ public class ExifTag {
* @exception IllegalArgumentException If the type is NOT
* {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
*/
- protected void getBytes(byte[] buf, int offset, int length) {
+ public void getBytes(byte[] buf, int offset, int length) {
if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
throw new IllegalArgumentException("Cannot get BYTE value from "
+ convertTypeToString(mDataType));
@@ -860,14 +860,14 @@ public class ExifTag {
* Gets the offset of this tag. This is only valid if this data size > 4 and
* contains an offset to the location of the actual value.
*/
- protected int getOffset() {
+ public int getOffset() {
return mOffset;
}
/**
* Sets the offset of this tag.
*/
- protected void setOffset(int offset) {
+ public void setOffset(int offset) {
mOffset = offset;
}
diff --git a/src/com/android/camera/exif/JpegHeader.java b/src/com/android/camera/exif/JpegHeader.java
index 383617af4..6fc46ca18 100644
--- a/src/com/android/camera/exif/JpegHeader.java
+++ b/src/com/android/camera/exif/JpegHeader.java
@@ -16,8 +16,9 @@
package com.android.camera.exif;
-class JpegHeader {
+public class JpegHeader {
public static final short SOI = (short) 0xFFD8;
+ public static final short APP2 = (short) 0xFFE2;
public static final short APP1 = (short) 0xFFE1;
public static final short APP0 = (short) 0xFFE0;
public static final short EOI = (short) 0xFFD9;
diff --git a/src/com/android/camera/exif/OrderedDataOutputStream.java b/src/com/android/camera/exif/OrderedDataOutputStream.java
index abc0a6eb1..3206d72b0 100644
--- a/src/com/android/camera/exif/OrderedDataOutputStream.java
+++ b/src/com/android/camera/exif/OrderedDataOutputStream.java
@@ -22,7 +22,7 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-class OrderedDataOutputStream extends FilterOutputStream {
+public class OrderedDataOutputStream extends FilterOutputStream {
private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
private int mSize = 0;
diff --git a/src/com/android/camera/mpo/MpoData.java b/src/com/android/camera/mpo/MpoData.java
new file mode 100644
index 000000000..7cd431ebe
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoData.java
@@ -0,0 +1,184 @@
+/*
+ * 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.mpo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.camera.mpo.MpoTag.MpEntry;
+
+public class MpoData {
+
+ private MpoImageData mPrimaryMpoImage;
+ private ArrayList<MpoImageData> mAuxiliaryImages = new ArrayList<MpoImageData>();
+
+ public MpoData() {
+ }
+
+ public void setPrimaryMpoImage(MpoImageData image) {
+ mPrimaryMpoImage = image;
+ addDefaultAttribIfdTags(mPrimaryMpoImage, 1);
+ addDefaultIndexIfdTags();
+ }
+
+ public void addAuxiliaryMpoImage(MpoImageData image) {
+ mAuxiliaryImages.add(image);
+ int imageNum = getAuxiliaryImageCount() + ((mPrimaryMpoImage == null) ? 0 : 1);
+ addDefaultAttribIfdTags(image, imageNum);
+ }
+
+ public boolean removeAuxiliaryMpoImage(MpoImageData image) {
+ boolean ret = mAuxiliaryImages.remove(image);
+ return ret;
+ }
+
+ public MpoImageData getPrimaryMpoImage() {
+ return mPrimaryMpoImage;
+ }
+
+ public List<MpoImageData> getAuxiliaryMpoImages() {
+ return mAuxiliaryImages;
+ }
+
+ public int getAuxiliaryImageCount() {
+ return mAuxiliaryImages.size();
+ }
+
+ public void addDefaultAttribIfdTags(MpoImageData image, int imageNum) {
+ MpoTag mpFormatVersionTag = new MpoTag((short) MpoInterface.TAG_MP_FORMAT_VERSION,
+ MpoTag.TYPE_UNDEFINED, 4, MpoIfdData.TYPE_MP_ATTRIB_IFD, true);
+ mpFormatVersionTag.setValue(MpoIfdData.MP_FORMAT_VER_VALUE);
+ image.addTag(mpFormatVersionTag);
+
+ MpoTag imageNumTag = new MpoTag((short) MpoInterface.TAG_IMAGE_NUMBER,
+ MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_ATTRIB_IFD, false);
+ imageNumTag.setValue(imageNum);
+ image.addTag(imageNumTag);
+ }
+
+ public void addDefaultIndexIfdTags() {
+ if (mPrimaryMpoImage == null)
+ throw new IllegalArgumentException("Primary Mpo Image has not been set");
+ if (getAuxiliaryImageCount() == 0)
+ throw new IllegalArgumentException("No auxiliary images have been added");
+
+ MpoTag mpFormatVersionTag = mPrimaryMpoImage.getTag(
+ (short) MpoInterface.TAG_MP_FORMAT_VERSION, MpoIfdData.TYPE_MP_INDEX_IFD);
+ if (mpFormatVersionTag == null) {
+ mpFormatVersionTag = new MpoTag((short) MpoInterface.TAG_MP_FORMAT_VERSION,
+ MpoTag.TYPE_UNDEFINED, 4, MpoIfdData.TYPE_MP_INDEX_IFD, true);
+ mpFormatVersionTag.setValue(MpoIfdData.MP_FORMAT_VER_VALUE);
+ mPrimaryMpoImage.addTag(mpFormatVersionTag);
+ }
+
+ MpoTag numImagesTag = mPrimaryMpoImage.getTag((short) MpoInterface.TAG_NUM_IMAGES,
+ MpoIfdData.TYPE_MP_INDEX_IFD);
+ if (numImagesTag == null) {
+ numImagesTag = new MpoTag((short) MpoInterface.TAG_NUM_IMAGES,
+ MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_INDEX_IFD, false);
+ }
+ numImagesTag.setValue(getAuxiliaryImageCount() + 1);
+ mPrimaryMpoImage.addTag(numImagesTag);
+
+ // check, create and add required tags
+ MpoTag mpEntryTag = new MpoTag((short) MpoInterface.TAG_MP_ENTRY, MpoTag.TYPE_UNDEFINED,
+ MpoTag.SIZE_UNDEFINED, MpoIfdData.TYPE_MP_INDEX_IFD, false);
+ ArrayList<MpEntry> mpEntries = new ArrayList<MpEntry>(getAuxiliaryImageCount() + 1);
+ mpEntries.add(new MpEntry()); // primary image
+ for (int i = 0; i < getAuxiliaryImageCount(); i++) {
+ mpEntries.add(new MpEntry()); // aux images
+ }
+ mpEntryTag.setValue(mpEntries);
+ mPrimaryMpoImage.addTag(mpEntryTag);
+ }
+
+ public void updateAllTags() {
+ updateAttribIfdTags();
+ updateIndexIfdTags();
+ }
+
+ private void updateIndexIfdTags() {
+ if (mPrimaryMpoImage == null)
+ throw new IllegalArgumentException("Primary Mpo Image has not been set");
+ if (getAuxiliaryImageCount() == 0)
+ throw new IllegalArgumentException("No auxiliary images have been added");
+
+ MpoTag numImagesTag = mPrimaryMpoImage.getTag((short) MpoInterface.TAG_NUM_IMAGES,
+ MpoIfdData.TYPE_MP_INDEX_IFD);
+ if (numImagesTag == null) {
+ numImagesTag = new MpoTag((short) MpoInterface.TAG_NUM_IMAGES,
+ MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_INDEX_IFD, false);
+ }
+ numImagesTag.setValue(getAuxiliaryImageCount() + 1);
+ mPrimaryMpoImage.addTag(numImagesTag);
+
+ // check, create and add required tags
+ MpoTag mpEntryTag = new MpoTag((short) MpoInterface.TAG_MP_ENTRY, MpoTag.TYPE_UNDEFINED,
+ MpoTag.SIZE_UNDEFINED, MpoIfdData.TYPE_MP_INDEX_IFD, false);
+ ArrayList<MpEntry> mpEntries = new ArrayList<MpEntry>(getAuxiliaryImageCount() + 1);
+
+ int imgOffset = 0;
+ // primary image
+ MpEntry entry = new MpEntry(1 << 29, mPrimaryMpoImage.calculateImageSize(), imgOffset);
+ mpEntries.add(entry);
+ imgOffset += mPrimaryMpoImage.calculateImageSize();
+
+ for (MpoImageData image : getAuxiliaryMpoImages()) {
+ int imageSize = image.calculateImageSize();
+ entry = new MpEntry(0x020002, imageSize, imgOffset);
+ mpEntries.add(entry); // aux images
+ imgOffset += imageSize;
+ }
+ mpEntryTag.setValue(mpEntries);
+ mPrimaryMpoImage.addTag(mpEntryTag);
+ }
+
+ private void updateAttribIfdTags() {
+ if (mPrimaryMpoImage == null)
+ throw new IllegalArgumentException("Primary Mpo Image has not been set");
+ if (getAuxiliaryImageCount() == 0)
+ throw new IllegalArgumentException("No auxiliary images have been added");
+
+ int imageNum = 1;
+ MpoTag imageNumTag = null;
+
+ imageNumTag = new MpoTag((short) MpoInterface.TAG_IMAGE_NUMBER, MpoTag.TYPE_UNSIGNED_LONG,
+ 1, MpoIfdData.TYPE_MP_ATTRIB_IFD, false);
+ imageNumTag.setValue(0xFFFFFFFFL);
+ mPrimaryMpoImage.addTag(imageNumTag);
+
+ for (MpoImageData image : getAuxiliaryMpoImages()) {
+ imageNumTag = new MpoTag((short) MpoInterface.TAG_IMAGE_NUMBER,
+ MpoTag.TYPE_UNSIGNED_LONG, 1, MpoIfdData.TYPE_MP_ATTRIB_IFD, false);
+ imageNumTag.setValue(imageNum++);
+ image.addTag(imageNumTag);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/camera/mpo/MpoIfdData.java b/src/com/android/camera/mpo/MpoIfdData.java
new file mode 100644
index 000000000..8e422944a
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoIfdData.java
@@ -0,0 +1,126 @@
+/*
+ * 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.mpo;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class stores all the tags in an MP Index IFD.
+ */
+public class MpoIfdData {
+ public static final int TYPE_MP_INDEX_IFD = 1;
+ public static final int TYPE_MP_ATTRIB_IFD = 2;
+ public static final byte[] MP_FORMAT_VER_VALUE = { 0x30, 0x31, 0x30, 0x30 };
+
+ private final int mIfdId;
+ private final Map<Short, MpoTag> mTags = new HashMap<Short, MpoTag>();
+ private int mOffsetToNextIfd = 0;
+
+ /**
+ * Creates an empty MpIndexIfdData
+ */
+ public MpoIfdData(int ifdId) {
+ mIfdId = ifdId;
+ }
+
+ /**
+ * Get a array the contains all {@link MpoTag} in this IFD.
+ */
+ protected MpoTag[] getAllTags() {
+ return mTags.values().toArray(new MpoTag[mTags.size()]);
+ }
+
+ /**
+ * Gets the {@link MpoTag} with given tag id. Return null if there is no
+ * such tag.
+ */
+ protected MpoTag getTag(short tagId) {
+ return mTags.get(tagId);
+ }
+
+ /**
+ * Adds or replaces a {@link MpoTag}.
+ */
+ protected MpoTag setTag(MpoTag tag) {
+ tag.setIfd(mIfdId);
+ return mTags.put(tag.getTagId(), tag);
+ }
+
+ protected boolean checkCollision(short tagId) {
+ return mTags.get(tagId) != null;
+ }
+
+ /**
+ * Removes the tag of the given ID
+ */
+ protected void removeTag(short tagId) {
+ mTags.remove(tagId);
+ }
+
+ /**
+ * Gets the tags count in the IFD.
+ */
+ protected int getTagCount() {
+ return mTags.size();
+ }
+
+ /**
+ * Sets the offset of next IFD.
+ */
+ protected void setOffsetToNextIfd(int offset) {
+ mOffsetToNextIfd = offset;
+ }
+
+ /**
+ * Gets the offset of next IFD.
+ */
+ protected int getOffsetToNextIfd() {
+ return mOffsetToNextIfd;
+ }
+
+ /**
+ * Returns true if all tags in this two IFDs are equal. Note that tags of
+ * IFD offset will be ignored.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof MpoIfdData) {
+ MpoIfdData data = (MpoIfdData) obj;
+ if (data.getTagCount() == getTagCount()) {
+ MpoTag[] tags = data.getAllTags();
+ for (MpoTag tag : tags) {
+ MpoTag tag2 = mTags.get(tag.getTagId());
+ if (!tag.equals(tag2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/camera/mpo/MpoImageData.java b/src/com/android/camera/mpo/MpoImageData.java
new file mode 100644
index 000000000..84d2d0f6e
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoImageData.java
@@ -0,0 +1,248 @@
+/*
+ * 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.mpo;
+
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class stores the MPO header in IFDs according to the MPO specification.
+ */
+
+public class MpoImageData {
+ private static final String TAG = "MpoImageData";
+ static final int OFFSET_TO_FIRST_IFD = 8;
+ static final int MP_FORMAT_IDENTIFIER = 0x4D504600; // 'M' 'P' 'F' 'NULL'
+ static final int MP_HEADER_SIZE = 8;
+ static final int APP_HEADER_SIZE = 6;
+
+ private final MpoIfdData mMpIndexIfdData = new MpoIfdData(MpoIfdData.TYPE_MP_INDEX_IFD);
+ private final MpoIfdData mMpAttribIfdData = new MpoIfdData(MpoIfdData.TYPE_MP_ATTRIB_IFD);
+ private final byte[] mJpegData;
+ private final ByteOrder mByteOrder;
+
+ public MpoImageData(byte[] jpegData, ByteOrder byteOrder) {
+ mJpegData = jpegData;
+ mByteOrder = byteOrder;
+ }
+
+ /**
+ * Gets the jpeg data.
+ */
+ protected byte[] getJpegData() {
+ return mJpegData;
+ }
+
+ /**
+ * Gets the byte order.
+ */
+ protected ByteOrder getByteOrder() {
+ return mByteOrder;
+ }
+
+ /**
+ * Returns the {@link mMpAttribIfdData} object if it exists or null.
+ */
+ protected MpoIfdData getAttribIfdData() {
+ return mMpAttribIfdData;
+ }
+
+ /**
+ * Returns the {@link mMpIndexIfdData} object if it exists or null.
+ */
+ protected MpoIfdData getIndexIfdData() {
+ return mMpIndexIfdData;
+ }
+
+ /**
+ * Returns the {@link MpoIfdData} object corresponding to a given IFD.
+ */
+ protected MpoIfdData getMpIfdData(int ifdId) {
+ return (ifdId == MpoIfdData.TYPE_MP_INDEX_IFD) ? mMpIndexIfdData : mMpAttribIfdData;
+ }
+
+ /**
+ * Returns the tag with a given tag ID in the given IFD if the tag exists.
+ * Otherwise returns null.
+ */
+ protected MpoTag getTag(short tag, int ifd) {
+ MpoIfdData mpIfdData = getMpIfdData(ifd);
+ return mpIfdData.getTag(tag);
+ }
+
+ /**
+ * Adds the given MpoTag to its default IFD and returns an existing MpoTag
+ * with the same TID or null if none exist.
+ */
+ protected MpoTag addTag(MpoTag tag) {
+ if (tag != null) {
+ int ifd = tag.getIfd();
+ return addTag(tag, ifd);
+ }
+ return null;
+ }
+
+ /**
+ * Adds the given MpoTag to the given IFD and returns an existing MpoTag
+ * with the same tag ID or null if none exist.
+ */
+ protected MpoTag addTag(MpoTag tag, int ifdId) {
+ if (tag != null && MpoTag.isValidIfd(ifdId)) {
+ return getMpIfdData(ifdId).setTag(tag);
+ }
+ return null;
+ }
+
+ /**
+ * Removes the tag with a given tag ID and IFD.
+ */
+ protected void removeTag(short tagId, int ifdId) {
+ getMpIfdData(ifdId).removeTag(tagId);
+ }
+
+ /**
+ * Returns a list of all {@link MpoTag}s in the ExifData or null if there
+ * are none.
+ */
+ protected List<MpoTag> getAllTags() {
+ ArrayList<MpoTag> ret = new ArrayList<MpoTag>();
+ MpoTag[] tags = mMpIndexIfdData.getAllTags();
+ if (tags != null) {
+ for (MpoTag t : tags) {
+ ret.add(t);
+ }
+ }
+
+ tags = mMpAttribIfdData.getAllTags();
+ if (tags != null) {
+ for (MpoTag t : tags) {
+ ret.add(t);
+ }
+ }
+
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all {@link MpoTag}s in a given IFD or null if there are
+ * none.
+ */
+ protected List<MpoTag> getAllTagsForIfd(int ifd) {
+ MpoTag[] tags = getMpIfdData(ifd).getAllTags();
+ if (tags == null) {
+ return null;
+ }
+ ArrayList<MpoTag> ret = new ArrayList<MpoTag>(tags.length);
+ for (MpoTag t : tags) {
+ ret.add(t);
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all {@link MpoTag}s with a given TID or null if there
+ * are none.
+ */
+ protected List<MpoTag> getAllTagsForTagId(short tag) {
+ ArrayList<MpoTag> ret = new ArrayList<MpoTag>();
+ MpoTag t = mMpIndexIfdData.getTag(tag);
+ if (t != null) {
+ ret.add(t);
+ }
+
+ t = mMpAttribIfdData.getTag(tag);
+ if (t != null) {
+ ret.add(t);
+ }
+
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof MpoImageData) {
+ MpoImageData data = (MpoImageData) obj;
+ if (data.mByteOrder != mByteOrder) {
+ return false;
+ }
+
+ MpoIfdData indexIfd1 = data.getMpIfdData(MpoIfdData.TYPE_MP_INDEX_IFD);
+ MpoIfdData indexIfd2 = getMpIfdData(MpoIfdData.TYPE_MP_INDEX_IFD);
+ if (indexIfd1 != indexIfd2 && indexIfd1 != null && !indexIfd1.equals(indexIfd2)) {
+ return false;
+ }
+
+ MpoIfdData attribIfd1 = data.getMpIfdData(MpoIfdData.TYPE_MP_ATTRIB_IFD);
+ MpoIfdData attribIfd2 = getMpIfdData(MpoIfdData.TYPE_MP_ATTRIB_IFD);
+ if (attribIfd1 != attribIfd2 && attribIfd1 != null && !attribIfd1.equals(attribIfd2)) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private int calculateOffsetOfIfd(MpoIfdData ifd, int offset) {
+ offset += 2 + ifd.getTagCount() * MpoTag.TAG_SIZE + 4;
+ MpoTag[] tags = ifd.getAllTags();
+ for (MpoTag tag : tags) {
+ if (tag.getDataSize() > 4) {
+ tag.setOffset(offset);
+ offset += tag.getDataSize();
+ }
+ }
+ return offset;
+ }
+
+ public int calculateAllIfdOffsets() {
+ int offset = MP_HEADER_SIZE;
+ MpoIfdData indexIfd = getIndexIfdData();
+ if (indexIfd.getTagCount() > 0)
+ offset = calculateOffsetOfIfd(indexIfd, offset);
+
+ MpoIfdData attribIfd = getAttribIfdData();
+ if (attribIfd.getTagCount() > 0) {
+ indexIfd.setOffsetToNextIfd(offset);
+ offset = calculateOffsetOfIfd(attribIfd, offset);
+ }
+
+ return offset;
+ }
+
+ public int calculateImageSize() {
+ return 2 + APP_HEADER_SIZE + calculateAllIfdOffsets() + mJpegData.length;
+ }
+}
diff --git a/src/com/android/camera/mpo/MpoInterface.java b/src/com/android/camera/mpo/MpoInterface.java
new file mode 100644
index 000000000..3e2d9a4e2
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoInterface.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Not a contribution.
+ *
+ * Copyright (C) 2013 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.mpo;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import android.util.Log;
+
+import com.android.camera.exif.ExifInterface;
+import com.android.camera.util.CameraUtil;
+
+public class MpoInterface {
+ private static final String TAG = "MpoInterface";
+ private static final String NULL_ARGUMENT_STRING = "Argument is null";
+
+ // Index IFD
+ public static final int TAG_MP_FORMAT_VERSION = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_INDEX_IFD + MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB000);
+ public static final int TAG_NUM_IMAGES = ExifInterface.defineTag(MpoIfdData.TYPE_MP_INDEX_IFD,
+ (short) 0xB001);
+ public static final int TAG_MP_ENTRY = ExifInterface.defineTag(MpoIfdData.TYPE_MP_INDEX_IFD,
+ (short) 0xB002);
+ public static final int TAG_IMAGE_UNIQUE_ID_LIST = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_INDEX_IFD, (short) 0xB003);
+ public static final int TAG_NUM_CAPTURED_FRAMES = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_INDEX_IFD, (short) 0xB004);
+
+ // Attrib IFD
+ public static final int TAG_IMAGE_NUMBER = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB101);
+ public static final int TAG_PAN_ORIENTATION = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB201);
+ public static final int TAG_PAN_OVERLAP_H = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB202);
+ public static final int TAG_PAN_OVERLAP_V = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB203);
+ public static final int TAG_BASE_VIEWPOINT_NUM = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB204);
+ public static final int TAG_CONVERGE_ANGLE = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB205);
+ public static final int TAG_BASELINE_LEN = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB206);
+ public static final int TAG_DIVERGE_ANGLE = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB207);
+ public static final int TAG_AXIS_DISTANCE_X = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB208);
+ public static final int TAG_AXIS_DISTANCE_Y = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB209);
+ public static final int TAG_AXIS_DISTANCE_Z = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB20A);
+ public static final int TAG_YAW_ANGLE = ExifInterface.defineTag(MpoIfdData.TYPE_MP_ATTRIB_IFD,
+ (short) 0xB20B);
+ public static final int TAG_PITCH_ANGLE = ExifInterface.defineTag(
+ MpoIfdData.TYPE_MP_ATTRIB_IFD, (short) 0xB20C);
+ public static final int TAG_ROLL_ANGLE = ExifInterface.defineTag(MpoIfdData.TYPE_MP_ATTRIB_IFD,
+ (short) 0xB20D);
+
+ public static int writeMpo(MpoData mpo, OutputStream out) {
+ if (mpo == null || out == null)
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+
+ MpoOutputStream s = getMpoWriterStream(out);
+ s.setMpoData(mpo);
+
+ // check and write mpo file
+ try {
+ s.writeMpoFile();
+ } catch (IOException e) {
+ CameraUtil.closeSilently(s);
+ Log.w(TAG, "IO Exception when writing mpo image");
+ return -1;
+ }
+
+ // close stream
+ CameraUtil.closeSilently(s);
+ return s.size();
+ }
+
+ public static int writeMpo(MpoData mpo, String outFilename) {
+ if (mpo == null || outFilename == null)
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+
+ return writeMpo(mpo, getFileWriterStream(outFilename));
+ }
+
+ /**
+ * Wraps an OutputStream object with an MpoOutputStream.
+ *
+ * @param outStream
+ * an OutputStream to wrap.
+ * @return an MpoOutputStream that wraps the outStream parameter, and adds
+ * mpo metadata. A jpeg image should be written to this stream.
+ */
+ private static MpoOutputStream getMpoWriterStream(OutputStream outStream) {
+ if (outStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ MpoOutputStream mos = new MpoOutputStream(outStream);
+ return mos;
+ }
+
+ /**
+ * Returns an FileOutputStream object that writes to a file.
+ *
+ * @param outFileName
+ * an String containing a filepath for a file.
+ * @return an FileOutputStream that writes to the outFileName file.
+ * @throws FileNotFoundException
+ */
+ private static OutputStream getFileWriterStream(String outFileName) {
+ if (outFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream out = null;
+ try {
+ out = new FileOutputStream(outFileName);
+ } catch (FileNotFoundException e) {
+ CameraUtil.closeSilently(out);
+ Log.w(TAG, "File not found");
+ }
+ return out;
+ }
+}
diff --git a/src/com/android/camera/mpo/MpoOutputStream.java b/src/com/android/camera/mpo/MpoOutputStream.java
new file mode 100644
index 000000000..6e8e72fd9
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoOutputStream.java
@@ -0,0 +1,333 @@
+/*
+ * 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.mpo;
+
+import java.io.BufferedOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.List;
+
+import android.util.Log;
+
+import com.android.camera.exif.JpegHeader;
+import com.android.camera.exif.OrderedDataOutputStream;
+import com.android.camera.mpo.MpoTag.MpEntry;
+
+class MpoOutputStream extends FilterOutputStream {
+ private static final String TAG = "MpoOutputStream";
+ private static final boolean DEBUG = true;
+ private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
+
+ private static final int STATE_SOI = 0;
+ private static final int STATE_FRAME_HEADER = 1;
+ private static final int STATE_JPEG_DATA = 3;
+
+ private static final short TIFF_HEADER = 0x002A;
+ private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+ private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+ private static final int MAX_EXIF_SIZE = 65535;
+
+ private MpoData mMpoData;
+ private MpoImageData mCurrentImageData;
+ private int mState = STATE_SOI;
+ private int mByteToSkip;
+ private int mByteToCopy;
+ private byte[] mSingleByteArray = new byte[1];
+ private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+ private int mMpoOffsetStart = -1;
+ private int mSize = 0;
+
+ protected MpoOutputStream(OutputStream ou) {
+ super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
+ }
+
+ /**
+ * Sets the ExifData to be written into the JPEG file. Should be called
+ * before writing image data.
+ */
+ protected void setMpoData(MpoData mpoData) {
+ mMpoData = mpoData;
+ mMpoData.updateAllTags();
+ }
+
+ private void resetStates() {
+ mState = STATE_SOI;
+ mByteToSkip = 0;
+ mByteToCopy = 0;
+ mBuffer.rewind();
+ }
+
+ private int requestByteToBuffer(int requestByteCount, byte[] buffer, int offset, int length) {
+ int byteNeeded = requestByteCount - mBuffer.position();
+ int byteToRead = length > byteNeeded ? byteNeeded : length;
+ mBuffer.put(buffer, offset, byteToRead);
+ return byteToRead;
+ }
+
+ void writeMpoFile() throws IOException {
+ // check and write primary image
+ mCurrentImageData = mMpoData.getPrimaryMpoImage();
+ write(mCurrentImageData.getJpegData());
+ flush();
+
+ // check and write auxiliary images
+ for (MpoImageData image : mMpoData.getAuxiliaryMpoImages()) {
+ resetStates();
+ mCurrentImageData = image;
+ write(mCurrentImageData.getJpegData());
+ flush();
+ }
+ }
+
+ /**
+ * Writes the image out. The input data should be a valid JPEG format. After
+ * writing, it's Exif header will be replaced by the given header.
+ */
+ @Override
+ public void write(byte[] buffer, int offset, int length) throws IOException {
+ while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA) && length > 0) {
+ if (mByteToSkip > 0) {
+ int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
+ length -= byteToProcess;
+ mByteToSkip -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (mByteToCopy > 0) {
+ int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
+ out.write(buffer, offset, byteToProcess);
+ mSize += byteToProcess;
+ length -= byteToProcess;
+ mByteToCopy -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (length == 0) {
+ return;
+ }
+ switch (mState) {
+ case STATE_SOI:
+ int byteRead = requestByteToBuffer(2, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ if (mBuffer.position() < 2) {
+ return;
+ }
+ mBuffer.rewind();
+ if (mBuffer.getShort() != JpegHeader.SOI) {
+ throw new IOException("Not a valid jpeg image, cannot write exif");
+ }
+ out.write(mBuffer.array(), 0, 2);
+ mSize += 2;
+ mState = STATE_FRAME_HEADER;
+ mBuffer.rewind();
+ break;
+ case STATE_FRAME_HEADER:
+ // Copy APP1 if it exists
+ // Insert MPO data
+ // Copy remainder of image
+ byteRead = requestByteToBuffer(4, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ // Check if this image data doesn't contain SOF.
+ if (mBuffer.position() == 2) {
+ short tag = mBuffer.getShort();
+ if (tag == JpegHeader.EOI) {
+ out.write(mBuffer.array(), 0, 2);
+ mSize += 2;
+ mBuffer.rewind();
+ }
+ }
+ if (mBuffer.position() < 4) {
+ return;
+ }
+ mBuffer.rewind();
+ short marker = mBuffer.getShort();
+ if (marker == JpegHeader.APP1 || marker == JpegHeader.APP0) {
+ out.write(mBuffer.array(), 0, 4);
+ mSize += 4;
+ mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
+ } else {
+ writeMpoData();
+ out.write(mBuffer.array(), 0, 4);
+ mSize += 4;
+ mState = STATE_JPEG_DATA;
+ }
+ mBuffer.rewind();
+ break;
+ }
+ }
+ if (length > 0) {
+ out.write(buffer, offset, length);
+ mSize += length;
+ }
+ }
+
+ /**
+ * Writes the one bytes out. The input data should be a valid JPEG format.
+ * After writing, it's Exif header will be replaced by the given header.
+ */
+ @Override
+ public void write(int oneByte) throws IOException {
+ mSingleByteArray[0] = (byte) (0xff & oneByte);
+ write(mSingleByteArray);
+ }
+
+ /**
+ * Equivalent to calling write(buffer, 0, buffer.length).
+ */
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ write(buffer, 0, buffer.length);
+ }
+
+ private void writeMpoData() throws IOException {
+ if (mMpoData == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Writing mpo data...");
+ }
+ int exifSize = mCurrentImageData.calculateAllIfdOffsets() + MpoImageData.APP_HEADER_SIZE;
+ if (exifSize > MAX_EXIF_SIZE) {
+ throw new IOException("Exif header is too large (>64Kb)");
+ }
+ OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
+ dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ dataOutputStream.writeShort(JpegHeader.APP2);
+ dataOutputStream.writeShort((short) (exifSize));
+ dataOutputStream.writeInt(MpoImageData.MP_FORMAT_IDENTIFIER);
+ if (mMpoOffsetStart == -1) {
+ mMpoOffsetStart = mSize + dataOutputStream.size();
+ }
+ if (mCurrentImageData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+ dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
+ } else {
+ dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
+ }
+ dataOutputStream.setByteOrder(mCurrentImageData.getByteOrder());
+ dataOutputStream.writeShort(TIFF_HEADER);
+ if (exifSize > MpoImageData.MP_HEADER_SIZE + MpoImageData.APP_HEADER_SIZE) {
+ dataOutputStream.writeInt(MpoImageData.OFFSET_TO_FIRST_IFD);
+ writeAllTags(dataOutputStream);
+ } else
+ dataOutputStream.writeInt(0);
+
+ mSize += dataOutputStream.size();
+ }
+
+ private void updateIndexIfdOffsets(MpoIfdData indexIfd, int mpoOffset) {
+ // update offsets
+ MpoTag mpEntryTag = mMpoData.getPrimaryMpoImage().getTag((short) MpoInterface.TAG_MP_ENTRY,
+ MpoIfdData.TYPE_MP_INDEX_IFD);
+ List<MpEntry> mpEntries = mpEntryTag.getMpEntryValue();
+ for (int i = 1; i < mpEntries.size(); i++) { // primary offset is always
+ // 0
+ MpEntry entry = mpEntries.get(i);
+ entry.setImageOffset(entry.getImageOffset() - mpoOffset);
+ }
+
+ mpEntryTag.setValue(mpEntries);
+ }
+
+ private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
+ MpoIfdData indexIfd = mCurrentImageData.getIndexIfdData();
+ if (indexIfd.getTagCount() > 0) {
+ updateIndexIfdOffsets(indexIfd, mMpoOffsetStart);
+ writeIfd(indexIfd, dataOutputStream);
+ }
+
+ MpoIfdData attribIfd = mCurrentImageData.getAttribIfdData();
+ if (attribIfd.getTagCount() > 0)
+ writeIfd(attribIfd, dataOutputStream);
+ }
+
+ private void writeIfd(MpoIfdData ifd, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ MpoTag[] tags = ifd.getAllTags();
+ dataOutputStream.writeShort((short) tags.length);
+ for (MpoTag tag : tags) {
+ dataOutputStream.writeShort(tag.getTagId());
+ dataOutputStream.writeShort(tag.getDataType());
+ dataOutputStream.writeInt(tag.getComponentCount());
+ if (DEBUG) {
+ Log.v(TAG, "\n" + tag.toString());
+ }
+ if (tag.getDataSize() > 4) {
+ dataOutputStream.writeInt(tag.getOffset());
+ } else {
+ MpoOutputStream.writeTagValue(tag, dataOutputStream);
+ for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
+ dataOutputStream.write(0);
+ }
+ }
+ }
+ dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
+ for (MpoTag tag : tags) {
+ if (tag.getDataSize() > 4) {
+ MpoOutputStream.writeTagValue(tag, dataOutputStream);
+ }
+ }
+ }
+
+ static void writeTagValue(MpoTag tag, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ switch (tag.getDataType()) {
+ case MpoTag.TYPE_ASCII:
+ byte buf[] = tag.getStringByte();
+ if (buf.length == tag.getComponentCount()) {
+ buf[buf.length - 1] = 0;
+ dataOutputStream.write(buf);
+ } else {
+ dataOutputStream.write(buf);
+ dataOutputStream.write(0);
+ }
+ break;
+ case MpoTag.TYPE_LONG:
+ case MpoTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeInt((int) tag.getValueAt(i));
+ }
+ break;
+ case MpoTag.TYPE_RATIONAL:
+ case MpoTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeRational(tag.getRational(i));
+ }
+ break;
+ case MpoTag.TYPE_UNDEFINED:
+ case MpoTag.TYPE_UNSIGNED_BYTE:
+ buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ dataOutputStream.write(buf);
+ break;
+ case MpoTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeShort((short) tag.getValueAt(i));
+ }
+ break;
+ }
+ }
+
+ int size() {
+ return mSize;
+ }
+}
diff --git a/src/com/android/camera/mpo/MpoTag.java b/src/com/android/camera/mpo/MpoTag.java
new file mode 100644
index 000000000..bc0e6ce30
--- /dev/null
+++ b/src/com/android/camera/mpo/MpoTag.java
@@ -0,0 +1,163 @@
+/*
+ * 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.mpo;
+
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.util.Log;
+
+import com.android.camera.exif.ExifTag;
+
+public class MpoTag extends ExifTag {
+ private static final String TAG = "MpoTag";
+ static final int TAG_SIZE = 12;
+
+ MpoTag(short tagId, short type, int componentCount, int ifd, boolean hasDefinedComponentCount) {
+ super(tagId, type, componentCount, ifd, hasDefinedComponentCount);
+ }
+
+ public boolean setValue(List<MpEntry> entries) {
+ if (getTagId() != (short) MpoInterface.TAG_MP_ENTRY) {
+ return false;
+ }
+
+ byte[] bytes = new byte[entries.size() * MpEntry.SIZE];
+ for (int i = 0; i < entries.size(); i++) {
+ MpEntry entry = entries.get(i);
+ entry.getBytes(ByteBuffer.wrap(bytes, i * MpEntry.SIZE, MpEntry.SIZE));
+ }
+ return setValue(bytes);
+ }
+
+ public List<MpEntry> getMpEntryValue() {
+ if (getTagId() != (short) MpoInterface.TAG_MP_ENTRY) {
+ return null;
+ }
+
+ byte[] bytes = getValueAsBytes();
+ List<MpEntry> entries = new ArrayList<MpEntry>(bytes.length / MpEntry.SIZE);
+ for (int i = 0; i < bytes.length; i += MpEntry.SIZE) {
+ entries.add(new MpEntry(ByteBuffer.wrap(bytes, i, MpEntry.SIZE)));
+ }
+ return entries;
+ }
+
+ static class MpEntry {
+ static final int SIZE = 16;
+ private int mImageAttrib;
+ private int mImageSize;
+ private int mImageOffset;
+ private short mDependantImage1;
+ private short mDependantImage2;
+
+ public MpEntry() {
+ this(0, 0, 0, (short) 0, (short) 0);
+ }
+
+ public MpEntry(int imageAttrib, int imageSize, int imageOffset) {
+ this(imageAttrib, imageSize, imageOffset, (short) 0, (short) 0);
+ }
+
+ public MpEntry(int imageAttrib, int imageSize, int imageOffset, short dependantImage1,
+ short dependantImage2) {
+ mImageAttrib = imageAttrib;
+ mImageSize = imageSize;
+ mImageOffset = imageOffset;
+ mDependantImage1 = dependantImage1;
+ mDependantImage2 = dependantImage2;
+ }
+
+ public MpEntry(ByteBuffer buffer) {
+ mImageAttrib = buffer.getInt();
+ mImageSize = buffer.getInt();
+ mImageOffset = buffer.getInt();
+ mDependantImage1 = buffer.getShort();
+ mDependantImage2 = buffer.getShort();
+ }
+
+ public int getImageAttrib() {
+ return mImageAttrib;
+ }
+
+ public int getImageSize() {
+ return mImageSize;
+ }
+
+ public int getImageOffset() {
+ return mImageOffset;
+ }
+
+ public short getDependantImage1() {
+ return mDependantImage1;
+ }
+
+ public short getDependantImage2() {
+ return mDependantImage2;
+ }
+
+ public void setImageAttrib(int imageAttrib) {
+ mImageAttrib = imageAttrib;
+ }
+
+ public void setImageSize(int imageSize) {
+ mImageSize = imageSize;
+ }
+
+ public void setImageOffset(int imageOffset) {
+ mImageOffset = imageOffset;
+ }
+
+ public void setDependantImage1(short depImage1) {
+ mDependantImage1 = depImage1;
+ }
+
+ public void setDependantImage2(short depImage2) {
+ mDependantImage2 = depImage2;
+ }
+
+ public boolean getBytes(ByteBuffer buffer) {
+ try {
+ buffer.putInt(mImageAttrib);
+ buffer.putInt(mImageSize);
+ buffer.putInt(mImageOffset);
+ buffer.putShort(mDependantImage1);
+ buffer.putShort(mDependantImage2);
+ } catch (BufferOverflowException e) {
+ Log.w(TAG, "Buffer size too small");
+ return false;
+ }
+
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/android/camera/ui/CameraControls.java b/src/com/android/camera/ui/CameraControls.java
index 587cfcad0..989568953 100644
--- a/src/com/android/camera/ui/CameraControls.java
+++ b/src/com/android/camera/ui/CameraControls.java
@@ -84,7 +84,7 @@ public class CameraControls extends RotatableLayout {
private float[][] mLocY = new float[4][10];
private boolean[] mTempEnabled = new boolean[10];
private boolean mLocSet = false;
-
+ private boolean mHideRemainingPhoto = false;
private LinearLayout mRemainingPhotos;
private TextView mRemainingPhotosText;
private int mCurrentRemaining = -1;
@@ -706,7 +706,8 @@ public class CameraControls extends RotatableLayout {
mPreview.animate().translationXBy(mSize).setDuration(ANIME_DURATION);
break;
}
- if (mRemainingPhotos.getVisibility() == View.INVISIBLE) {
+ if ((mRemainingPhotos.getVisibility() == View.INVISIBLE) &&
+ !mHideRemainingPhoto){
mRemainingPhotos.setVisibility(View.VISIBLE);
}
mRefocusToast.setVisibility(View.GONE);
@@ -922,7 +923,7 @@ public class CameraControls extends RotatableLayout {
public void updateRemainingPhotos(int remaining) {
long remainingStorage = Storage.getAvailableSpace() - Storage.LOW_STORAGE_THRESHOLD_BYTES;
- if (remaining < 0 && remainingStorage <= 0) {
+ if ((remaining < 0 && remainingStorage <= 0) || mHideRemainingPhoto) {
mRemainingPhotos.setVisibility(View.GONE);
} else {
for (int i = mRemainingPhotos.getChildCount() - 1; i >= 0; --i) {
@@ -961,7 +962,7 @@ public class CameraControls extends RotatableLayout {
public void showRefocusToast(boolean show) {
mRefocusToast.setVisibility(show ? View.VISIBLE : View.GONE);
- if (mCurrentRemaining > 0 ) {
+ if ((mCurrentRemaining > 0 ) && !mHideRemainingPhoto) {
mRemainingPhotos.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
@@ -1003,6 +1004,12 @@ public class CameraControls extends RotatableLayout {
mMenu.setVisibility(View.VISIBLE);
}
+ public void hideRemainingPhotoCnt() {
+ mHideRemainingPhoto = true;
+ mRemainingPhotos.setVisibility(View.GONE);
+ mRemainingPhotosText.setVisibility(View.GONE);
+ }
+
private class ArrowTextView extends TextView {
private static final int TEXT_SIZE = 14;
private static final int PADDING_SIZE = 18;
diff --git a/src/com/android/camera/ui/ZoomRenderer.java b/src/com/android/camera/ui/ZoomRenderer.java
index 7a2e2e805..0358bda7a 100644
--- a/src/com/android/camera/ui/ZoomRenderer.java
+++ b/src/com/android/camera/ui/ZoomRenderer.java
@@ -49,11 +49,16 @@ public class ZoomRenderer extends OverlayRenderer
private int mZoomFraction;
private Rect mTextBounds;
private int mOrientation;
+ private boolean mCamera2 = false;
+ private float mZoomValue;
+ private float mZoomMinValue;
+ private float mZoomMaxValue;
public interface OnZoomChangedListener {
void onZoomStart();
void onZoomEnd();
void onZoomValueChanged(int index); // only for immediate zoom
+ void onZoomValueChanged(float value);
}
public ZoomRenderer(Context ctx) {
@@ -81,10 +86,23 @@ public class ZoomRenderer extends OverlayRenderer
mMinZoom = 0;
}
+ public void setZoomMax(float zoomMax) {
+ mCamera2 = true;
+ mZoomMaxValue = zoomMax;
+ mZoomMinValue = 1f;
+ }
+
public void setZoom(int index) {
mCircleSize = mMinCircle + index * (mMaxCircle - mMinCircle) / (mMaxZoom - mMinZoom);
}
+ public void setZoom(float zoomValue) {
+ mCamera2 = true;
+ mZoomValue = zoomValue;
+ mCircleSize = (int) (mMinCircle + (mMaxCircle - mMinCircle) * (mZoomValue - mZoomMinValue) /
+ (mZoomMaxValue - mZoomMinValue));
+ }
+
public void setZoomValue(int value) {
value = value / 10;
mZoomSig = value / 10;
@@ -120,6 +138,7 @@ public class ZoomRenderer extends OverlayRenderer
canvas.drawCircle((float) mCenterX, (float) mCenterY,
mCircleSize, mPaint);
String txt = mZoomSig+"."+mZoomFraction+"x";
+ if (mCamera2) txt = "" + mZoomValue;
mTextPaint.getTextBounds(txt, 0, txt.length(), mTextBounds);
canvas.drawText(txt, mCenterX - mTextBounds.centerX(), mCenterY - mTextBounds.centerY(),
mTextPaint);
@@ -133,8 +152,16 @@ public class ZoomRenderer extends OverlayRenderer
circle = Math.min(mMaxCircle, circle);
if (mListener != null && circle != mCircleSize) {
mCircleSize = circle;
- int zoom = mMinZoom + (int) ((mCircleSize - mMinCircle) * (mMaxZoom - mMinZoom) / (mMaxCircle - mMinCircle));
- mListener.onZoomValueChanged(zoom);
+ if (mCamera2) {
+ float zoom = mZoomMinValue + (mZoomMaxValue - mZoomMinValue) / (mMaxCircle -
+ mMinCircle) * (mCircleSize - mMinCircle);
+ zoom = ((int) (zoom * 10)) / 10.0f;
+ mListener.onZoomValueChanged(zoom);
+ } else {
+ int zoom = mMinZoom + (int) ((mCircleSize - mMinCircle) * (mMaxZoom - mMinZoom) /
+ (mMaxCircle - mMinCircle));
+ mListener.onZoomValueChanged(zoom);
+ }
update();
}
return true;
diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java
index 812220c1a..b1deca6e0 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -35,6 +35,7 @@ import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
+import android.hardware.camera2.CameraCharacteristics;
import android.location.Location;
import android.net.Uri;
import android.os.Handler;
@@ -1168,4 +1169,32 @@ public class CameraUtil {
return retRatio;
}
+ public static boolean isZoomSupported(CameraCharacteristics[] characteristics, List<Integer>
+ characteristicsIndex) {
+ for (int i = 0; i < characteristicsIndex.size(); i++) {
+ if (!isZoomSupported(characteristics[characteristicsIndex.get(i)]))
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isZoomSupported(CameraCharacteristics characteristic) {
+ return characteristic.get(CameraCharacteristics
+ .SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) > 1f;
+ }
+
+ public static boolean isAutoFocusSupported(CameraCharacteristics[] characteristics, List<Integer>
+ characteristicsIndex) {
+ for (int i = 0; i < characteristicsIndex.size(); i++) {
+ if (!isAutoFocusSupported(characteristics[characteristicsIndex.get(i)]))
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean isAutoFocusSupported(CameraCharacteristics characteristic) {
+ Integer maxAfRegions = characteristic.get(
+ CameraCharacteristics.CONTROL_MAX_REGIONS_AF);
+ return maxAfRegions != null && maxAfRegions > 0;
+ }
}
diff --git a/src/com/android/camera/util/ClearSightNativeEngine.java b/src/com/android/camera/util/ClearSightNativeEngine.java
new file mode 100644
index 000000000..cf0a740d7
--- /dev/null
+++ b/src/com/android/camera/util/ClearSightNativeEngine.java
@@ -0,0 +1,351 @@
+/*
+ * 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.util;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.util.Log;
+
+public class ClearSightNativeEngine {
+ private static final String TAG = "ClearSightNativeEngine";
+ static {
+ try {
+ System.loadLibrary("jni_clearsight");
+ mLibLoaded = true;
+ Log.v(TAG, "successfully loaded clearsight lib");
+ } catch (UnsatisfiedLinkError e) {
+ Log.e(TAG, "failed to load clearsight lib");
+ e.printStackTrace();
+ mLibLoaded = false;
+ }
+ }
+
+ private static final int METADATA_SIZE = 5;
+ private static final int Y_PLANE = 0;
+ private static final int VU_PLANE = 2;
+
+ // dummy OTP calib data
+ private static final String otp_calib = "Calibration OTP format version = 10301\n"
+ + "Main Native Sensor Resolution width = 4224px\n"
+ + "Main Native Sensor Resolution height = 3136px\n"
+ + "Main Calibration Resolution width = 1280px\n"
+ + "Main Calibration Resolution height = 950px\n"
+ + "Main Focal length ratio = 1.004896\n"
+ + "Aux Native Sensor Resolution width = 1600px\n"
+ + "Aux Native Sensor Resolution height = 1200px\n"
+ + "Aux Calibration Resolution width = 1280px\n"
+ + "Aux Calibration Resolution height = 960px\n"
+ + "Aux Focal length ratio = 1.000000\n"
+ + "Relative Rotation matrix [0] through [8] = 1.000000,-0.003008,0.000251,0.003073,1.000189,0.003329,0.019673,-0.003329,1.000284\n"
+ + "Relative Geometric surface parameters [0] through [31] = -0.307164,-0.879074,4.636152,0.297486,-0.157539,-6.889396,0.109467,-2.797022,-0.066306,-0.120142,0.196464,0.021974,2.905827,0.241197,0.048328,-5.116615,0.496533,-5.263813,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000,0.000000\n"
+ + "Relative Principal point X axis offset (ox) = 0.000000px\n"
+ + "Relative Principal point Y axis offset (oy) = 0.000000px\n"
+ + "Relative position flag = 1\n"
+ + "Baseline distance = 20.000000mm\n"
+ + "Main sensor mirror and flip setting = 3\n"
+ + "Aux sensor mirror and flip setting = 3\n"
+ + "Module orientation during calibration = 0\n"
+ + "Rotation flag = 0\n"
+ + "Main Normalized Focal length = 1000.0px\n"
+ + "Aux Normalized Focal length = 1000.0px";
+
+ private static boolean mLibLoaded;
+ private static ClearSightNativeEngine mInstance;
+
+ private Image mRefColorImage;
+ private Image mRefMonoImage;
+ private ArrayList<SourceImage> mSrcColor = new ArrayList<SourceImage>();
+ private ArrayList<SourceImage> mSrcMono = new ArrayList<SourceImage>();
+
+ private ClearSightNativeEngine() {
+ }
+
+ public static void createInstance() {
+ if (mInstance == null) {
+ mInstance = new ClearSightNativeEngine();
+ }
+ }
+
+ public static ClearSightNativeEngine getInstance() {
+ createInstance();
+ return mInstance;
+ }
+
+ public boolean isLibLoaded() {
+ return mLibLoaded;
+ }
+
+ public void reset() {
+ mSrcColor.clear();
+ mSrcMono.clear();
+ setReferenceColorImage(null);
+ setReferenceMonoImage(null);
+ }
+
+ public void setReferenceImage(boolean color, Image image) {
+ if (color)
+ setReferenceColorImage(image);
+ else
+ setReferenceMonoImage(image);
+ }
+
+ private void setReferenceColorImage(Image reference) {
+ if (mRefColorImage != null) {
+ mRefColorImage.close();
+ mRefColorImage = null;
+ }
+
+ mRefColorImage = reference;
+
+ if (mRefColorImage != null) {
+ Log.e(TAG,
+ "setRefColorImage - isdirectbuff: "
+ + mRefColorImage.getPlanes()[0].getBuffer()
+ .isDirect());
+ mSrcColor.add(new SourceImage(mRefColorImage.getPlanes()[Y_PLANE]
+ .getBuffer(), mRefColorImage.getPlanes()[VU_PLANE]
+ .getBuffer(), new int[] { 0, 0, 0, 0, 0 }));
+ }
+ }
+
+ private void setReferenceMonoImage(Image reference) {
+ if (mRefMonoImage != null) {
+ mRefMonoImage.close();
+ mRefMonoImage = null;
+ }
+
+ mRefMonoImage = reference;
+
+ if (mRefMonoImage != null) {
+ Log.e(TAG,
+ "setRefMonoImage - isdirectbuff: "
+ + mRefMonoImage.getPlanes()[0].getBuffer()
+ .isDirect());
+ mSrcMono.add(new SourceImage(mRefMonoImage.getPlanes()[Y_PLANE]
+ .getBuffer(), null, new int[] { 0, 0, 0, 0, 0 }));
+ }
+ }
+
+ public boolean hasReferenceImage(boolean color) {
+ return !(color ? mSrcColor.isEmpty() : mSrcMono.isEmpty());
+ }
+
+ public Image getReferenceImage(boolean color) {
+ return color ? mRefColorImage : mRefMonoImage;
+ }
+
+ public boolean registerImage(boolean color, Image image) {
+ return (color ? registerColorImage(image) : registerMonoImage(image));
+ }
+
+ private boolean registerColorImage(Image image) {
+ if (mSrcColor.isEmpty()) {
+ Log.w(TAG, "reference color image not yet set");
+ return false;
+ }
+
+ Plane[] planes = image.getPlanes();
+ ByteBuffer refY = mRefColorImage.getPlanes()[Y_PLANE].getBuffer();
+ ByteBuffer refVU = mRefColorImage.getPlanes()[VU_PLANE].getBuffer();
+ ByteBuffer regY = ByteBuffer.allocateDirect(refY.capacity());
+ ByteBuffer regVU = ByteBuffer.allocateDirect(refVU.capacity());
+ int[] metadata = new int[METADATA_SIZE];
+
+ boolean result = clearSightRegisterImage(refY,
+ planes[Y_PLANE].getBuffer(), planes[VU_PLANE].getBuffer(),
+ image.getWidth(), image.getHeight(),
+ planes[Y_PLANE].getRowStride(),
+ planes[VU_PLANE].getRowStride(), regY, regVU, metadata);
+
+ if (result) {
+ mSrcColor.add(new SourceImage(regY, regVU, metadata));
+ }
+
+ image.close();
+ return result;
+ }
+
+ private boolean registerMonoImage(Image image) {
+ if (mSrcMono.isEmpty()) {
+ Log.w(TAG, "reference mono image not yet set");
+ return false;
+ }
+
+ Plane[] planes = image.getPlanes();
+ ByteBuffer refY = mRefMonoImage.getPlanes()[Y_PLANE].getBuffer();
+ ByteBuffer regY = ByteBuffer.allocateDirect(refY.capacity());
+ int[] metadata = new int[METADATA_SIZE];
+
+ boolean result = clearSightRegisterImage(refY,
+ planes[Y_PLANE].getBuffer(), null, image.getWidth(),
+ image.getHeight(), planes[Y_PLANE].getRowStride(), 0, regY,
+ null, metadata);
+
+ if (result) {
+ mSrcMono.add(new SourceImage(regY, null, metadata));
+ }
+
+ image.close();
+ return result;
+ }
+
+ public ClearsightImage processImage() {
+ // check data validity
+ if (mSrcColor.size() != mSrcMono.size()) {
+ // mis-match in num images
+ Log.e(TAG, "processImage - numImages mismatch - bayer: "
+ + mSrcColor.size() + ", mono: " + mSrcMono.size());
+ return null;
+ }
+
+ int numImages = mSrcColor.size();
+ ByteBuffer[] srcColorY = new ByteBuffer[numImages];
+ ByteBuffer[] srcColorVU = new ByteBuffer[numImages];
+ int[][] metadataColor = new int[numImages][];
+ ByteBuffer[] srcMonoY = new ByteBuffer[numImages];
+ int[][] metadataMono = new int[numImages][];
+
+ Log.e(TAG, "processImage - numImages: " + numImages);
+
+ for (int i = 0; i < numImages; i++) {
+ SourceImage color = mSrcColor.get(i);
+ SourceImage mono = mSrcMono.get(i);
+
+ srcColorY[i] = color.mY;
+ srcColorVU[i] = color.mVU;
+ metadataColor[i] = color.mMetadata;
+
+ srcMonoY[i] = mono.mY;
+ metadataMono[i] = mono.mMetadata;
+ }
+
+ Plane[] colorPlanes = mRefColorImage.getPlanes();
+ Plane[] monoPlanes = mRefMonoImage.getPlanes();
+ ByteBuffer dstY = ByteBuffer.allocateDirect(colorPlanes[Y_PLANE]
+ .getBuffer().capacity());
+ ByteBuffer dstVU = ByteBuffer.allocateDirect(colorPlanes[VU_PLANE]
+ .getBuffer().capacity());
+ int[] roiRect = new int[4];
+
+ boolean result = clearSightProcess(numImages, srcColorY, srcColorVU,
+ metadataColor, mRefColorImage.getWidth(),
+ mRefColorImage.getHeight(),
+ colorPlanes[Y_PLANE].getRowStride(),
+ colorPlanes[VU_PLANE].getRowStride(), srcMonoY, metadataMono,
+ mRefMonoImage.getWidth(), mRefMonoImage.getHeight(),
+ monoPlanes[Y_PLANE].getRowStride(), otp_calib.getBytes(), dstY, dstVU,
+ colorPlanes[Y_PLANE].getRowStride(),
+ colorPlanes[VU_PLANE].getRowStride(), roiRect);
+
+ if (result) {
+ dstY.rewind();
+ dstVU.rewind();
+ byte[] data = new byte[dstY.capacity() + dstVU.capacity()];
+ int[] strides = new int[] { colorPlanes[Y_PLANE].getRowStride(),
+ colorPlanes[VU_PLANE].getRowStride() };
+ dstY.get(data, 0, dstY.capacity());
+ dstVU.get(data, dstY.capacity(), dstVU.capacity());
+ return new ClearsightImage(new YuvImage(data, ImageFormat.NV21,
+ mRefColorImage.getWidth(), mRefColorImage.getHeight(),
+ strides), roiRect);
+ } else {
+ return null;
+ }
+ }
+
+ native public boolean configureClearSight(float focalLengthRatio,
+ float brIntensity, float sharpenIntensity);
+
+ native public boolean clearSightRegisterImage(ByteBuffer refY,
+ ByteBuffer srcY, ByteBuffer srcVU, int width, int height,
+ int strideY, int strideVU, ByteBuffer dstY, ByteBuffer dstVU,
+ int[] metadata);
+
+ native public boolean clearSightProcess(int numImagePairs,
+ ByteBuffer[] srcColorY, ByteBuffer[] srcColorVU,
+ int[][] metadataColor, int srcColorWidth, int srcColorHeight,
+ int srcColorStrideY, int srcColorStrideVU, ByteBuffer[] srcMonoY,
+ int[][] metadataMono, int srcMonoWidth, int srcMonoHeight,
+ int srcMonoStrideY, byte[] otp, ByteBuffer dstY, ByteBuffer dstVU,
+ int dstStrideY, int dstStrideVU, int[] roiRect);
+
+ private class SourceImage {
+ ByteBuffer mY;
+ ByteBuffer mVU;
+ int[] mMetadata;
+
+ SourceImage(ByteBuffer y, ByteBuffer vu, int[] metadata) {
+ mY = y;
+ mVU = vu;
+ mMetadata = metadata;
+ }
+ }
+
+ public static class ClearsightImage {
+ private YuvImage mImage;
+ private Rect mRoiRect;
+
+ ClearsightImage(YuvImage image, int[] rect) {
+ mImage = image;
+ mRoiRect = new Rect(rect[0], rect[1], rect[0] + rect[2], rect[1]
+ + rect[3]);
+ }
+
+ public Rect getRoiRect() {
+ return mRoiRect;
+ }
+
+ public long getDataLength() {
+ return (mImage==null?0:mImage.getYuvData().length);
+ }
+
+ public int getWidth() {
+ return (mRoiRect.right - mRoiRect.left);
+ }
+
+ public int getHeight() {
+ return (mRoiRect.bottom - mRoiRect.top);
+ }
+
+ public byte[] compressToJpeg() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ mImage.compressToJpeg(mRoiRect, 100, baos);
+ return baos.toByteArray();
+ }
+ }
+}