summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk9
-rw-r--r--res/drawable-hdpi/ic_switch_pan.pngbin0 -> 5172 bytes
-rw-r--r--res/drawable-mdpi/ic_switch_pan.pngbin0 -> 3063 bytes
-rw-r--r--res/drawable-xhdpi/ic_switch_pan.pngbin0 -> 7660 bytes
-rw-r--r--res/drawable/pano_direction_left_indicator.xml21
-rw-r--r--res/drawable/pano_direction_right_indicator.xml21
-rw-r--r--res/layout-land/camera_controls.xml2
-rw-r--r--res/layout-land/pano_module_capture.xml96
-rw-r--r--res/layout-land/pano_module_review.xml48
-rw-r--r--res/layout-port/camera_controls.xml4
-rw-r--r--res/layout-port/pano_module_capture.xml96
-rw-r--r--res/layout-port/pano_module_review.xml60
-rw-r--r--res/layout/panorama_module.xml26
-rw-r--r--res/values-land/styles.xml7
-rw-r--r--res/values-port/styles.xml7
-rw-r--r--res/values/styles.xml7
-rw-r--r--src/com/android/camera/CameraActivity.java46
-rw-r--r--src/com/android/camera/Mosaic.java206
-rw-r--r--src/com/android/camera/MosaicFrameProcessor.java237
-rw-r--r--src/com/android/camera/MosaicPreviewRenderer.java190
-rw-r--r--src/com/android/camera/MosaicRenderer.java89
-rw-r--r--src/com/android/camera/PanoProgressBar.java188
-rw-r--r--src/com/android/camera/PanoUtil.java86
-rw-r--r--src/com/android/camera/PhotoUI.java12
-rw-r--r--src/com/android/camera/PreviewFrameLayout.java137
-rw-r--r--src/com/android/camera/VideoUI.java12
-rw-r--r--src/com/android/camera/WideAnglePanoramaController.java (renamed from src/com/android/camera/ui/LayoutChangeNotifier.java)23
-rw-r--r--src/com/android/camera/WideAnglePanoramaModule.java1071
-rw-r--r--src/com/android/camera/WideAnglePanoramaUI.java461
-rw-r--r--src/com/android/camera/app/AppManagerFactory.java47
-rw-r--r--src/com/android/camera/app/CameraApp.java1
-rw-r--r--src/com/android/camera/app/OrientationManager.java25
-rw-r--r--src/com/android/camera/app/OrientationSource.java6
-rw-r--r--src/com/android/camera/ui/CameraRootView.java4
-rw-r--r--src/com/android/camera/ui/LayoutChangeHelper.java43
-rw-r--r--src/com/android/camera/ui/LayoutNotifyView.java48
-rw-r--r--src/com/android/camera/ui/ModuleSwitcher.java (renamed from src/com/android/camera/ui/CameraSwitcher.java)30
-rw-r--r--src_pd/com/android/camera/PanoramaStitchingManager.java25
-rw-r--r--src_pd/com/android/camera/app/PanoramaStitchingManager.java43
39 files changed, 3095 insertions, 339 deletions
diff --git a/Android.mk b/Android.mk
index a3659d0..ef47728 100644
--- a/Android.mk
+++ b/Android.mk
@@ -26,6 +26,15 @@ LOCAL_SDK_VERSION := current
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+# If this is an unbundled build (to install seprately) then include
+# the libraries in the APK, otherwise just put them in /system/lib and
+# leave them out of the APK
+ifneq (,$(TARGET_BUILD_APPS))
+ LOCAL_JNI_SHARED_LIBRARIES := libjni_mosaic
+else
+ LOCAL_REQUIRED_MODULES := libjni_mosaic
+endif
+
include $(BUILD_PACKAGE)
include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/res/drawable-hdpi/ic_switch_pan.png b/res/drawable-hdpi/ic_switch_pan.png
new file mode 100644
index 0000000..c8161be
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_pan.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_pan.png b/res/drawable-mdpi/ic_switch_pan.png
new file mode 100644
index 0000000..e63b8e9
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_pan.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_pan.png b/res/drawable-xhdpi/ic_switch_pan.png
new file mode 100644
index 0000000..f17ce2f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_pan.png
Binary files differ
diff --git a/res/drawable/pano_direction_left_indicator.xml b/res/drawable/pano_direction_left_indicator.xml
new file mode 100644
index 0000000..a0bfb0a
--- /dev/null
+++ b/res/drawable/pano_direction_left_indicator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:drawable="@drawable/ic_pan_left_indicator" />
+ <item android:drawable="@drawable/ic_pan_left_indicator_fast" />
+</selector>
diff --git a/res/drawable/pano_direction_right_indicator.xml b/res/drawable/pano_direction_right_indicator.xml
new file mode 100644
index 0000000..c3ce377
--- /dev/null
+++ b/res/drawable/pano_direction_right_indicator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:drawable="@drawable/ic_pan_right_indicator" />
+ <item android:drawable="@drawable/ic_pan_right_indicator_fast" />
+</selector>
diff --git a/res/layout-land/camera_controls.xml b/res/layout-land/camera_controls.xml
index d177240..1495332 100644
--- a/res/layout-land/camera_controls.xml
+++ b/res/layout-land/camera_controls.xml
@@ -40,7 +40,7 @@
android:layout_gravity="right|top"
android:layout_marginRight="2dip" />
- <com.android.camera.ui.CameraSwitcher
+ <com.android.camera.ui.ModuleSwitcher
android:id="@+id/camera_switcher"
style="@style/SwitcherButton"
android:layout_gravity="right|bottom"
diff --git a/res/layout-land/pano_module_capture.xml b/res/layout-land/pano_module_capture.xml
new file mode 100644
index 0000000..cb76026
--- /dev/null
+++ b/res/layout-land/pano_module_capture.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/panorama_capture_layout"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <!-- The top bar with capture indication -->
+ <FrameLayout style="@style/PanoViewHorizontalBar">
+ <TextView android:id="@+id/pano_capture_indicator"
+ android:text="@string/pano_capture_indication"
+ android:textAppearance="?android:textAppearanceMedium"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_gravity="center"
+ android:layout_weight="6"
+ android:layout_width="match_parent"
+ android:layout_height="0dp">
+ <TextureView
+ android:id="@+id/pano_preview_textureview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ <View
+ android:id="@+id/pano_preview_area_border"
+ android:visibility="gone"
+ android:background="@drawable/ic_pan_border_fast"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </FrameLayout>
+
+ <!-- The bottom bar with progress bar and direction indicators -->
+ <RelativeLayout style="@style/PanoViewHorizontalBar">
+
+ <com.android.camera.PanoProgressBar
+ android:id="@+id/pano_pan_progress_bar"
+ android:visibility="gone"
+ android:src="@drawable/ic_pan_progression"
+ android:layout_centerInParent="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <ImageView
+ android:id="@+id/pano_pan_left_indicator"
+ android:src="@drawable/pano_direction_left_indicator"
+ android:visibility="gone"
+ android:layout_marginRight="5dp"
+ android:layout_toLeftOf="@id/pano_pan_progress_bar"
+ android:layout_centerVertical="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView
+ android:id="@+id/pano_pan_right_indicator"
+ android:src="@drawable/pano_direction_right_indicator"
+ android:visibility="gone"
+ android:layout_marginLeft="5dp"
+ android:layout_toRightOf="@id/pano_pan_progress_bar"
+ android:layout_centerVertical="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </RelativeLayout>
+
+ </LinearLayout>
+
+ <!-- The hint for "Too fast" text view -->
+ <TextView android:id="@+id/pano_capture_too_fast_textview"
+ android:text="@string/pano_too_fast_prompt"
+ android:textAppearance="?android:textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:visibility="gone" />
+</FrameLayout>
diff --git a/res/layout-land/pano_module_review.xml b/res/layout-land/pano_module_review.xml
new file mode 100644
index 0000000..002d47a
--- /dev/null
+++ b/res/layout-land/pano_module_review.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pano_review_layout"
+ android:visibility="gone"
+ android:orientation="vertical"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <TextView style="@style/PanoViewHorizontalBar"
+ android:text="@string/pano_review_rendering"
+ android:textAppearance="?android:textAppearanceMedium"
+ android:gravity="center" />
+
+ <ImageView android:id="@+id/pano_reviewarea"
+ android:scaleType="fitCenter"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/pano_mosaic_surface_height" />
+
+ <FrameLayout style="@style/PanoViewHorizontalBar">
+ <com.android.camera.PanoProgressBar
+ android:id="@+id/pano_saving_progress_bar"
+ android:src="@drawable/ic_pan_progression"
+ android:layout_gravity="center"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content" />
+
+ <com.android.camera.ui.RotateImageView android:id="@+id/pano_review_cancel_button"
+ style="@style/ReviewControlIcon"
+ android:contentDescription="@string/accessibility_review_cancel"
+ android:layout_gravity="center_vertical|right"
+ android:src="@drawable/ic_menu_cancel_holo_light" />
+ </FrameLayout>
+</LinearLayout>
diff --git a/res/layout-port/camera_controls.xml b/res/layout-port/camera_controls.xml
index 5f89830..03e896b 100644
--- a/res/layout-port/camera_controls.xml
+++ b/res/layout-port/camera_controls.xml
@@ -40,7 +40,7 @@
android:layout_marginBottom="2dip"
android:contentDescription="@string/accessibility_menu_button" />
- <com.android.camera.ui.CameraSwitcher
+ <com.android.camera.ui.ModuleSwitcher
android:id="@+id/camera_switcher"
style="@style/SwitcherButton"
android:layout_gravity="bottom|left"
@@ -67,4 +67,4 @@
android:scaleType="centerInside"
android:layout_gravity="top|right" />
-</com.android.camera.ui.CameraControls> \ No newline at end of file
+</com.android.camera.ui.CameraControls>
diff --git a/res/layout-port/pano_module_capture.xml b/res/layout-port/pano_module_capture.xml
new file mode 100644
index 0000000..57c00cd
--- /dev/null
+++ b/res/layout-port/pano_module_capture.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/panorama_capture_layout"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <!-- The top bar with capture indication -->
+ <FrameLayout style="@style/PanoViewHorizontalBar">
+ <TextView android:id="@+id/pano_capture_indicator"
+ android:text="@string/pano_capture_indication"
+ android:textAppearance="?android:textAppearanceMedium"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_gravity="center"
+ android:layout_weight="3"
+ android:layout_width="match_parent"
+ android:layout_height="0dp">
+ <TextureView
+ android:id="@+id/pano_preview_textureview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ <View
+ android:id="@+id/pano_preview_area_border"
+ android:visibility="gone"
+ android:background="@drawable/ic_pan_border_fast"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </FrameLayout>
+
+ <!-- The bottom bar with progress bar and direction indicators -->
+ <RelativeLayout style="@style/PanoViewHorizontalBar">
+
+ <com.android.camera.PanoProgressBar
+ android:id="@+id/pano_pan_progress_bar"
+ android:visibility="gone"
+ android:src="@drawable/ic_pan_progression"
+ android:layout_centerInParent="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <ImageView
+ android:id="@+id/pano_pan_left_indicator"
+ android:src="@drawable/pano_direction_left_indicator"
+ android:visibility="gone"
+ android:layout_marginRight="5dp"
+ android:layout_toLeftOf="@id/pano_pan_progress_bar"
+ android:layout_centerVertical="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView
+ android:id="@+id/pano_pan_right_indicator"
+ android:src="@drawable/pano_direction_right_indicator"
+ android:visibility="gone"
+ android:layout_marginLeft="5dp"
+ android:layout_toRightOf="@id/pano_pan_progress_bar"
+ android:layout_centerVertical="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </RelativeLayout>
+
+ </LinearLayout>
+
+ <!-- The hint for "Too fast" text view -->
+ <TextView android:id="@+id/pano_capture_too_fast_textview"
+ android:text="@string/pano_too_fast_prompt"
+ android:textAppearance="?android:textAppearanceMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:visibility="gone" />
+</FrameLayout>
diff --git a/res/layout-port/pano_module_review.xml b/res/layout-port/pano_module_review.xml
new file mode 100644
index 0000000..3c5eb2c
--- /dev/null
+++ b/res/layout-port/pano_module_review.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pano_review_layout"
+ android:visibility="gone"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
+ <TextView style="@style/PanoViewHorizontalBar"
+ android:text="@string/pano_review_rendering"
+ android:textAppearance="?android:textAppearanceMedium"
+ android:gravity="center" />
+
+ <ImageView android:id="@+id/pano_reviewarea"
+ android:scaleType="fitCenter"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1.5" />
+
+ <View style="@style/PanoViewHorizontalBar"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_horizontal|bottom">
+
+ <com.android.camera.PanoProgressBar
+ android:id="@+id/pano_saving_progress_bar"
+ android:src="@drawable/ic_pan_progression"
+ android:layout_gravity="center_horizontal"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content" />
+
+ <ImageView android:id="@id/pano_review_cancel_button"
+ style="@style/ReviewControlIcon"
+ android:contentDescription="@string/accessibility_review_cancel"
+ android:layout_gravity="center_horizontal"
+ android:src="@drawable/ic_menu_cancel_holo_light" />
+ </LinearLayout>
+</FrameLayout>
diff --git a/res/layout/panorama_module.xml b/res/layout/panorama_module.xml
new file mode 100644
index 0000000..64063a2
--- /dev/null
+++ b/res/layout/panorama_module.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pano_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <include layout="@layout/pano_module_capture" />
+ <include layout="@layout/pano_module_review" />
+ <include layout="@layout/camera_controls"
+ android:layout_gravity="center"
+ style="@style/CameraControls"/>
+</FrameLayout>
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
index 6ca7e91..69b524e 100644
--- a/res/values-land/styles.xml
+++ b/res/values-land/styles.xml
@@ -56,13 +56,6 @@
<item name="android:layout_marginBottom">13dp</item>
<item name="android:layout_marginTop">13dp</item>
</style>
- <style name="PanoViewHorizontalBar">
- <item name="android:background">#000000</item>
- <item name="android:alpha">1.0</item>
- <item name="android:layout_height">0dp</item>
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_weight">1.5</item>
- </style>
<style name="SettingPopupWindow_xlarge">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
diff --git a/res/values-port/styles.xml b/res/values-port/styles.xml
index 46871c6..830b0c7 100644
--- a/res/values-port/styles.xml
+++ b/res/values-port/styles.xml
@@ -47,13 +47,6 @@
<item name="android:layout_marginLeft">13dp</item>
<item name="android:layout_marginRight">13dp</item>
</style>
- <style name="PanoViewHorizontalBar">
- <item name="android:background">#000000</item>
- <item name="android:alpha">1.0</item>
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">0dp</item>
- <item name="android:layout_weight">1</item>
- </style>
<style name="SettingPopupWindow_xlarge">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2fd7dbd..23a5f5c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -264,4 +264,11 @@
<item name="android:drawablePadding">8dp</item>
<item name="android:background">@drawable/bg_pressed</item>
</style>
+ <style name="PanoViewHorizontalBar">
+ <item name="android:background">#000000</item>
+ <item name="android:alpha">1.0</item>
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">0dp</item>
+ <item name="android:layout_weight">1.5</item>
+ </style>
</resources>
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 47a964f..ec8fc95 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -53,6 +53,8 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.ShareActionProvider;
+import com.android.camera.app.AppManagerFactory;
+import com.android.camera.app.PanoramaStitchingManager;
import com.android.camera.data.CameraDataAdapter;
import com.android.camera.data.CameraPreviewData;
import com.android.camera.data.FixedFirstDataAdapter;
@@ -61,8 +63,7 @@ import com.android.camera.data.LocalData;
import com.android.camera.data.LocalDataAdapter;
import com.android.camera.data.MediaDetails;
import com.android.camera.data.SimpleViewData;
-import com.android.camera.ui.CameraSwitcher;
-import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
+import com.android.camera.ui.ModuleSwitcher;
import com.android.camera.ui.DetailsDialog;
import com.android.camera.ui.FilmStripView;
import com.android.camera.util.ApiHelper;
@@ -72,7 +73,7 @@ import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
import com.android.camera2.R;
public class CameraActivity extends Activity
- implements CameraSwitchListener {
+ implements ModuleSwitcher.ModuleSwitchListener {
private static final String TAG = "CAM_Activity";
@@ -128,7 +129,6 @@ public class CameraActivity extends Activity
private boolean mSecureCamera;
// This is a hack to speed up the start of SecureCamera.
private static boolean sFirstStartAfterScreenOn = true;
- private boolean mShowCameraPreview;
private int mLastRawOrientation;
private MyOrientationEventListener mOrientationListener;
private Handler mMainHandler;
@@ -643,7 +643,8 @@ public class CameraActivity extends Activity
mAboveFilmstripControlLayout.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
- mPanoramaManager = new PanoramaStitchingManager(CameraActivity.this);
+ mPanoramaManager = AppManagerFactory.getInstance(this)
+ .getPanoramaStitchingManager();
mPanoramaManager.addTaskListener(mStitchingListener);
LayoutInflater inflater = getLayoutInflater();
View rootLayout = inflater.inflate(R.layout.camera, null, false);
@@ -670,26 +671,26 @@ public class CameraActivity extends Activity
int moduleIndex = -1;
if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
|| MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
- moduleIndex = CameraSwitcher.VIDEO_MODULE_INDEX;
+ moduleIndex = ModuleSwitcher.VIDEO_MODULE_INDEX;
} else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction())
|| MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent()
.getAction())
|| MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction())
|| MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) {
- moduleIndex = CameraSwitcher.PHOTO_MODULE_INDEX;
+ moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
} else {
// If the activity has not been started using an explicit intent,
// read the module index from the last time the user changed modes
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
moduleIndex = prefs.getInt(PREF_STARTUP_MODULE_INDEX, -1);
if (moduleIndex < 0) {
- moduleIndex = CameraSwitcher.PHOTO_MODULE_INDEX;
+ moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
}
}
- setModuleFromIndex(moduleIndex);
- mCurrentModule.init(this, mCameraModuleRootView);
mOrientationListener = new MyOrientationEventListener(this);
+ setModuleFromIndex(moduleIndex);
+ mCurrentModule.init(this, mCameraModuleRootView);
mMainHandler = new Handler(getMainLooper());
if (!mSecureCamera) {
@@ -788,9 +789,6 @@ public class CameraActivity extends Activity
|| keyCode == KeyEvent.KEYCODE_MENU) {
if (event.isLongPress()) return true;
}
- if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
- return true;
- }
return super.onKeyDown(keyCode, event);
}
@@ -798,9 +796,6 @@ public class CameraActivity extends Activity
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mCurrentModule.onKeyUp(keyCode, event)) return true;
- if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) {
- return true;
- }
return super.onKeyUp(keyCode, event);
}
@@ -887,7 +882,7 @@ public class CameraActivity extends Activity
}
@Override
- public void onCameraSelected(int moduleIndex) {
+ public void onModuleSelected(int moduleIndex) {
if (mCurrentModuleIndex == moduleIndex) return;
CameraHolder.instance().keep();
@@ -913,15 +908,26 @@ public class CameraActivity extends Activity
private void setModuleFromIndex(int moduleIndex) {
mCurrentModuleIndex = moduleIndex;
switch (moduleIndex) {
- case CameraSwitcher.VIDEO_MODULE_INDEX:
+ case ModuleSwitcher.VIDEO_MODULE_INDEX: {
mCurrentModule = new VideoModule();
break;
- case CameraSwitcher.PHOTO_MODULE_INDEX:
+ }
+
+ case ModuleSwitcher.PHOTO_MODULE_INDEX: {
mCurrentModule = new PhotoModule();
break;
- case CameraSwitcher.LIGHTCYCLE_MODULE_INDEX:
+ }
+
+ case ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX: {
+ mCurrentModule = new WideAnglePanoramaModule();
+ break;
+ }
+
+ case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX: {
mCurrentModule = PhotoSphereHelper.createPanoramaModule();
break;
+ }
+
default:
break;
}
diff --git a/src/com/android/camera/Mosaic.java b/src/com/android/camera/Mosaic.java
new file mode 100644
index 0000000..b1d10c0
--- /dev/null
+++ b/src/com/android/camera/Mosaic.java
@@ -0,0 +1,206 @@
+/*
+ * 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;
+
+/**
+ * The Java interface to JNI calls regarding mosaic stitching.
+ *
+ * A high-level usage is:
+ *
+ * Mosaic mosaic = new Mosaic();
+ * mosaic.setSourceImageDimensions(width, height);
+ * mosaic.reset(blendType);
+ *
+ * while ((pixels = hasNextImage()) != null) {
+ * mosaic.setSourceImage(pixels);
+ * }
+ *
+ * mosaic.createMosaic(highRes);
+ * byte[] result = mosaic.getFinalMosaic();
+ *
+ */
+public class Mosaic {
+ /**
+ * In this mode, the images are stitched together in the same spatial arrangement as acquired
+ * i.e. if the user follows a curvy trajectory, the image boundary of the resulting mosaic will
+ * be curved in the same manner. This mode is useful if the user wants to capture a mosaic as
+ * if "painting" the scene using the smart-phone device and does not want any corrective warps
+ * to distort the captured images.
+ */
+ public static final int BLENDTYPE_FULL = 0;
+
+ /**
+ * This mode is the same as BLENDTYPE_FULL except that the resulting mosaic is rotated
+ * to balance the first and last images to be approximately at the same vertical offset in the
+ * output mosaic. This is useful when acquiring a mosaic by a typical panning-like motion to
+ * remove a one-sided curve in the mosaic (typically due to the camera not staying horizontal
+ * during the video capture) and convert it to a more symmetrical "smiley-face" like output.
+ */
+ public static final int BLENDTYPE_PAN = 1;
+
+ /**
+ * This mode compensates for typical "smiley-face" like output in longer mosaics and creates
+ * a rectangular mosaic with minimal black borders (by unwrapping the mosaic onto an imaginary
+ * cylinder). If the user follows a curved trajectory (instead of a perfect panning trajectory),
+ * the resulting mosaic here may suffer from some image distortions in trying to map the
+ * trajectory to a cylinder.
+ */
+ public static final int BLENDTYPE_CYLINDERPAN = 2;
+
+ /**
+ * This mode is basically BLENDTYPE_CYLINDERPAN plus doing a rectangle cropping before returning
+ * the mosaic. The mode is useful for making the resulting mosaic have a rectangle shape.
+ */
+ public static final int BLENDTYPE_HORIZONTAL =3;
+
+ /**
+ * This strip type will use the default thin strips where the strips are
+ * spaced according to the image capture rate.
+ */
+ public static final int STRIPTYPE_THIN = 0;
+
+ /**
+ * This strip type will use wider strips for blending. The strip separation
+ * is controlled by a threshold on the native side. Since the strips are
+ * wider, there is an additional cross-fade blending step to make the seam
+ * boundaries smoother. Since this mode uses lesser image frames, it is
+ * computationally more efficient than the thin strip mode.
+ */
+ public static final int STRIPTYPE_WIDE = 1;
+
+ /**
+ * Return flags returned by createMosaic() are one of the following.
+ */
+ public static final int MOSAIC_RET_OK = 1;
+ public static final int MOSAIC_RET_ERROR = -1;
+ public static final int MOSAIC_RET_CANCELLED = -2;
+ public static final int MOSAIC_RET_LOW_TEXTURE = -3;
+ public static final int MOSAIC_RET_FEW_INLIERS = 2;
+
+
+ static {
+ System.loadLibrary("jni_mosaic");
+ }
+
+ /**
+ * Allocate memory for the image frames at the given resolution.
+ *
+ * @param width width of the input frames in pixels
+ * @param height height of the input frames in pixels
+ */
+ public native void allocateMosaicMemory(int width, int height);
+
+ /**
+ * Free memory allocated by allocateMosaicMemory.
+ *
+ */
+ public native void freeMosaicMemory();
+
+ /**
+ * Pass the input image frame to the native layer. Each time the a new
+ * source image t is set, the transformation matrix from the first source
+ * image to t is computed and returned.
+ *
+ * @param pixels source image of NV21 format.
+ * @return Float array of length 11; first 9 entries correspond to the 3x3
+ * transformation matrix between the first frame and the passed frame;
+ * the 10th entry is the number of the passed frame, where the counting
+ * starts from 1; and the 11th entry is the returning code, whose value
+ * is one of those MOSAIC_RET_* returning flags defined above.
+ */
+ public native float[] setSourceImage(byte[] pixels);
+
+ /**
+ * This is an alternative to the setSourceImage function above. This should
+ * be called when the image data is already on the native side in a fixed
+ * byte array. In implementation, this array is filled by the GL thread
+ * using glReadPixels directly from GPU memory (where it is accessed by
+ * an associated SurfaceTexture).
+ *
+ * @return Float array of length 11; first 9 entries correspond to the 3x3
+ * transformation matrix between the first frame and the passed frame;
+ * the 10th entry is the number of the passed frame, where the counting
+ * starts from 1; and the 11th entry is the returning code, whose value
+ * is one of those MOSAIC_RET_* returning flags defined above.
+ */
+ public native float[] setSourceImageFromGPU();
+
+ /**
+ * Set the type of blending.
+ *
+ * @param type the blending type defined in the class. {BLENDTYPE_FULL,
+ * BLENDTYPE_PAN, BLENDTYPE_CYLINDERPAN, BLENDTYPE_HORIZONTAL}
+ */
+ public native void setBlendingType(int type);
+
+ /**
+ * Set the type of strips to use for blending.
+ * @param type the blending strip type to use {STRIPTYPE_THIN,
+ * STRIPTYPE_WIDE}.
+ */
+ public native void setStripType(int type);
+
+ /**
+ * Tell the native layer to create the final mosaic after all the input frame
+ * data have been collected.
+ * The case of generating high-resolution mosaic may take dozens of seconds to finish.
+ *
+ * @param value True means generating a high-resolution mosaic -
+ * which is based on the original images set in setSourceImage().
+ * False means generating a low-resolution version -
+ * which is based on 1/4 downscaled images from the original images.
+ * @return Returns a status code suggesting if the mosaic building was
+ * successful, in error, or was cancelled by the user.
+ */
+ public native int createMosaic(boolean value);
+
+ /**
+ * Get the data for the created mosaic.
+ *
+ * @return Returns an integer array which contains the final mosaic in the ARGB_8888 format.
+ * The first MosaicWidth*MosaicHeight values contain the image data, followed by 2
+ * integers corresponding to the values MosaicWidth and MosaicHeight respectively.
+ */
+ public native int[] getFinalMosaic();
+
+ /**
+ * Get the data for the created mosaic.
+ *
+ * @return Returns a byte array which contains the final mosaic in the NV21 format.
+ * The first MosaicWidth*MosaicHeight*1.5 values contain the image data, followed by
+ * 8 bytes which pack the MosaicWidth and MosaicHeight integers into 4 bytes each
+ * respectively.
+ */
+ public native byte[] getFinalMosaicNV21();
+
+ /**
+ * Reset the state of the frame arrays which maintain the captured frame data.
+ * Also re-initializes the native mosaic object to make it ready for capturing a new mosaic.
+ */
+ public native void reset();
+
+ /**
+ * Get the progress status of the mosaic computation process.
+ * @param hires Boolean flag to select whether to report progress of the
+ * low-res or high-res mosaicer.
+ * @param cancelComputation Boolean flag to allow cancelling the
+ * mosaic computation when needed from the GUI end.
+ * @return Returns a number from 0-100 where 50 denotes that the mosaic
+ * computation is 50% done.
+ */
+ public native int reportProgress(boolean hires, boolean cancelComputation);
+}
diff --git a/src/com/android/camera/MosaicFrameProcessor.java b/src/com/android/camera/MosaicFrameProcessor.java
new file mode 100644
index 0000000..cb30534
--- /dev/null
+++ b/src/com/android/camera/MosaicFrameProcessor.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.util.Log;
+
+/**
+ * A singleton to handle the processing of each frame by {@link Mosaic}.
+ */
+public class MosaicFrameProcessor {
+ private static final String TAG = "MosaicFrameProcessor";
+ private static final int NUM_FRAMES_IN_BUFFER = 2;
+ private static final int MAX_NUMBER_OF_FRAMES = 100;
+ private static final int MOSAIC_RET_CODE_INDEX = 10;
+ private static final int FRAME_COUNT_INDEX = 9;
+ private static final int X_COORD_INDEX = 2;
+ private static final int Y_COORD_INDEX = 5;
+ private static final int HR_TO_LR_DOWNSAMPLE_FACTOR = 4;
+ private static final int WINDOW_SIZE = 3;
+
+ private Mosaic mMosaicer;
+ private boolean mIsMosaicMemoryAllocated = false;
+ private float mTranslationLastX;
+ private float mTranslationLastY;
+
+ private int mFillIn = 0;
+ private int mTotalFrameCount = 0;
+ private int mLastProcessFrameIdx = -1;
+ private int mCurrProcessFrameIdx = -1;
+ private boolean mFirstRun;
+
+ // Panning rate is in unit of percentage of image content translation per
+ // frame. Use moving average to calculate the panning rate.
+ private float mPanningRateX;
+ private float mPanningRateY;
+
+ private float[] mDeltaX = new float[WINDOW_SIZE];
+ private float[] mDeltaY = new float[WINDOW_SIZE];
+ private int mOldestIdx = 0;
+ private float mTotalTranslationX = 0f;
+ private float mTotalTranslationY = 0f;
+
+ private ProgressListener mProgressListener;
+
+ private int mPreviewWidth;
+ private int mPreviewHeight;
+ private int mPreviewBufferSize;
+
+ private static MosaicFrameProcessor sMosaicFrameProcessor; // singleton
+
+ public interface ProgressListener {
+ public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
+ float progressX, float progressY);
+ }
+
+ public static MosaicFrameProcessor getInstance() {
+ if (sMosaicFrameProcessor == null) {
+ sMosaicFrameProcessor = new MosaicFrameProcessor();
+ }
+ return sMosaicFrameProcessor;
+ }
+
+ private MosaicFrameProcessor() {
+ mMosaicer = new Mosaic();
+ }
+
+ public void setProgressListener(ProgressListener listener) {
+ mProgressListener = listener;
+ }
+
+ public int reportProgress(boolean hires, boolean cancel) {
+ return mMosaicer.reportProgress(hires, cancel);
+ }
+
+ public void initialize(int previewWidth, int previewHeight, int bufSize) {
+ mPreviewWidth = previewWidth;
+ mPreviewHeight = previewHeight;
+ mPreviewBufferSize = bufSize;
+ setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize);
+ setStripType(Mosaic.STRIPTYPE_WIDE);
+ // no need to call reset() here. reset() should be called by the client
+ // after this initialization before calling other methods of this object.
+ }
+
+ public void clear() {
+ if (mIsMosaicMemoryAllocated) {
+ mMosaicer.freeMosaicMemory();
+ mIsMosaicMemoryAllocated = false;
+ }
+ synchronized (this) {
+ notify();
+ }
+ }
+
+ public boolean isMosaicMemoryAllocated() {
+ return mIsMosaicMemoryAllocated;
+ }
+
+ public void setStripType(int type) {
+ mMosaicer.setStripType(type);
+ }
+
+ private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {
+ Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize);
+
+ if (mIsMosaicMemoryAllocated) throw new RuntimeException("MosaicFrameProcessor in use!");
+ mIsMosaicMemoryAllocated = true;
+ mMosaicer.allocateMosaicMemory(previewWidth, previewHeight);
+ }
+
+ public void reset() {
+ // reset() can be called even if MosaicFrameProcessor is not initialized.
+ // Only counters will be changed.
+ mFirstRun = true;
+ mTotalFrameCount = 0;
+ mFillIn = 0;
+ mTotalTranslationX = 0;
+ mTranslationLastX = 0;
+ mTotalTranslationY = 0;
+ mTranslationLastY = 0;
+ mPanningRateX = 0;
+ mPanningRateY = 0;
+ mLastProcessFrameIdx = -1;
+ mCurrProcessFrameIdx = -1;
+ for (int i = 0; i < WINDOW_SIZE; ++i) {
+ mDeltaX[i] = 0f;
+ mDeltaY[i] = 0f;
+ }
+ mMosaicer.reset();
+ }
+
+ public int createMosaic(boolean highRes) {
+ return mMosaicer.createMosaic(highRes);
+ }
+
+ public byte[] getFinalMosaicNV21() {
+ return mMosaicer.getFinalMosaicNV21();
+ }
+
+ // Processes the last filled image frame through the mosaicer and
+ // updates the UI to show progress.
+ // When done, processes and displays the final mosaic.
+ public void processFrame() {
+ if (!mIsMosaicMemoryAllocated) {
+ // clear() is called and buffers are cleared, stop computation.
+ // This can happen when the onPause() is called in the activity, but still some frames
+ // are not processed yet and thus the callback may be invoked.
+ return;
+ }
+
+ mCurrProcessFrameIdx = mFillIn;
+ mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER);
+
+ // Check that we are trying to process a frame different from the
+ // last one processed (useful if this class was running asynchronously)
+ if (mCurrProcessFrameIdx != mLastProcessFrameIdx) {
+ mLastProcessFrameIdx = mCurrProcessFrameIdx;
+
+ // TODO: make the termination condition regarding reaching
+ // MAX_NUMBER_OF_FRAMES solely determined in the library.
+ if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) {
+ // If we are still collecting new frames for the current mosaic,
+ // process the new frame.
+ calculateTranslationRate();
+
+ // Publish progress of the ongoing processing
+ if (mProgressListener != null) {
+ mProgressListener.onProgress(false, mPanningRateX, mPanningRateY,
+ mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
+ mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
+ }
+ } else {
+ if (mProgressListener != null) {
+ mProgressListener.onProgress(true, mPanningRateX, mPanningRateY,
+ mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
+ mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
+ }
+ }
+ }
+ }
+
+ public void calculateTranslationRate() {
+ float[] frameData = mMosaicer.setSourceImageFromGPU();
+ int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX];
+ mTotalFrameCount = (int) frameData[FRAME_COUNT_INDEX];
+ float translationCurrX = frameData[X_COORD_INDEX];
+ float translationCurrY = frameData[Y_COORD_INDEX];
+
+ if (mFirstRun) {
+ // First time: no need to update delta values.
+ mTranslationLastX = translationCurrX;
+ mTranslationLastY = translationCurrY;
+ mFirstRun = false;
+ return;
+ }
+
+ // Moving average: remove the oldest translation/deltaTime and
+ // add the newest translation/deltaTime in
+ int idx = mOldestIdx;
+ mTotalTranslationX -= mDeltaX[idx];
+ mTotalTranslationY -= mDeltaY[idx];
+ mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX);
+ mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY);
+ mTotalTranslationX += mDeltaX[idx];
+ mTotalTranslationY += mDeltaY[idx];
+
+ // The panning rate is measured as the rate of the translation percentage in
+ // image width/height. Take the horizontal panning rate for example, the image width
+ // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR).
+ // To get the horizontal translation percentage, the horizontal translation,
+ // (translationCurrX - mTranslationLastX), is divided by the
+ // image width. We then get the rate by dividing the translation percentage with the
+ // number of frames.
+ mPanningRateX = mTotalTranslationX /
+ (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;
+ mPanningRateY = mTotalTranslationY /
+ (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;
+
+ mTranslationLastX = translationCurrX;
+ mTranslationLastY = translationCurrY;
+ mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE;
+ }
+}
diff --git a/src/com/android/camera/MosaicPreviewRenderer.java b/src/com/android/camera/MosaicPreviewRenderer.java
new file mode 100644
index 0000000..e8c02db
--- /dev/null
+++ b/src/com/android/camera/MosaicPreviewRenderer.java
@@ -0,0 +1,190 @@
+/*
+ * 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;
+
+import android.annotation.TargetApi;
+import android.graphics.SurfaceTexture;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+
+import com.android.camera.util.ApiHelper;
+
+import javax.microedition.khronos.opengles.GL10;
+
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture
+public class MosaicPreviewRenderer {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "CAM_MosaicPreviewRenderer";
+
+ private int mWidth; // width of the view in UI
+ private int mHeight; // height of the view in UI
+
+ private boolean mIsLandscape = true;
+ private final float[] mTransformMatrix = new float[16];
+
+ private ConditionVariable mEglThreadBlockVar = new ConditionVariable();
+ private HandlerThread mEglThread;
+ private MyHandler mHandler;
+ private SurfaceTextureRenderer mSTRenderer;
+
+ private SurfaceTexture mInputSurfaceTexture;
+
+ private class MyHandler extends Handler {
+ public static final int MSG_INIT_SYNC = 0;
+ public static final int MSG_SHOW_PREVIEW_FRAME_SYNC = 1;
+ public static final int MSG_SHOW_PREVIEW_FRAME = 2;
+ public static final int MSG_ALIGN_FRAME_SYNC = 3;
+ public static final int MSG_RELEASE = 4;
+
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INIT_SYNC:
+ doInit();
+ mEglThreadBlockVar.open();
+ break;
+ case MSG_SHOW_PREVIEW_FRAME_SYNC:
+ doShowPreviewFrame();
+ mEglThreadBlockVar.open();
+ break;
+ case MSG_SHOW_PREVIEW_FRAME:
+ doShowPreviewFrame();
+ break;
+ case MSG_ALIGN_FRAME_SYNC:
+ doAlignFrame();
+ mEglThreadBlockVar.open();
+ break;
+ case MSG_RELEASE:
+ doRelease();
+ mEglThreadBlockVar.open();
+ break;
+ }
+ }
+
+ private void doAlignFrame() {
+ mInputSurfaceTexture.updateTexImage();
+ mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
+
+ MosaicRenderer.setWarping(true);
+ // Call preprocess to render it to low-res and high-res RGB textures.
+ MosaicRenderer.preprocess(mTransformMatrix);
+ // Now, transfer the textures from GPU to CPU memory for processing
+ MosaicRenderer.transferGPUtoCPU();
+ MosaicRenderer.updateMatrix();
+ MosaicRenderer.step();
+ }
+
+ private void doShowPreviewFrame() {
+ mInputSurfaceTexture.updateTexImage();
+ mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
+
+ MosaicRenderer.setWarping(false);
+ // Call preprocess to render it to low-res and high-res RGB textures.
+ MosaicRenderer.preprocess(mTransformMatrix);
+ MosaicRenderer.updateMatrix();
+ MosaicRenderer.step();
+ }
+
+ private void doInit() {
+ mInputSurfaceTexture = new SurfaceTexture(MosaicRenderer.init());
+ MosaicRenderer.reset(mWidth, mHeight, mIsLandscape);
+ }
+
+ private void doRelease() {
+ releaseSurfaceTexture(mInputSurfaceTexture);
+ mEglThread.quit();
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void releaseSurfaceTexture(SurfaceTexture st) {
+ if (ApiHelper.HAS_RELEASE_SURFACE_TEXTURE) {
+ st.release();
+ }
+ }
+
+ // Should be called from other thread.
+ public void sendMessageSync(int msg) {
+ mEglThreadBlockVar.close();
+ sendEmptyMessage(msg);
+ mEglThreadBlockVar.block();
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param tex The {@link SurfaceTexture} for the final UI output.
+ * @param w The width of the UI view.
+ * @param h The height of the UI view.
+ * @param isLandscape The UI orientation. {@code true} if in landscape,
+ * false if in portrait.
+ */
+ public MosaicPreviewRenderer(SurfaceTexture tex, int w, int h, boolean isLandscape) {
+ mIsLandscape = isLandscape;
+
+ mEglThread = new HandlerThread("PanoramaRealtimeRenderer");
+ mEglThread.start();
+ mHandler = new MyHandler(mEglThread.getLooper());
+ mWidth = w;
+ mHeight = h;
+
+ SurfaceTextureRenderer.FrameDrawer dummy = new SurfaceTextureRenderer.FrameDrawer() {
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ // nothing, we have our draw functions.
+ }
+ };
+ mSTRenderer = new SurfaceTextureRenderer(tex, mHandler, dummy);
+
+ // We need to sync this because the generation of surface texture for input is
+ // done here and the client will continue with the assumption that the
+ // generation is completed.
+ mHandler.sendMessageSync(MyHandler.MSG_INIT_SYNC);
+ }
+
+ public void release() {
+ mSTRenderer.release();
+ mHandler.sendMessageSync(MyHandler.MSG_RELEASE);
+ }
+
+ public void showPreviewFrameSync() {
+ mHandler.sendMessageSync(MyHandler.MSG_SHOW_PREVIEW_FRAME_SYNC);
+ mSTRenderer.draw(true);
+ }
+
+ public void showPreviewFrame() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_SHOW_PREVIEW_FRAME);
+ mSTRenderer.draw(false);
+ }
+
+ public void alignFrameSync() {
+ mHandler.sendMessageSync(MyHandler.MSG_ALIGN_FRAME_SYNC);
+ mSTRenderer.draw(true);
+ }
+
+ public SurfaceTexture getInputSurfaceTexture() {
+ return mInputSurfaceTexture;
+ }
+}
diff --git a/src/com/android/camera/MosaicRenderer.java b/src/com/android/camera/MosaicRenderer.java
new file mode 100644
index 0000000..c50ca0d
--- /dev/null
+++ b/src/com/android/camera/MosaicRenderer.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * The Java interface to JNI calls regarding mosaic preview rendering.
+ *
+ */
+public class MosaicRenderer
+{
+ static
+ {
+ System.loadLibrary("jni_mosaic");
+ }
+
+ /**
+ * Function to be called in onSurfaceCreated() to initialize
+ * the GL context, load and link the shaders and create the
+ * program. Returns a texture ID to be used for SurfaceTexture.
+ *
+ * @return textureID the texture ID of the newly generated texture to
+ * be assigned to the SurfaceTexture object.
+ */
+ public static native int init();
+
+ /**
+ * Pass the drawing surface's width and height to initialize the
+ * renderer viewports and FBO dimensions.
+ *
+ * @param width width of the drawing surface in pixels.
+ * @param height height of the drawing surface in pixels.
+ * @param isLandscapeOrientation is the orientation of the activity layout in landscape.
+ */
+ public static native void reset(int width, int height, boolean isLandscapeOrientation);
+
+ /**
+ * Calling this function will render the SurfaceTexture to a new 2D texture
+ * using the provided STMatrix.
+ *
+ * @param stMatrix texture coordinate transform matrix obtained from the
+ * Surface texture
+ */
+ public static native void preprocess(float[] stMatrix);
+
+ /**
+ * This function calls glReadPixels to transfer both the low-res and high-res
+ * data from the GPU memory to the CPU memory for further processing by the
+ * mosaicing library.
+ */
+ public static native void transferGPUtoCPU();
+
+ /**
+ * Function to be called in onDrawFrame() to update the screen with
+ * the new frame data.
+ */
+ public static native void step();
+
+ /**
+ * Call this function when a new low-res frame has been processed by
+ * the mosaicing library. This will tell the renderer library to
+ * update its texture and warping transformation. Any calls to step()
+ * after this call will use the new image frame and transformation data.
+ */
+ public static native void updateMatrix();
+
+ /**
+ * This function allows toggling between showing the input image data
+ * (without applying any warp) and the warped image data. For running
+ * the renderer as a viewfinder, we set the flag to false. To see the
+ * preview mosaic, we set the flag to true.
+ *
+ * @param flag boolean flag to set the warping to true or false.
+ */
+ public static native void setWarping(boolean flag);
+}
diff --git a/src/com/android/camera/PanoProgressBar.java b/src/com/android/camera/PanoProgressBar.java
new file mode 100644
index 0000000..8dfb366
--- /dev/null
+++ b/src/com/android/camera/PanoProgressBar.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+class PanoProgressBar extends ImageView {
+ @SuppressWarnings("unused")
+ private static final String TAG = "PanoProgressBar";
+ public static final int DIRECTION_NONE = 0;
+ public static final int DIRECTION_LEFT = 1;
+ public static final int DIRECTION_RIGHT = 2;
+ private float mProgress = 0;
+ private float mMaxProgress = 0;
+ private float mLeftMostProgress = 0;
+ private float mRightMostProgress = 0;
+ private float mProgressOffset = 0;
+ private float mIndicatorWidth = 0;
+ private int mDirection = 0;
+ private final Paint mBackgroundPaint = new Paint();
+ private final Paint mDoneAreaPaint = new Paint();
+ private final Paint mIndicatorPaint = new Paint();
+ private float mWidth;
+ private float mHeight;
+ private RectF mDrawBounds;
+ private OnDirectionChangeListener mListener = null;
+
+ public interface OnDirectionChangeListener {
+ public void onDirectionChange(int direction);
+ }
+
+ public PanoProgressBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDoneAreaPaint.setStyle(Paint.Style.FILL);
+ mDoneAreaPaint.setAlpha(0xff);
+
+ mBackgroundPaint.setStyle(Paint.Style.FILL);
+ mBackgroundPaint.setAlpha(0xff);
+
+ mIndicatorPaint.setStyle(Paint.Style.FILL);
+ mIndicatorPaint.setAlpha(0xff);
+
+ mDrawBounds = new RectF();
+ }
+
+ public void setOnDirectionChangeListener(OnDirectionChangeListener l) {
+ mListener = l;
+ }
+
+ private void setDirection(int direction) {
+ if (mDirection != direction) {
+ mDirection = direction;
+ if (mListener != null) {
+ mListener.onDirectionChange(mDirection);
+ }
+ invalidate();
+ }
+ }
+
+ public int getDirection() {
+ return mDirection;
+ }
+
+ @Override
+ public void setBackgroundColor(int color) {
+ mBackgroundPaint.setColor(color);
+ invalidate();
+ }
+
+ public void setDoneColor(int color) {
+ mDoneAreaPaint.setColor(color);
+ invalidate();
+ }
+
+ public void setIndicatorColor(int color) {
+ mIndicatorPaint.setColor(color);
+ invalidate();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWidth = w;
+ mHeight = h;
+ mDrawBounds.set(0, 0, mWidth, mHeight);
+ }
+
+ public void setMaxProgress(int progress) {
+ mMaxProgress = progress;
+ }
+
+ public void setIndicatorWidth(float w) {
+ mIndicatorWidth = w;
+ invalidate();
+ }
+
+ public void setRightIncreasing(boolean rightIncreasing) {
+ if (rightIncreasing) {
+ mLeftMostProgress = 0;
+ mRightMostProgress = 0;
+ mProgressOffset = 0;
+ setDirection(DIRECTION_RIGHT);
+ } else {
+ mLeftMostProgress = mWidth;
+ mRightMostProgress = mWidth;
+ mProgressOffset = mWidth;
+ setDirection(DIRECTION_LEFT);
+ }
+ invalidate();
+ }
+
+ public void setProgress(int progress) {
+ // The panning direction will be decided after user pan more than 10 degrees in one
+ // direction.
+ if (mDirection == DIRECTION_NONE) {
+ if (progress > 10) {
+ setRightIncreasing(true);
+ } else if (progress < -10) {
+ setRightIncreasing(false);
+ }
+ }
+ // mDirection might be modified by setRightIncreasing() above. Need to check again.
+ if (mDirection != DIRECTION_NONE) {
+ mProgress = progress * mWidth / mMaxProgress + mProgressOffset;
+ // value bounds.
+ mProgress = Math.min(mWidth, Math.max(0, mProgress));
+ if (mDirection == DIRECTION_RIGHT) {
+ // The right most progress is adjusted.
+ mRightMostProgress = Math.max(mRightMostProgress, mProgress);
+ }
+ if (mDirection == DIRECTION_LEFT) {
+ // The left most progress is adjusted.
+ mLeftMostProgress = Math.min(mLeftMostProgress, mProgress);
+ }
+ invalidate();
+ }
+ }
+
+ public void reset() {
+ mProgress = 0;
+ mProgressOffset = 0;
+ setDirection(DIRECTION_NONE);
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // the background
+ canvas.drawRect(mDrawBounds, mBackgroundPaint);
+ if (mDirection != DIRECTION_NONE) {
+ // the progress area
+ canvas.drawRect(mLeftMostProgress, mDrawBounds.top, mRightMostProgress,
+ mDrawBounds.bottom, mDoneAreaPaint);
+ // the indication bar
+ float l;
+ float r;
+ if (mDirection == DIRECTION_RIGHT) {
+ l = Math.max(mProgress - mIndicatorWidth, 0f);
+ r = mProgress;
+ } else {
+ l = mProgress;
+ r = Math.min(mProgress + mIndicatorWidth, mWidth);
+ }
+ canvas.drawRect(l, mDrawBounds.top, r, mDrawBounds.bottom, mIndicatorPaint);
+ }
+
+ // draw the mask image on the top for shaping.
+ super.onDraw(canvas);
+ }
+}
diff --git a/src/com/android/camera/PanoUtil.java b/src/com/android/camera/PanoUtil.java
new file mode 100644
index 0000000..e50eacc
--- /dev/null
+++ b/src/com/android/camera/PanoUtil.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class PanoUtil {
+ public static String createName(String format, long dateTaken) {
+ Date date = new Date(dateTaken);
+ SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+ return dateFormat.format(date);
+ }
+
+ // TODO: Add comments about the range of these two arguments.
+ public static double calculateDifferenceBetweenAngles(double firstAngle,
+ double secondAngle) {
+ double difference1 = (secondAngle - firstAngle) % 360;
+ if (difference1 < 0) {
+ difference1 += 360;
+ }
+
+ double difference2 = (firstAngle - secondAngle) % 360;
+ if (difference2 < 0) {
+ difference2 += 360;
+ }
+
+ return Math.min(difference1, difference2);
+ }
+
+ public static void decodeYUV420SPQuarterRes(int[] rgb, byte[] yuv420sp, int width, int height) {
+ final int frameSize = width * height;
+
+ for (int j = 0, ypd = 0; j < height; j += 4) {
+ int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
+ for (int i = 0; i < width; i += 4, ypd++) {
+ int y = (0xff & (yuv420sp[j * width + i])) - 16;
+ if (y < 0) {
+ y = 0;
+ }
+ if ((i & 1) == 0) {
+ v = (0xff & yuv420sp[uvp++]) - 128;
+ u = (0xff & yuv420sp[uvp++]) - 128;
+ uvp += 2; // Skip the UV values for the 4 pixels skipped in between
+ }
+ int y1192 = 1192 * y;
+ int r = (y1192 + 1634 * v);
+ int g = (y1192 - 833 * v - 400 * u);
+ int b = (y1192 + 2066 * u);
+
+ if (r < 0) {
+ r = 0;
+ } else if (r > 262143) {
+ r = 262143;
+ }
+ if (g < 0) {
+ g = 0;
+ } else if (g > 262143) {
+ g = 262143;
+ }
+ if (b < 0) {
+ b = 0;
+ } else if (b > 262143) {
+ b = 262143;
+ }
+
+ rgb[ypd] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) |
+ ((b >> 10) & 0xff);
+ }
+ }
+ }
+}
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index ea0037d..73840ab 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -26,10 +26,7 @@ import android.graphics.SurfaceTexture;
import android.graphics.drawable.ColorDrawable;
import android.hardware.Camera;
import android.hardware.Camera.Face;
-import android.hardware.Camera.Size;
import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Message;
import android.util.Log;
import android.view.Gravity;
import android.view.TextureView;
@@ -45,12 +42,11 @@ import android.widget.Toast;
import com.android.camera.CameraPreference.OnPreferenceChangedListener;
import com.android.camera.FocusOverlayManager.FocusUI;
+import com.android.camera.ui.ModuleSwitcher;
import com.android.camera.util.ApiHelper;
import com.android.camera.ui.AbstractSettingPopup;
import com.android.camera.ui.CameraControls;
import com.android.camera.ui.CameraRootView;
-import com.android.camera.ui.CameraSwitcher;
-import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
import com.android.camera.ui.CountDownView;
import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
import com.android.camera.ui.FaceView;
@@ -92,7 +88,7 @@ public class PhotoUI implements PieListener,
private View mMenuButton;
private PhotoMenu mMenu;
- private CameraSwitcher mSwitcher;
+ private ModuleSwitcher mSwitcher;
private CameraControls mCameraControls;
private AlertDialog mLocationDialog;
@@ -196,8 +192,8 @@ public class PhotoUI implements PieListener,
initIndicators();
mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
- mSwitcher = (CameraSwitcher) mRootView.findViewById(R.id.camera_switcher);
- mSwitcher.setCurrentIndex(CameraSwitcher.PHOTO_MODULE_INDEX);
+ mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher);
+ mSwitcher.setCurrentIndex(ModuleSwitcher.PHOTO_MODULE_INDEX);
mSwitcher.setSwitchListener(mActivity);
mMenuButton = mRootView.findViewById(R.id.menu);
if (ApiHelper.HAS_FACE_DETECTION) {
diff --git a/src/com/android/camera/PreviewFrameLayout.java b/src/com/android/camera/PreviewFrameLayout.java
deleted file mode 100644
index 2bdace6..0000000
--- a/src/com/android/camera/PreviewFrameLayout.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.camera;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.RelativeLayout;
-import com.android.camera.util.ApiHelper;
-import com.android.camera.ui.LayoutChangeHelper;
-import com.android.camera.ui.LayoutChangeNotifier;
-import com.android.camera.util.CameraUtil;
-import com.android.camera2.R;
-
-/**
- * A layout which handles the preview aspect ratio.
- */
-public class PreviewFrameLayout extends RelativeLayout implements LayoutChangeNotifier {
-
- private static final String TAG = "CAM_preview";
-
- /** A callback to be invoked when the preview frame's size changes. */
- public interface OnSizeChangedListener {
- public void onSizeChanged(int width, int height);
- }
-
- private double mAspectRatio;
- private View mBorder;
- private OnSizeChangedListener mListener;
- private LayoutChangeHelper mLayoutChangeHelper;
-
- public PreviewFrameLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- setAspectRatio(4.0 / 3.0);
- mLayoutChangeHelper = new LayoutChangeHelper(this);
- }
-
- @Override
- protected void onFinishInflate() {
- mBorder = findViewById(R.id.preview_border);
- }
-
- public void setAspectRatio(double ratio) {
- if (ratio <= 0.0) throw new IllegalArgumentException();
-
- if (mAspectRatio != ratio) {
- mAspectRatio = ratio;
- requestLayout();
- }
- }
-
- public void showBorder(boolean enabled) {
- mBorder.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
- }
-
- public void fadeOutBorder() {
- CameraUtil.fadeOut(mBorder);
- }
-
- @Override
- protected void onMeasure(int widthSpec, int heightSpec) {
- int previewWidth = MeasureSpec.getSize(widthSpec);
- int previewHeight = MeasureSpec.getSize(heightSpec);
-
- if (!ApiHelper.HAS_SURFACE_TEXTURE) {
- // Get the padding of the border background.
- int hPadding = getPaddingLeft() + getPaddingRight();
- int vPadding = getPaddingTop() + getPaddingBottom();
-
- // Resize the preview frame with correct aspect ratio.
- previewWidth -= hPadding;
- previewHeight -= vPadding;
-
- boolean widthLonger = previewWidth > previewHeight;
- int longSide = (widthLonger ? previewWidth : previewHeight);
- int shortSide = (widthLonger ? previewHeight : previewWidth);
- if (longSide > shortSide * mAspectRatio) {
- longSide = (int) ((double) shortSide * mAspectRatio);
- } else {
- shortSide = (int) ((double) longSide / mAspectRatio);
- }
- if (widthLonger) {
- previewWidth = longSide;
- previewHeight = shortSide;
- } else {
- previewWidth = shortSide;
- previewHeight = longSide;
- }
-
- // Add the padding of the border.
- previewWidth += hPadding;
- previewHeight += vPadding;
- }
-
- // Ask children to follow the new preview dimension.
- super.onMeasure(MeasureSpec.makeMeasureSpec(previewWidth, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(previewHeight, MeasureSpec.EXACTLY));
- }
-
- public void setOnSizeChangedListener(OnSizeChangedListener listener) {
- mListener = listener;
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- if (mListener != null) mListener.onSizeChanged(w, h);
- }
-
- @Override
- public void setOnLayoutChangeListener(
- LayoutChangeNotifier.Listener listener) {
- mLayoutChangeHelper.setOnLayoutChangeListener(listener);
- }
-
- @SuppressLint("WrongCall")
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- // TODO: Suspicious call!
- mLayoutChangeHelper.onLayout(changed, l, t, r, b);
- }
-}
diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java
index ad42b1a..88a7b58 100644
--- a/src/com/android/camera/VideoUI.java
+++ b/src/com/android/camera/VideoUI.java
@@ -36,7 +36,6 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -47,8 +46,7 @@ import com.android.camera.CameraPreference.OnPreferenceChangedListener;
import com.android.camera.ui.AbstractSettingPopup;
import com.android.camera.ui.CameraControls;
import com.android.camera.ui.CameraRootView;
-import com.android.camera.ui.CameraSwitcher;
-import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
+import com.android.camera.ui.ModuleSwitcher;
import com.android.camera.ui.PieRenderer;
import com.android.camera.ui.RenderOverlay;
import com.android.camera.ui.RotateLayout;
@@ -74,7 +72,7 @@ public class VideoUI implements PieRenderer.PieListener,
private View mReviewDoneButton;
private View mReviewPlayButton;
private ShutterButton mShutterButton;
- private CameraSwitcher mSwitcher;
+ private ModuleSwitcher mSwitcher;
private TextView mRecordingTimeView;
private LinearLayout mLabelsLinearLayout;
private View mTimeLapseLabel;
@@ -172,9 +170,9 @@ public class VideoUI implements PieRenderer.PieListener,
((CameraRootView) mRootView).setDisplayChangeListener(this);
mFlashOverlay = mRootView.findViewById(R.id.flash_overlay);
mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
- mSwitcher = (CameraSwitcher) mRootView.findViewById(R.id.camera_switcher);
- mSwitcher.setCurrentIndex(CameraSwitcher.VIDEO_MODULE_INDEX);
- mSwitcher.setSwitchListener((CameraSwitchListener) mActivity);
+ mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher);
+ mSwitcher.setCurrentIndex(ModuleSwitcher.VIDEO_MODULE_INDEX);
+ mSwitcher.setSwitchListener(mActivity);
initializeMiscControls();
initializeControlByIntent();
initializeOverlay();
diff --git a/src/com/android/camera/ui/LayoutChangeNotifier.java b/src/com/android/camera/WideAnglePanoramaController.java
index 6261d34..6ac7c51 100644
--- a/src/com/android/camera/ui/LayoutChangeNotifier.java
+++ b/src/com/android/camera/WideAnglePanoramaController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -14,15 +14,20 @@
* limitations under the License.
*/
-package com.android.camera.ui;
+package com.android.camera;
-import android.view.View;
+/**
+ * The interface that controls the wide angle panorama module.
+ */
+public interface WideAnglePanoramaController {
+
+ public void onPreviewUIReady();
+
+ public void onPreviewUIDestroyed();
+
+ public void cancelHighResStitching();
-public interface LayoutChangeNotifier {
- public interface Listener {
- // Invoked only when the layout has changed or it is the first layout.
- public void onLayoutChange(View v, int l, int t, int r, int b);
- }
+ public void onShutterButtonClick();
- public void setOnLayoutChangeListener(LayoutChangeNotifier.Listener listener);
+ public void onPreviewUILayoutChange(int l, int t, int r, int b);
}
diff --git a/src/com/android/camera/WideAnglePanoramaModule.java b/src/com/android/camera/WideAnglePanoramaModule.java
new file mode 100644
index 0000000..1756e4c
--- /dev/null
+++ b/src/com/android/camera/WideAnglePanoramaModule.java
@@ -0,0 +1,1071 @@
+/*
+ * 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;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.graphics.YuvImage;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.location.Location;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.OrientationEventListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.app.OrientationManager;
+import com.android.camera.exif.ExifInterface;
+import com.android.camera.ui.PopupManager;
+import com.android.camera.util.CameraUtil;
+import com.android.camera.util.UsageStatistics;
+import com.android.camera2.R;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * Activity to handle panorama capturing.
+ */
+public class WideAnglePanoramaModule
+ implements CameraModule, WideAnglePanoramaController,
+ SurfaceTexture.OnFrameAvailableListener {
+
+ public static final int DEFAULT_SWEEP_ANGLE = 160;
+ public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL;
+ public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720;
+
+ private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1;
+ private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 2;
+ private static final int MSG_END_DIALOG_RESET_TO_PREVIEW = 3;
+ private static final int MSG_CLEAR_SCREEN_DELAY = 4;
+ private static final int MSG_RESET_TO_PREVIEW = 5;
+
+ private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "CAM_WidePanoModule";
+ private static final int PREVIEW_STOPPED = 0;
+ private static final int PREVIEW_ACTIVE = 1;
+ public static final int CAPTURE_STATE_VIEWFINDER = 0;
+ public static final int CAPTURE_STATE_MOSAIC = 1;
+
+ // The unit of speed is degrees per frame.
+ private static final float PANNING_SPEED_THRESHOLD = 2.5f;
+ private static final boolean DEBUG = false;
+
+ private ContentResolver mContentResolver;
+ private WideAnglePanoramaUI mUI;
+
+ private MosaicPreviewRenderer mMosaicPreviewRenderer;
+ private Object mRendererLock = new Object();
+ private Object mWaitObject = new Object();
+
+ private String mPreparePreviewString;
+ private String mDialogTitle;
+ private String mDialogOkString;
+ private String mDialogPanoramaFailedString;
+ private String mDialogWaitingPreviousString;
+
+ private int mPreviewUIWidth;
+ private int mPreviewUIHeight;
+ private boolean mUsingFrontCamera;
+ private int mCameraPreviewWidth;
+ private int mCameraPreviewHeight;
+ private int mCameraState;
+ private int mCaptureState;
+ private PowerManager.WakeLock mPartialWakeLock;
+ private MosaicFrameProcessor mMosaicFrameProcessor;
+ private boolean mMosaicFrameProcessorInitialized;
+ private AsyncTask <Void, Void, Void> mWaitProcessorTask;
+ private long mTimeTaken;
+ private Handler mMainHandler;
+ private SurfaceTexture mCameraTexture;
+ private boolean mThreadRunning;
+ private boolean mCancelComputation;
+ private float mHorizontalViewAngle;
+ private float mVerticalViewAngle;
+
+ // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of
+ // getting a better image quality by the former.
+ private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY;
+
+ private PanoOrientationEventListener mOrientationEventListener;
+ // The value could be 0, 90, 180, 270 for the 4 different orientations measured in clockwise
+ // respectively.
+ private int mDeviceOrientation;
+ private int mDeviceOrientationAtCapture;
+ private int mCameraOrientation;
+ private int mOrientationCompensation;
+
+ private SoundClips.Player mSoundPlayer;
+
+ private Runnable mOnFrameAvailableRunnable;
+
+ private CameraActivity mActivity;
+ private View mRootView;
+ private CameraProxy mCameraDevice;
+ private boolean mPaused;
+
+ private LocationManager mLocationManager;
+ private OrientationManager mOrientationManager;
+ private ComboPreferences mPreferences;
+ private boolean mMosaicPreviewConfigured;
+
+ @Override
+ public void onPreviewUIReady() {
+ configMosaicPreview();
+ }
+
+ @Override
+ public void onPreviewUIDestroyed() {
+
+ }
+
+ private class MosaicJpeg {
+ public MosaicJpeg(byte[] data, int width, int height) {
+ this.data = data;
+ this.width = width;
+ this.height = height;
+ this.isValid = true;
+ }
+
+ public MosaicJpeg() {
+ this.data = null;
+ this.width = 0;
+ this.height = 0;
+ this.isValid = false;
+ }
+
+ public final byte[] data;
+ public final int width;
+ public final int height;
+ public final boolean isValid;
+ }
+
+ private class PanoOrientationEventListener extends OrientationEventListener {
+ public PanoOrientationEventListener(Context context) {
+ super(context);
+ }
+
+ @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 == ORIENTATION_UNKNOWN) return;
+ mDeviceOrientation = CameraUtil.roundOrientation(orientation, mDeviceOrientation);
+ // When the screen is unlocked, display rotation may change. Always
+ // calculate the up-to-date orientationCompensation.
+ int orientationCompensation = mDeviceOrientation
+ + CameraUtil.getDisplayRotation(mActivity) % 360;
+ if (mOrientationCompensation != orientationCompensation) {
+ mOrientationCompensation = orientationCompensation;
+ }
+ }
+ }
+
+ @Override
+ public void init(CameraActivity activity, View parent) {
+ mActivity = activity;
+ mRootView = parent;
+
+ mOrientationManager = new OrientationManager(activity);
+ mCaptureState = CAPTURE_STATE_VIEWFINDER;
+ mUI = new WideAnglePanoramaUI(mActivity, this, (ViewGroup) mRootView);
+ mUI.setCaptureProgressOnDirectionChangeListener(
+ new PanoProgressBar.OnDirectionChangeListener() {
+ @Override
+ public void onDirectionChange(int direction) {
+ if (mCaptureState == CAPTURE_STATE_MOSAIC) {
+ mUI.showDirectionIndicators(direction);
+ }
+ }
+ });
+
+ mContentResolver = mActivity.getContentResolver();
+ // This runs in UI thread.
+ mOnFrameAvailableRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // Frames might still be available after the activity is paused.
+ // If we call onFrameAvailable after pausing, the GL thread will crash.
+ if (mPaused) return;
+
+ MosaicPreviewRenderer renderer = null;
+ synchronized (mRendererLock) {
+ if (mMosaicPreviewRenderer == null) {
+ return;
+ }
+ renderer = mMosaicPreviewRenderer;
+ }
+ if (mRootView.getVisibility() != View.VISIBLE) {
+ renderer.showPreviewFrameSync();
+ mRootView.setVisibility(View.VISIBLE);
+ } else {
+ if (mCaptureState == CAPTURE_STATE_VIEWFINDER) {
+ renderer.showPreviewFrame();
+ } else {
+ renderer.alignFrameSync();
+ mMosaicFrameProcessor.processFrame();
+ }
+ }
+ }
+ };
+
+ PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
+ mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama");
+
+ mOrientationEventListener = new PanoOrientationEventListener(mActivity);
+
+ mMosaicFrameProcessor = MosaicFrameProcessor.getInstance();
+
+ Resources appRes = mActivity.getResources();
+ mPreparePreviewString = appRes.getString(R.string.pano_dialog_prepare_preview);
+ mDialogTitle = appRes.getString(R.string.pano_dialog_title);
+ mDialogOkString = appRes.getString(R.string.dialog_ok);
+ mDialogPanoramaFailedString = appRes.getString(R.string.pano_dialog_panorama_failed);
+ mDialogWaitingPreviousString = appRes.getString(R.string.pano_dialog_waiting_previous);
+
+ mPreferences = new ComboPreferences(mActivity);
+ CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+ mLocationManager = new LocationManager(mActivity, null);
+
+ mMainHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LOW_RES_FINAL_MOSAIC_READY:
+ onBackgroundThreadFinished();
+ showFinalMosaic((Bitmap) msg.obj);
+ saveHighResMosaic();
+ break;
+ case MSG_GENERATE_FINAL_MOSAIC_ERROR:
+ onBackgroundThreadFinished();
+ if (mPaused) {
+ resetToPreviewIfPossible();
+ } else {
+ mUI.showAlertDialog(
+ mDialogTitle, mDialogPanoramaFailedString,
+ mDialogOkString, new Runnable() {
+ @Override
+ public void run() {
+ resetToPreviewIfPossible();
+ }
+ });
+ }
+ clearMosaicFrameProcessorIfNeeded();
+ break;
+ case MSG_END_DIALOG_RESET_TO_PREVIEW:
+ onBackgroundThreadFinished();
+ resetToPreviewIfPossible();
+ clearMosaicFrameProcessorIfNeeded();
+ break;
+ case MSG_CLEAR_SCREEN_DELAY:
+ mActivity.getWindow().clearFlags(WindowManager.LayoutParams.
+ FLAG_KEEP_SCREEN_ON);
+ break;
+ case MSG_RESET_TO_PREVIEW:
+ resetToPreviewIfPossible();
+ break;
+ }
+ }
+ };
+ }
+
+ @Override
+ public void onSwitchMode(boolean toCamera) {
+ if (toCamera) {
+ mUI.showUI();
+ } else {
+ mUI.hideUI();
+ }
+ }
+
+ private void setupCamera() throws CameraHardwareException, CameraDisabledException {
+ openCamera();
+ Parameters parameters = mCameraDevice.getParameters();
+ setupCaptureParams(parameters);
+ configureCamera(parameters);
+ }
+
+ private void releaseCamera() {
+ if (mCameraDevice != null) {
+ CameraHolder.instance().release();
+ mCameraDevice = null;
+ mCameraState = PREVIEW_STOPPED;
+ }
+ }
+
+ private void openCamera() throws CameraHardwareException, CameraDisabledException {
+ int cameraId = CameraHolder.instance().getBackCameraId();
+ // If there is no back camera, use the first camera. Camera id starts
+ // from 0. Currently if a camera is not back facing, it is front facing.
+ // This is also forward compatible if we have a new facing other than
+ // back or front in the future.
+ if (cameraId == -1) cameraId = 0;
+ mCameraDevice = CameraUtil.openCamera(mActivity, cameraId);
+ mCameraOrientation = CameraUtil.getCameraOrientation(cameraId);
+ if (cameraId == CameraHolder.instance().getFrontCameraId()) mUsingFrontCamera = true;
+ }
+
+ private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3,
+ boolean needSmaller) {
+ int pixelsDiff = DEFAULT_CAPTURE_PIXELS;
+ boolean hasFound = false;
+ for (Size size : supportedSizes) {
+ int h = size.height;
+ int w = size.width;
+ // we only want 4:3 format.
+ int d = DEFAULT_CAPTURE_PIXELS - h * w;
+ if (needSmaller && d < 0) { // no bigger preview than 960x720.
+ continue;
+ }
+ if (need4To3 && (h * 4 != w * 3)) {
+ continue;
+ }
+ d = Math.abs(d);
+ if (d < pixelsDiff) {
+ mCameraPreviewWidth = w;
+ mCameraPreviewHeight = h;
+ pixelsDiff = d;
+ hasFound = true;
+ }
+ }
+ return hasFound;
+ }
+
+ private void setupCaptureParams(Parameters parameters) {
+ List<Size> supportedSizes = parameters.getSupportedPreviewSizes();
+ if (!findBestPreviewSize(supportedSizes, true, true)) {
+ Log.w(TAG, "No 4:3 ratio preview size supported.");
+ if (!findBestPreviewSize(supportedSizes, false, true)) {
+ Log.w(TAG, "Can't find a supported preview size smaller than 960x720.");
+ findBestPreviewSize(supportedSizes, false, false);
+ }
+ }
+ Log.d(TAG, "camera preview h = "
+ + mCameraPreviewHeight + " , w = " + mCameraPreviewWidth);
+ parameters.setPreviewSize(mCameraPreviewWidth, mCameraPreviewHeight);
+
+ List<int[]> frameRates = parameters.getSupportedPreviewFpsRange();
+ int last = frameRates.size() - 1;
+ int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX];
+ int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX];
+ parameters.setPreviewFpsRange(minFps, maxFps);
+ Log.d(TAG, "preview fps: " + minFps + ", " + maxFps);
+
+ List<String> supportedFocusModes = parameters.getSupportedFocusModes();
+ if (supportedFocusModes.indexOf(mTargetFocusMode) >= 0) {
+ parameters.setFocusMode(mTargetFocusMode);
+ } else {
+ // Use the default focus mode and log a message
+ Log.w(TAG, "Cannot set the focus mode to " + mTargetFocusMode +
+ " becuase the mode is not supported.");
+ }
+
+ parameters.set(CameraUtil.RECORDING_HINT, CameraUtil.FALSE);
+
+ mHorizontalViewAngle = parameters.getHorizontalViewAngle();
+ mVerticalViewAngle = parameters.getVerticalViewAngle();
+ }
+
+ public int getPreviewBufSize() {
+ PixelFormat pixelInfo = new PixelFormat();
+ PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo);
+ // TODO: remove this extra 32 byte after the driver bug is fixed.
+ return (mCameraPreviewWidth * mCameraPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32;
+ }
+
+ private void configureCamera(Parameters parameters) {
+ mCameraDevice.setParameters(parameters);
+ }
+
+ /**
+ * Configures the preview renderer according to the dimension defined by
+ * {@code mPreviewUIWidth} and {@code mPreviewUIHeight}.
+ * Will stop the camera preview first.
+ */
+ private void configMosaicPreview() {
+ if (mPreviewUIWidth == 0 || mPreviewUIHeight == 0
+ || mUI.getSurfaceTexture() == null) {
+ return;
+ }
+
+ stopCameraPreview();
+ synchronized (mRendererLock) {
+ if (mMosaicPreviewRenderer != null) {
+ mMosaicPreviewRenderer.release();
+ }
+ mMosaicPreviewRenderer = null;
+ }
+ final boolean isLandscape =
+ (mActivity.getResources().getConfiguration().orientation ==
+ Configuration.ORIENTATION_LANDSCAPE);
+
+ MosaicPreviewRenderer renderer = new MosaicPreviewRenderer(
+ mUI.getSurfaceTexture(),
+ mPreviewUIWidth, mPreviewUIHeight, isLandscape);
+ synchronized (mRendererLock) {
+ mMosaicPreviewRenderer = renderer;
+ mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture();
+
+ if (!mPaused && !mThreadRunning && mWaitProcessorTask == null) {
+ mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW);
+ }
+ mRendererLock.notifyAll();
+ }
+ mMosaicPreviewConfigured = true;
+ resetToPreviewIfPossible();
+ }
+
+ /**
+ * Receives the layout change event from the preview area. So we can
+ * initialize the mosaic preview renderer.
+ */
+ @Override
+ public void onPreviewUILayoutChange(int l, int t, int r, int b) {
+ Log.d(TAG, "layout change: " + (r - l) + "/" + (b - t));
+ mPreviewUIWidth = r - l;
+ mPreviewUIHeight = b - t;
+ configMosaicPreview();
+ }
+
+ @Override
+ public void onFrameAvailable(SurfaceTexture surface) {
+ /* This function may be called by some random thread,
+ * so let's be safe and jump back to ui thread.
+ * No OpenGL calls can be done here. */
+ mActivity.runOnUiThread(mOnFrameAvailableRunnable);
+ }
+
+ public void startCapture() {
+ // Reset values so we can do this again.
+ mCancelComputation = false;
+ mTimeTaken = System.currentTimeMillis();
+ mActivity.setSwipingEnabled(false);
+ mCaptureState = CAPTURE_STATE_MOSAIC;
+ mUI.onStartCapture();
+
+ mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {
+ @Override
+ public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
+ float progressX, float progressY) {
+ float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle;
+ float accumulatedVerticalAngle = progressY * mVerticalViewAngle;
+ if (isFinished
+ || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE)
+ || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) {
+ stopCapture(false);
+ } else {
+ float panningRateXInDegree = panningRateX * mHorizontalViewAngle;
+ float panningRateYInDegree = panningRateY * mVerticalViewAngle;
+ mUI.updateCaptureProgress(panningRateXInDegree, panningRateYInDegree,
+ accumulatedHorizontalAngle, accumulatedVerticalAngle,
+ PANNING_SPEED_THRESHOLD);
+ }
+ }
+ });
+
+ mUI.resetCaptureProgress();
+ // TODO: calculate the indicator width according to different devices to reflect the actual
+ // angle of view of the camera device.
+ mUI.setMaxCaptureProgress(DEFAULT_SWEEP_ANGLE);
+ mUI.showCaptureProgress();
+ mDeviceOrientationAtCapture = mDeviceOrientation;
+ keepScreenOn();
+ // TODO: mActivity.getOrientationManager().lockOrientation();
+ mOrientationManager.lockOrientation();
+ int degrees = CameraUtil.getDisplayRotation(mActivity);
+ int cameraId = CameraHolder.instance().getBackCameraId();
+ int orientation = CameraUtil.getDisplayOrientation(degrees, cameraId);
+ mUI.setProgressOrientation(orientation);
+ }
+
+ private void stopCapture(boolean aborted) {
+ mCaptureState = CAPTURE_STATE_VIEWFINDER;
+ mUI.onStopCapture();
+
+ mMosaicFrameProcessor.setProgressListener(null);
+ stopCameraPreview();
+
+ mCameraTexture.setOnFrameAvailableListener(null);
+
+ if (!aborted && !mThreadRunning) {
+ mUI.showWaitingDialog(mPreparePreviewString);
+ // Hide shutter button, shutter icon, etc when waiting for
+ // panorama to stitch
+ mUI.hideUI();
+ runBackgroundThread(new Thread() {
+ @Override
+ public void run() {
+ MosaicJpeg jpeg = generateFinalMosaic(false);
+
+ if (jpeg != null && jpeg.isValid) {
+ Bitmap bitmap = null;
+ bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length);
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(
+ MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap));
+ } else {
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(
+ MSG_END_DIALOG_RESET_TO_PREVIEW));
+ }
+ }
+ });
+ }
+ keepScreenOnAwhile();
+ }
+
+ @Override
+ public void onShutterButtonClick() {
+ // If mCameraTexture == null then GL setup is not finished yet.
+ // No buttons can be pressed.
+ if (mPaused || mThreadRunning || mCameraTexture == null) return;
+ // Since this button will stay on the screen when capturing, we need to check the state
+ // right now.
+ switch (mCaptureState) {
+ case CAPTURE_STATE_VIEWFINDER:
+ if(mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) return;
+ mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING);
+ startCapture();
+ break;
+ case CAPTURE_STATE_MOSAIC:
+ mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING);
+ stopCapture(false);
+ }
+ }
+
+ public void reportProgress() {
+ mUI.resetSavingProgress();
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ while (mThreadRunning) {
+ final int progress = mMosaicFrameProcessor.reportProgress(
+ true, mCancelComputation);
+
+ try {
+ synchronized (mWaitObject) {
+ mWaitObject.wait(50);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Panorama reportProgress failed", e);
+ }
+ // Update the progress bar
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mUI.updateSavingProgress(progress);
+ }
+ });
+ }
+ }
+ };
+ t.start();
+ }
+
+ private int getCaptureOrientation() {
+ // The panorama image returned from the library is oriented based on the
+ // natural orientation of a camera. We need to set an orientation for the image
+ // in its EXIF header, so the image can be displayed correctly.
+ // The orientation is calculated from compensating the
+ // device orientation at capture and the camera orientation respective to
+ // the natural orientation of the device.
+ int orientation;
+ if (mUsingFrontCamera) {
+ // mCameraOrientation is negative with respect to the front facing camera.
+ // See document of android.hardware.Camera.Parameters.setRotation.
+ orientation = (mDeviceOrientationAtCapture - mCameraOrientation + 360) % 360;
+ } else {
+ orientation = (mDeviceOrientationAtCapture + mCameraOrientation) % 360;
+ }
+ return orientation;
+ }
+
+ public void saveHighResMosaic() {
+ runBackgroundThread(new Thread() {
+ @Override
+ public void run() {
+ mPartialWakeLock.acquire();
+ MosaicJpeg jpeg;
+ try {
+ jpeg = generateFinalMosaic(true);
+ } finally {
+ mPartialWakeLock.release();
+ }
+
+ if (jpeg == null) { // Cancelled by user.
+ mMainHandler.sendEmptyMessage(MSG_END_DIALOG_RESET_TO_PREVIEW);
+ } else if (!jpeg.isValid) { // Error when generating mosaic.
+ mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR);
+ } else {
+ int orientation = getCaptureOrientation();
+ final Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation);
+ if (uri != null) {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.notifyNewMedia(uri);
+ }
+ });
+ }
+ mMainHandler.sendMessage(
+ mMainHandler.obtainMessage(MSG_END_DIALOG_RESET_TO_PREVIEW));
+ }
+ }
+ });
+ reportProgress();
+ }
+
+ private void runBackgroundThread(Thread thread) {
+ mThreadRunning = true;
+ thread.start();
+ }
+
+ private void onBackgroundThreadFinished() {
+ mThreadRunning = false;
+ mUI.dismissAllDialogs();
+ }
+
+ private void cancelHighResComputation() {
+ mCancelComputation = true;
+ synchronized (mWaitObject) {
+ mWaitObject.notify();
+ }
+ }
+
+ // This function will be called upon the first camera frame is available.
+ private void reset() {
+ mCaptureState = CAPTURE_STATE_VIEWFINDER;
+
+ mOrientationManager.unlockOrientation();
+ mUI.reset();
+ mActivity.setSwipingEnabled(true);
+ // Orientation change will trigger onLayoutChange->configMosaicPreview->
+ // resetToPreview. Do not show the capture UI in film strip.
+ /* if (mActivity.mShowCameraAppView) {
+ mCaptureLayout.setVisibility(View.VISIBLE); */
+ mUI.showPreviewUI();
+ /*} else {
+ }*/
+ mMosaicFrameProcessor.reset();
+ }
+
+ private void resetToPreviewIfPossible() {
+ if (!mMosaicFrameProcessorInitialized
+ || mUI.getSurfaceTexture() == null
+ || !mMosaicPreviewConfigured) {
+ return;
+ }
+ reset();
+ if (!mPaused) startCameraPreview();
+ }
+
+ private void showFinalMosaic(Bitmap bitmap) {
+ mUI.showFinalMosaic(bitmap, getCaptureOrientation());
+ }
+
+ private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) {
+ if (jpegData != null) {
+ String filename = PanoUtil.createName(
+ mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken);
+ String filepath = Storage.generateFilepath(filename);
+
+ Location loc = mLocationManager.getCurrentLocation();
+ ExifInterface exif = new ExifInterface();
+ try {
+ exif.readExif(jpegData);
+ exif.addGpsDateTimeStampTag(mTimeTaken);
+ exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, mTimeTaken,
+ TimeZone.getDefault());
+ exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,
+ ExifInterface.getOrientationValueForRotation(orientation)));
+ writeLocation(loc, exif);
+ exif.writeExif(jpegData, filepath);
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot set exif for " + filepath, e);
+ Storage.writeFile(filepath, jpegData);
+ }
+ int jpegLength = (int) (new File(filepath).length());
+ return Storage.addImage(mContentResolver, filename, mTimeTaken,
+ loc, orientation, jpegLength, filepath, width, height);
+ }
+ return null;
+ }
+
+ private static void writeLocation(Location location, ExifInterface exif) {
+ if (location == null) {
+ return;
+ }
+ exif.addGpsTags(location.getLatitude(), location.getLongitude());
+ exif.setTag(exif.buildTag(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider()));
+ }
+
+ private void clearMosaicFrameProcessorIfNeeded() {
+ if (!mPaused || mThreadRunning) return;
+ // Only clear the processor if it is initialized by this activity
+ // instance. Other activity instances may be using it.
+ if (mMosaicFrameProcessorInitialized) {
+ mMosaicFrameProcessor.clear();
+ mMosaicFrameProcessorInitialized = false;
+ }
+ }
+
+ private void initMosaicFrameProcessorIfNeeded() {
+ if (mPaused || mThreadRunning) {
+ return;
+ }
+
+ mMosaicFrameProcessor.initialize(
+ mCameraPreviewWidth, mCameraPreviewHeight, getPreviewBufSize());
+ mMosaicFrameProcessorInitialized = true;
+ }
+
+ @Override
+ public void onPauseBeforeSuper() {
+ mPaused = true;
+ if (mLocationManager != null) mLocationManager.recordLocation(false);
+ }
+
+ @Override
+ public void onPauseAfterSuper() {
+ mOrientationEventListener.disable();
+ if (mCameraDevice == null) {
+ // Camera open failed. Nothing should be done here.
+ return;
+ }
+ // Stop the capturing first.
+ if (mCaptureState == CAPTURE_STATE_MOSAIC) {
+ stopCapture(true);
+ reset();
+ }
+
+ releaseCamera();
+ synchronized (mRendererLock) {
+ mCameraTexture = null;
+
+ // The preview renderer might not have a chance to be initialized
+ // before onPause().
+ if (mMosaicPreviewRenderer != null) {
+ mMosaicPreviewRenderer.release();
+ mMosaicPreviewRenderer = null;
+ }
+ }
+
+ clearMosaicFrameProcessorIfNeeded();
+ if (mWaitProcessorTask != null) {
+ mWaitProcessorTask.cancel(true);
+ mWaitProcessorTask = null;
+ }
+ resetScreenOn();
+ if (mSoundPlayer != null) {
+ mSoundPlayer.release();
+ mSoundPlayer = null;
+ }
+ System.gc();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ mUI.onConfigurationChanged(newConfig, mThreadRunning);
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ }
+
+ @Override
+ public void onResumeBeforeSuper() {
+ mPaused = false;
+ }
+
+ @Override
+ public void onResumeAfterSuper() {
+ mOrientationEventListener.enable();
+
+ mCaptureState = CAPTURE_STATE_VIEWFINDER;
+
+ try {
+ setupCamera();
+ } catch (CameraHardwareException e) {
+ CameraUtil.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+ return;
+ } catch (CameraDisabledException e) {
+ CameraUtil.showErrorAndFinish(mActivity, R.string.camera_disabled);
+ return;
+ }
+
+ // Set up sound playback for shutter button
+ mSoundPlayer = SoundClips.getPlayer(mActivity);
+
+ // Check if another panorama instance is using the mosaic frame processor.
+ mUI.dismissAllDialogs();
+ if (!mThreadRunning && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
+ mUI.showWaitingDialog(mDialogWaitingPreviousString);
+ // If stitching is still going on, make sure switcher and shutter button
+ // are not showing
+ mUI.hideUI();
+ mWaitProcessorTask = new WaitProcessorTask().execute();
+ } else {
+ // Camera must be initialized before MosaicFrameProcessor is
+ // initialized. The preview size has to be decided by camera device.
+ initMosaicFrameProcessorIfNeeded();
+ Point size = mUI.getPreviewAreaSize();
+ mPreviewUIWidth = size.x;
+ mPreviewUIHeight = size.y;
+ configMosaicPreview();
+ }
+ keepScreenOnAwhile();
+
+ // Initialize location service.
+ boolean recordLocation = RecordLocationPreference.get(mPreferences,
+ mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+
+ // Dismiss open menu if exists.
+ PopupManager.getInstance(mActivity).notifyShowPopup(null);
+ UsageStatistics.onContentViewChanged(
+ UsageStatistics.COMPONENT_CAMERA, "PanoramaModule");
+ }
+
+ /**
+ * Generate the final mosaic image.
+ *
+ * @param highRes flag to indicate whether we want to get a high-res version.
+ * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation
+ * process is cancelled; and a MosaicJpeg with its isValid flag set to false if there
+ * is an error in generating the final mosaic.
+ */
+ public MosaicJpeg generateFinalMosaic(boolean highRes) {
+ int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes);
+ if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) {
+ return null;
+ } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) {
+ return new MosaicJpeg();
+ }
+
+ byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();
+ if (imageData == null) {
+ Log.e(TAG, "getFinalMosaicNV21() returned null.");
+ return new MosaicJpeg();
+ }
+
+ int len = imageData.length - 8;
+ int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16)
+ + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF);
+ int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16)
+ + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF);
+ Log.d(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height);
+
+ if (width <= 0 || height <= 0) {
+ // TODO: pop up an error message indicating that the final result is not generated.
+ Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " +
+ height);
+ return new MosaicJpeg();
+ }
+
+ YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
+ try {
+ out.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in storing final mosaic", e);
+ return new MosaicJpeg();
+ }
+ return new MosaicJpeg(out.toByteArray(), width, height);
+ }
+
+ private void startCameraPreview() {
+ if (mCameraDevice == null) {
+ // Camera open failed. Return.
+ return;
+ }
+
+ if (mUI.getSurfaceTexture() == null) {
+ // UI is not ready.
+ return;
+ }
+
+ // This works around a driver issue. startPreview may fail if
+ // stopPreview/setPreviewTexture/startPreview are called several times
+ // in a row. mCameraTexture can be null after pressing home during
+ // mosaic generation and coming back. Preview will be started later in
+ // onLayoutChange->configMosaicPreview. This also reduces the latency.
+ synchronized (mRendererLock) {
+ if (mCameraTexture == null) return;
+
+ // If we're previewing already, stop the preview first (this will
+ // blank the screen).
+ if (mCameraState != PREVIEW_STOPPED) stopCameraPreview();
+
+ // Set the display orientation to 0, so that the underlying mosaic
+ // library can always get undistorted mCameraPreviewWidth x mCameraPreviewHeight
+ // image data from SurfaceTexture.
+ mCameraDevice.setDisplayOrientation(0);
+
+ mCameraTexture.setOnFrameAvailableListener(this);
+ mCameraDevice.setPreviewTexture(mCameraTexture);
+ }
+ mCameraDevice.startPreview();
+ mCameraState = PREVIEW_ACTIVE;
+ }
+
+ private void stopCameraPreview() {
+ if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+ mCameraDevice.stopPreview();
+ }
+ mCameraState = PREVIEW_STOPPED;
+ }
+
+ @Override
+ public void onUserInteraction() {
+ if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile();
+ }
+
+ @Override
+ public boolean onBackPressed() {
+ // If panorama is generating low res or high res mosaic, ignore back
+ // key. So the activity will not be destroyed.
+ if (mThreadRunning) return true;
+ return false;
+ }
+
+ private void resetScreenOn() {
+ mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ private void keepScreenOnAwhile() {
+ mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+ }
+
+ private void keepScreenOn() {
+ mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ private class WaitProcessorTask extends AsyncTask<Void, Void, Void> {
+ @Override
+ protected Void doInBackground(Void... params) {
+ synchronized (mMosaicFrameProcessor) {
+ while (!isCancelled() && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
+ try {
+ mMosaicFrameProcessor.wait();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ mWaitProcessorTask = null;
+ mUI.dismissAllDialogs();
+ // TODO (shkong): mGLRootView.setVisibility(View.VISIBLE);
+ initMosaicFrameProcessorIfNeeded();
+ Point size = mUI.getPreviewAreaSize();
+ mPreviewUIWidth = size.x;
+ mPreviewUIHeight = size.y;
+ configMosaicPreview();
+ resetToPreviewIfPossible();
+ }
+ }
+
+ @Override
+ public void cancelHighResStitching() {
+ if (mPaused || mCameraTexture == null) return;
+ cancelHighResComputation();
+ }
+
+ @Override
+ public void onStop() {
+ }
+
+ @Override
+ public void installIntentFilter() {
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ }
+
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public void onSingleTapUp(View view, int x, int y) {
+ }
+
+ @Override
+ public void onPreviewTextureCopied() {
+ }
+
+ @Override
+ public void onCaptureTextureCopied() {
+ }
+
+ @Override
+ public boolean updateStorageHintOnResume() {
+ return false;
+ }
+
+ @Override
+ public void updateCameraAppView() {
+ }
+
+ @Override
+ public void onShowSwitcherPopup() {
+ }
+
+ @Override
+ public void onMediaSaveServiceConnected(MediaSaveService s) {
+ // do nothing.
+ }
+}
diff --git a/src/com/android/camera/WideAnglePanoramaUI.java b/src/com/android/camera/WideAnglePanoramaUI.java
new file mode 100644
index 0000000..0ce9d62
--- /dev/null
+++ b/src/com/android/camera/WideAnglePanoramaUI.java
@@ -0,0 +1,461 @@
+/*
+ * 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;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.camera.ui.CameraControls;
+import com.android.camera.ui.CameraRootView;
+import com.android.camera.ui.ModuleSwitcher;
+import com.android.camera2.R;
+
+/**
+ * The UI of {@link WideAnglePanoramaModule}.
+ */
+public class WideAnglePanoramaUI implements
+ TextureView.SurfaceTextureListener,
+ ShutterButton.OnShutterButtonListener,
+ View.OnLayoutChangeListener {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "CAM_WidePanoramaUI";
+
+ private CameraActivity mActivity;
+ private WideAnglePanoramaController mController;
+
+ private ViewGroup mRootView;
+ private ModuleSwitcher mSwitcher;
+ private ViewGroup mPanoLayout;
+ private FrameLayout mCaptureLayout;
+ private View mReviewLayout;
+ private ImageView mReview;
+ private View mPreviewBorder;
+ private View mLeftIndicator;
+ private View mRightIndicator;
+ private View mCaptureIndicator;
+ private PanoProgressBar mCaptureProgressBar;
+ private PanoProgressBar mSavingProgressBar;
+ private TextView mTooFastPrompt;
+ private TextureView mTextureView;
+ private ShutterButton mShutterButton;
+ private CameraControls mCameraControls;
+
+ private Matrix mProgressDirectionMatrix = new Matrix();
+ private float[] mProgressAngle = new float[2];
+
+ private DialogHelper mDialogHelper;
+
+ // Color definitions.
+ private int mIndicatorColor;
+ private int mIndicatorColorFast;
+ private int mReviewBackground;
+ private SurfaceTexture mSurfaceTexture;
+
+ /** Constructor. */
+ public WideAnglePanoramaUI(
+ CameraActivity activity,
+ WideAnglePanoramaController controller,
+ ViewGroup root) {
+ mActivity = activity;
+ mController = controller;
+ mRootView = root;
+
+ createContentView();
+ mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher);
+ Log.v(TAG, "setting CurrentIndex:" + ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX);
+ mSwitcher.setCurrentIndex(ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX);
+ mSwitcher.setSwitchListener(mActivity);
+ }
+
+ public void onStartCapture() {
+ hideSwitcher();
+ mShutterButton.setImageResource(R.drawable.btn_shutter_recording);
+ mCaptureIndicator.setVisibility(View.VISIBLE);
+ showDirectionIndicators(PanoProgressBar.DIRECTION_NONE);
+ }
+
+ public void showPreviewUI() {
+ mCaptureLayout.setVisibility(View.VISIBLE);
+ showUI();
+ }
+
+ public void onStopCapture() {
+ mCaptureIndicator.setVisibility(View.INVISIBLE);
+ hideTooFastIndication();
+ hideDirectionIndicators();
+ }
+
+ public void hideSwitcher() {
+ mSwitcher.closePopup();
+ mSwitcher.setVisibility(View.INVISIBLE);
+ }
+
+ public void hideUI() {
+ hideSwitcher();
+ mCameraControls.setVisibility(View.INVISIBLE);
+ }
+
+ public void showUI() {
+ showSwitcher();
+ mCameraControls.setVisibility(View.VISIBLE);
+ }
+
+ public void showSwitcher() {
+ mSwitcher.setVisibility(View.VISIBLE);
+ }
+
+ public void setCaptureProgressOnDirectionChangeListener(
+ PanoProgressBar.OnDirectionChangeListener listener) {
+ mCaptureProgressBar.setOnDirectionChangeListener(listener);
+ }
+
+ public void resetCaptureProgress() {
+ mCaptureProgressBar.reset();
+ }
+
+ public void setMaxCaptureProgress(int max) {
+ mCaptureProgressBar.setMaxProgress(max);
+ }
+
+ public void showCaptureProgress() {
+ mCaptureProgressBar.setVisibility(View.VISIBLE);
+ }
+
+ public void updateCaptureProgress(
+ float panningRateXInDegree, float panningRateYInDegree,
+ float progressHorizontalAngle, float progressVerticalAngle,
+ float maxPanningSpeed) {
+
+ if ((Math.abs(panningRateXInDegree) > maxPanningSpeed)
+ || (Math.abs(panningRateYInDegree) > maxPanningSpeed)) {
+ showTooFastIndication();
+ } else {
+ hideTooFastIndication();
+ }
+
+ // progressHorizontalAngle and progressVerticalAngle are relative to the
+ // camera. Convert them to UI direction.
+ mProgressAngle[0] = progressHorizontalAngle;
+ mProgressAngle[1] = progressVerticalAngle;
+ mProgressDirectionMatrix.mapPoints(mProgressAngle);
+
+ int angleInMajorDirection =
+ (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1]))
+ ? (int) mProgressAngle[0]
+ : (int) mProgressAngle[1];
+ mCaptureProgressBar.setProgress((angleInMajorDirection));
+ }
+
+ public void setProgressOrientation(int orientation) {
+ mProgressDirectionMatrix.reset();
+ mProgressDirectionMatrix.postRotate(orientation);
+ }
+
+ public void showDirectionIndicators(int direction) {
+ switch (direction) {
+ case PanoProgressBar.DIRECTION_NONE:
+ mLeftIndicator.setVisibility(View.VISIBLE);
+ mRightIndicator.setVisibility(View.VISIBLE);
+ break;
+ case PanoProgressBar.DIRECTION_LEFT:
+ mLeftIndicator.setVisibility(View.VISIBLE);
+ mRightIndicator.setVisibility(View.INVISIBLE);
+ break;
+ case PanoProgressBar.DIRECTION_RIGHT:
+ mLeftIndicator.setVisibility(View.INVISIBLE);
+ mRightIndicator.setVisibility(View.VISIBLE);
+ break;
+ }
+ }
+
+ public SurfaceTexture getSurfaceTexture() {
+ return mSurfaceTexture;
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) {
+ mSurfaceTexture = surfaceTexture;
+ mController.onPreviewUIReady();
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) {
+
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ mController.onPreviewUIDestroyed();
+ mSurfaceTexture = null;
+ Log.d(TAG, "surfaceTexture is destroyed");
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+ }
+
+ private void hideDirectionIndicators() {
+ mLeftIndicator.setVisibility(View.INVISIBLE);
+ mRightIndicator.setVisibility(View.INVISIBLE);
+ }
+
+ public Point getPreviewAreaSize() {
+ return new Point(
+ mTextureView.getWidth(), mTextureView.getHeight());
+ }
+
+ public void reset() {
+ mShutterButton.setImageResource(R.drawable.btn_new_shutter);
+ mReviewLayout.setVisibility(View.GONE);
+ mCaptureProgressBar.setVisibility(View.INVISIBLE);
+ }
+
+ public void showFinalMosaic(Bitmap bitmap, int orientation) {
+ if (bitmap != null && orientation != 0) {
+ Matrix rotateMatrix = new Matrix();
+ rotateMatrix.setRotate(orientation);
+ bitmap = Bitmap.createBitmap(
+ bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
+ rotateMatrix, false);
+ }
+
+ mReview.setImageBitmap(bitmap);
+ mCaptureLayout.setVisibility(View.GONE);
+ mReviewLayout.setVisibility(View.VISIBLE);
+ }
+
+ public void onConfigurationChanged(
+ Configuration newConfig, boolean threadRunning) {
+ Drawable lowResReview = null;
+ if (threadRunning) lowResReview = mReview.getDrawable();
+
+ // Change layout in response to configuration change
+ /* TODO (shkong):mCaptureLayout.setOrientation(
+ newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
+ ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);*/
+ LayoutInflater inflater = (LayoutInflater)
+ mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ mPanoLayout.removeView(mReviewLayout);
+ inflater.inflate(R.layout.pano_module_review, mPanoLayout);
+
+ mPanoLayout.bringChildToFront(mCameraControls);
+ setViews(mActivity.getResources());
+ if (threadRunning) {
+ mReview.setImageDrawable(lowResReview);
+ mCaptureLayout.setVisibility(View.GONE);
+ mReviewLayout.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public void resetSavingProgress() {
+ mSavingProgressBar.reset();
+ mSavingProgressBar.setRightIncreasing(true);
+ }
+
+ public void updateSavingProgress(int progress) {
+ mSavingProgressBar.setProgress(progress);
+ }
+
+ @Override
+ public void onShutterButtonFocus(boolean pressed) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onShutterButtonClick() {
+ mController.onShutterButtonClick();
+ }
+
+ @Override
+ public void onLayoutChange(
+ View v, int l, int t, int r, int b,
+ int oldl, int oldt, int oldr, int oldb) {
+ mController.onPreviewUILayoutChange(l, t, r, b);
+ }
+
+ public void showAlertDialog(
+ String title, String failedString,
+ String OKString, Runnable runnable) {
+ mDialogHelper.showAlertDialog(title, failedString, OKString, runnable);
+ }
+
+ public void showWaitingDialog(String title) {
+ mDialogHelper.showWaitingDialog(title);
+ }
+
+ public void dismissAllDialogs() {
+ mDialogHelper.dismissAll();
+ }
+
+ private void createContentView() {
+ LayoutInflater inflator = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflator.inflate(R.layout.panorama_module, mRootView, true);
+ mPanoLayout = (ViewGroup) mRootView.findViewById(R.id.pano_layout);
+ Resources appRes = mActivity.getResources();
+ mIndicatorColor = appRes.getColor(R.color.pano_progress_indication);
+ mReviewBackground = appRes.getColor(R.color.review_background);
+ mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast);
+ mDialogHelper = new DialogHelper();
+ setViews(appRes);
+ }
+
+ private void setViews(Resources appRes) {
+ mCaptureLayout = (FrameLayout) mRootView.findViewById(R.id.panorama_capture_layout);
+ // TODO (shkong): set display change listener properly.
+ ((CameraRootView) mRootView).setDisplayChangeListener(null);
+ mTextureView = (TextureView) mRootView.findViewById(R.id.pano_preview_textureview);
+ mTextureView.setSurfaceTextureListener(this);
+ mTextureView.addOnLayoutChangeListener(this);
+ mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls);
+ mCaptureProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar);
+ mCaptureProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
+ mCaptureProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done));
+ mCaptureProgressBar.setIndicatorColor(mIndicatorColor);
+ mCaptureProgressBar.setIndicatorWidth(20);
+
+ mPreviewBorder = mCaptureLayout.findViewById(R.id.pano_preview_area_border);
+
+ mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator);
+ mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator);
+ mLeftIndicator.setEnabled(false);
+ mRightIndicator.setEnabled(false);
+ mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview);
+
+ mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar);
+ mSavingProgressBar.setIndicatorWidth(0);
+ mSavingProgressBar.setMaxProgress(100);
+ mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
+ mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication));
+
+ mCaptureIndicator = mRootView.findViewById(R.id.pano_capture_indicator);
+
+ mReviewLayout = mRootView.findViewById(R.id.pano_review_layout);
+ mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea);
+ mReview.setBackgroundColor(mReviewBackground);
+ View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button);
+ cancelButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ mController.cancelHighResStitching();
+ }
+ });
+
+ mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
+ mShutterButton.setImageResource(R.drawable.btn_new_shutter);
+ mShutterButton.setOnShutterButtonListener(this);
+ }
+
+ private void showTooFastIndication() {
+ mTooFastPrompt.setVisibility(View.VISIBLE);
+ // The PreviewArea also contains the border for "too fast" indication.
+ mPreviewBorder.setVisibility(View.VISIBLE);
+ mCaptureProgressBar.setIndicatorColor(mIndicatorColorFast);
+ mLeftIndicator.setEnabled(true);
+ mRightIndicator.setEnabled(true);
+ }
+
+ private void hideTooFastIndication() {
+ mTooFastPrompt.setVisibility(View.GONE);
+ mPreviewBorder.setVisibility(View.INVISIBLE);
+ mCaptureProgressBar.setIndicatorColor(mIndicatorColor);
+ mLeftIndicator.setEnabled(false);
+ mRightIndicator.setEnabled(false);
+ }
+
+ private class DialogHelper {
+ private ProgressDialog mProgressDialog;
+ private AlertDialog mAlertDialog;
+
+ DialogHelper() {
+ mProgressDialog = null;
+ mAlertDialog = null;
+ }
+
+ public void dismissAll() {
+ if (mAlertDialog != null) {
+ mAlertDialog.dismiss();
+ mAlertDialog = null;
+ }
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+
+ public void showAlertDialog(
+ CharSequence title, CharSequence message,
+ CharSequence buttonMessage, final Runnable buttonRunnable) {
+ dismissAll();
+ mAlertDialog = (new AlertDialog.Builder(mActivity))
+ .setTitle(title)
+ .setMessage(message)
+ .setNeutralButton(buttonMessage, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ buttonRunnable.run();
+ }
+ })
+ .show();
+ }
+
+ public void showWaitingDialog(CharSequence message) {
+ dismissAll();
+ mProgressDialog = ProgressDialog.show(mActivity, null, message, true, false);
+ }
+ }
+
+ private static class FlipBitmapDrawable extends BitmapDrawable {
+
+ public FlipBitmapDrawable(Resources res, Bitmap bitmap) {
+ super(res, bitmap);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Rect bounds = getBounds();
+ int cx = bounds.centerX();
+ int cy = bounds.centerY();
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.rotate(180, cx, cy);
+ super.draw(canvas);
+ canvas.restore();
+ }
+ }
+}
diff --git a/src/com/android/camera/app/AppManagerFactory.java b/src/com/android/camera/app/AppManagerFactory.java
new file mode 100644
index 0000000..9c047aa
--- /dev/null
+++ b/src/com/android/camera/app/AppManagerFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.app;
+
+import android.app.Application;
+import android.content.Context;
+
+/**
+ * A singleton class which provides application level utility
+ * classes.
+ */
+public class AppManagerFactory {
+
+ private static AppManagerFactory sFactory;
+
+ public static synchronized AppManagerFactory getInstance(Context ctx) {
+ if (sFactory == null) {
+ sFactory = new AppManagerFactory(ctx);
+ }
+ return sFactory;
+ }
+
+ private PanoramaStitchingManager mPanoramaStitchingManager;
+
+ /** No public constructor. */
+ private AppManagerFactory(Context ctx) {
+ mPanoramaStitchingManager = new PanoramaStitchingManager(ctx);
+ }
+
+ public PanoramaStitchingManager getPanoramaStitchingManager() {
+ return mPanoramaStitchingManager;
+ }
+}
diff --git a/src/com/android/camera/app/CameraApp.java b/src/com/android/camera/app/CameraApp.java
index e4da414..2e61fa9 100644
--- a/src/com/android/camera/app/CameraApp.java
+++ b/src/com/android/camera/app/CameraApp.java
@@ -28,3 +28,4 @@ public class CameraApp extends Application {
CameraUtil.initialize(this);
}
}
+
diff --git a/src/com/android/camera/app/OrientationManager.java b/src/com/android/camera/app/OrientationManager.java
index 412be30..7bf9242 100644
--- a/src/com/android/camera/app/OrientationManager.java
+++ b/src/com/android/camera/app/OrientationManager.java
@@ -1,5 +1,20 @@
-package com.android.camera.app;
+/*
+ * 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.app;
import android.app.Activity;
import android.content.ContentResolver;
@@ -13,8 +28,8 @@ import android.view.Surface;
import com.android.camera.util.ApiHelper;
-public class OrientationManager implements OrientationSource {
- private static final String TAG = "OrientationManager";
+public class OrientationManager {
+ private static final String TAG = "CAM_OrientationManager";
// Orientation hysteresis amount used in rounding, in degrees
private static final int ORIENTATION_HYSTERESIS = 5;
@@ -112,12 +127,10 @@ public class OrientationManager implements OrientationSource {
}
}
- @Override
public int getDisplayRotation() {
return getDisplayRotation(mActivity);
}
- @Override
public int getCompensation() {
return 0;
}
@@ -148,4 +161,4 @@ public class OrientationManager implements OrientationSource {
}
return 0;
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/camera/app/OrientationSource.java b/src/com/android/camera/app/OrientationSource.java
deleted file mode 100644
index 57dcfeb..0000000
--- a/src/com/android/camera/app/OrientationSource.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.android.camera.app;
-
-public interface OrientationSource {
- public int getDisplayRotation();
- public int getCompensation();
-}
diff --git a/src/com/android/camera/ui/CameraRootView.java b/src/com/android/camera/ui/CameraRootView.java
index 48f24e4..75d0842 100644
--- a/src/com/android/camera/ui/CameraRootView.java
+++ b/src/com/android/camera/ui/CameraRootView.java
@@ -77,7 +77,9 @@ public class CameraRootView extends FrameLayout {
@Override
public void onDisplayChanged(int arg0) {
- mListener.onDisplayChanged();
+ if (mListener != null) {
+ mListener.onDisplayChanged();
+ }
}
@Override
diff --git a/src/com/android/camera/ui/LayoutChangeHelper.java b/src/com/android/camera/ui/LayoutChangeHelper.java
deleted file mode 100644
index ef4eb6a..0000000
--- a/src/com/android/camera/ui/LayoutChangeHelper.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.ui;
-
-import android.view.View;
-
-public class LayoutChangeHelper implements LayoutChangeNotifier {
- private LayoutChangeNotifier.Listener mListener;
- private boolean mFirstTimeLayout;
- private View mView;
-
- public LayoutChangeHelper(View v) {
- mView = v;
- mFirstTimeLayout = true;
- }
-
- @Override
- public void setOnLayoutChangeListener(LayoutChangeNotifier.Listener listener) {
- mListener = listener;
- }
-
- public void onLayout(boolean changed, int l, int t, int r, int b) {
- if (mListener == null) return;
- if (mFirstTimeLayout || changed) {
- mFirstTimeLayout = false;
- mListener.onLayoutChange(mView, l, t, r, b);
- }
- }
-}
diff --git a/src/com/android/camera/ui/LayoutNotifyView.java b/src/com/android/camera/ui/LayoutNotifyView.java
deleted file mode 100644
index 6e118fc..0000000
--- a/src/com/android/camera/ui/LayoutNotifyView.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-
-/*
- * Customized view to support onLayoutChange() at or before API 10.
- */
-public class LayoutNotifyView extends View implements LayoutChangeNotifier {
- private LayoutChangeHelper mLayoutChangeHelper = new LayoutChangeHelper(this);
-
- public LayoutNotifyView(Context context) {
- super(context);
- }
-
- public LayoutNotifyView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void setOnLayoutChangeListener(
- LayoutChangeNotifier.Listener listener) {
- mLayoutChangeHelper.setOnLayoutChangeListener(listener);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- mLayoutChangeHelper.onLayout(changed, l, t, r, b);
- }
-}
diff --git a/src/com/android/camera/ui/CameraSwitcher.java b/src/com/android/camera/ui/ModuleSwitcher.java
index aaa9cda..5eb316c 100644
--- a/src/com/android/camera/ui/CameraSwitcher.java
+++ b/src/com/android/camera/ui/ModuleSwitcher.java
@@ -34,13 +34,13 @@ import android.view.ViewGroup;
import android.widget.FrameLayout.LayoutParams;
import android.widget.LinearLayout;
+import com.android.camera.util.ApiHelper;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.PhotoSphereHelper;
import com.android.camera.util.UsageStatistics;
import com.android.camera2.R;
-import com.android.camera.util.ApiHelper;
-public class CameraSwitcher extends RotateImageView
+public class ModuleSwitcher extends RotateImageView
implements OnClickListener, OnTouchListener {
@SuppressWarnings("unused")
@@ -49,20 +49,22 @@ public class CameraSwitcher extends RotateImageView
public static final int PHOTO_MODULE_INDEX = 0;
public static final int VIDEO_MODULE_INDEX = 1;
- public static final int LIGHTCYCLE_MODULE_INDEX = 2;
+ public static final int WIDE_ANGLE_PANO_MODULE_INDEX = 2;
+ public static final int LIGHTCYCLE_MODULE_INDEX = 3;
private static final int[] DRAW_IDS = {
R.drawable.ic_switch_camera,
R.drawable.ic_switch_video,
+ R.drawable.ic_switch_pan,
R.drawable.ic_switch_photosphere,
};
- public interface CameraSwitchListener {
- public void onCameraSelected(int i);
+ public interface ModuleSwitchListener {
+ public void onModuleSelected(int i);
public void onShowSwitcherPopup();
}
- private CameraSwitchListener mListener;
+ private ModuleSwitchListener mListener;
private int mCurrentIndex;
private int[] mModuleIds;
private int[] mDrawIds;
@@ -79,12 +81,12 @@ public class CameraSwitcher extends RotateImageView
private AnimatorListener mHideAnimationListener;
private AnimatorListener mShowAnimationListener;
- public CameraSwitcher(Context context) {
+ public ModuleSwitcher(Context context) {
super(context);
init(context);
}
- public CameraSwitcher(Context context, AttributeSet attrs) {
+ public ModuleSwitcher(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
@@ -126,7 +128,7 @@ public class CameraSwitcher extends RotateImageView
setImageResource(mDrawIds[i]);
}
- public void setSwitchListener(CameraSwitchListener l) {
+ public void setSwitchListener(ModuleSwitchListener l) {
mListener = l;
}
@@ -136,14 +138,14 @@ public class CameraSwitcher extends RotateImageView
mListener.onShowSwitcherPopup();
}
- private void onCameraSelected(int ix) {
+ private void onModuleSelected(int ix) {
hidePopup();
if ((ix != mCurrentIndex) && (mListener != null)) {
UsageStatistics.onEvent("CameraModeSwitch", null, null);
UsageStatistics.setPendingTransitionCause(
UsageStatistics.TRANSITION_MENU_TAP);
setCurrentIndex(ix);
- mListener.onCameraSelected(mModuleIds[ix]);
+ mListener.onModuleSelected(mModuleIds[ix]);
}
}
@@ -178,7 +180,7 @@ public class CameraSwitcher extends RotateImageView
@Override
public void onClick(View v) {
if (showsPopup()) {
- onCameraSelected(index);
+ onModuleSelected(index);
}
}
});
@@ -191,6 +193,10 @@ public class CameraSwitcher extends RotateImageView
item.setContentDescription(getContext().getResources().getString(
R.string.accessibility_switch_to_video));
break;
+ case R.drawable.ic_switch_pan:
+ item.setContentDescription(getContext().getResources().getString(
+ R.string.accessibility_switch_to_panorama));
+ break;
case R.drawable.ic_switch_photosphere:
item.setContentDescription(getContext().getResources().getString(
R.string.accessibility_switch_to_photo_sphere));
diff --git a/src_pd/com/android/camera/PanoramaStitchingManager.java b/src_pd/com/android/camera/PanoramaStitchingManager.java
deleted file mode 100644
index 48f67ce..0000000
--- a/src_pd/com/android/camera/PanoramaStitchingManager.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.android.camera;
-
-import android.content.Context;
-import android.net.Uri;
-
-class PanoramaStitchingManager implements ImageTaskManager {
-
- public PanoramaStitchingManager(Context ctx) {
- }
-
- @Override
- public void addTaskListener(TaskListener l) {
- // do nothing.
- }
-
- @Override
- public void removeTaskListener(TaskListener l) {
- // do nothing.
- }
-
- @Override
- public int getTaskProgress(Uri uri) {
- return -1;
- }
-}
diff --git a/src_pd/com/android/camera/app/PanoramaStitchingManager.java b/src_pd/com/android/camera/app/PanoramaStitchingManager.java
new file mode 100644
index 0000000..9d6e796
--- /dev/null
+++ b/src_pd/com/android/camera/app/PanoramaStitchingManager.java
@@ -0,0 +1,43 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.camera.ImageTaskManager;
+
+public class PanoramaStitchingManager implements ImageTaskManager {
+
+ public PanoramaStitchingManager(Context ctx) {
+ }
+
+ @Override
+ public void addTaskListener(TaskListener l) {
+ // do nothing.
+ }
+
+ @Override
+ public void removeTaskListener(TaskListener l) {
+ // do nothing.
+ }
+
+ @Override
+ public int getTaskProgress(Uri uri) {
+ return -1;
+ }
+}