summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml36
-rw-r--r--CleanSpec.mk1
-rw-r--r--gallerycommon/src/com/android/gallery3d/common/ApiHelper.java4
-rw-r--r--res/drawable/filtershow_drawing.pngbin0 -> 1277 bytes
-rw-r--r--res/layout-land/camera_controls.xml (renamed from res/layout-land/photo_module_content.xml)45
-rw-r--r--res/layout-land/camera_shutter_switcher.xml48
-rw-r--r--res/layout-land/filtershow_activity.xml336
-rw-r--r--res/layout-land/pano_module_capture.xml2
-rw-r--r--res/layout-land/review_module_control.xml16
-rw-r--r--res/layout-land/switcher_popup.xml3
-rw-r--r--res/layout-land/video_module.xml58
-rw-r--r--res/layout-port/camera_controls.xml (renamed from res/layout-port/photo_module_content.xml)45
-rw-r--r--res/layout-port/camera_shutter_switcher.xml47
-rw-r--r--res/layout-port/pano_module_capture.xml2
-rw-r--r--res/layout-port/review_module_control.xml16
-rw-r--r--res/layout-port/switcher_popup.xml3
-rw-r--r--res/layout-port/video_module.xml58
-rw-r--r--res/layout/album_set.xml26
-rw-r--r--res/layout/album_set_item.xml42
-rw-r--r--res/layout/camera_main.xml6
-rw-r--r--res/layout/filtershow_activity.xml125
-rw-r--r--res/layout/filtershow_history_operation_row.xml49
-rw-r--r--res/layout/filtershow_imagestate_row.xml18
-rw-r--r--res/layout/panorama_module.xml4
-rw-r--r--res/layout/photo_module.xml20
-rw-r--r--res/layout/photo_set.xml28
-rw-r--r--res/layout/photo_set_item.xml7
-rw-r--r--res/layout/preview_module_frame.xml43
-rw-r--r--res/layout/preview_surface_view.xml20
-rw-r--r--res/layout/video_module.xml (renamed from res/layout/preview_module_frame_video.xml)31
-rw-r--r--res/menu/filtershow_activity_menu.xml7
-rw-r--r--res/menu/gallery.xml23
-rw-r--r--res/values-af/filtershow_strings.xml4
-rw-r--r--res/values-af/strings.xml4
-rw-r--r--res/values-am/filtershow_strings.xml4
-rw-r--r--res/values-am/strings.xml4
-rw-r--r--res/values-ar/filtershow_strings.xml4
-rw-r--r--res/values-ar/strings.xml4
-rw-r--r--res/values-be/filtershow_strings.xml4
-rw-r--r--res/values-be/strings.xml4
-rw-r--r--res/values-bg/filtershow_strings.xml4
-rw-r--r--res/values-bg/strings.xml4
-rw-r--r--res/values-ca/filtershow_strings.xml4
-rw-r--r--res/values-ca/strings.xml4
-rw-r--r--res/values-cs/filtershow_strings.xml4
-rw-r--r--res/values-cs/strings.xml4
-rw-r--r--res/values-da/filtershow_strings.xml4
-rw-r--r--res/values-da/strings.xml4
-rw-r--r--res/values-de/filtershow_strings.xml4
-rw-r--r--res/values-de/strings.xml4
-rw-r--r--res/values-el/filtershow_strings.xml4
-rw-r--r--res/values-el/strings.xml4
-rw-r--r--res/values-en-rGB/filtershow_strings.xml4
-rw-r--r--res/values-en-rGB/strings.xml4
-rw-r--r--res/values-es-rUS/filtershow_strings.xml4
-rw-r--r--res/values-es-rUS/strings.xml4
-rw-r--r--res/values-es/filtershow_strings.xml4
-rw-r--r--res/values-es/strings.xml4
-rw-r--r--res/values-et/filtershow_strings.xml4
-rw-r--r--res/values-et/strings.xml4
-rw-r--r--res/values-fa/filtershow_strings.xml4
-rw-r--r--res/values-fa/strings.xml4
-rw-r--r--res/values-fi/filtershow_strings.xml4
-rw-r--r--res/values-fi/strings.xml4
-rw-r--r--res/values-fr/filtershow_strings.xml4
-rw-r--r--res/values-fr/strings.xml4
-rw-r--r--res/values-hi/filtershow_strings.xml4
-rw-r--r--res/values-hi/strings.xml4
-rw-r--r--res/values-hr/filtershow_strings.xml4
-rw-r--r--res/values-hr/strings.xml4
-rw-r--r--res/values-hu/filtershow_strings.xml4
-rw-r--r--res/values-hu/strings.xml4
-rw-r--r--res/values-in/filtershow_strings.xml4
-rw-r--r--res/values-in/strings.xml4
-rw-r--r--res/values-it/filtershow_strings.xml4
-rw-r--r--res/values-it/strings.xml4
-rw-r--r--res/values-iw/filtershow_strings.xml4
-rw-r--r--res/values-iw/strings.xml4
-rw-r--r--res/values-ja/filtershow_strings.xml4
-rw-r--r--res/values-ja/strings.xml4
-rw-r--r--res/values-ko/filtershow_strings.xml4
-rw-r--r--res/values-ko/strings.xml4
-rw-r--r--res/values-lt/filtershow_strings.xml4
-rw-r--r--res/values-lt/strings.xml4
-rw-r--r--res/values-lv/filtershow_strings.xml4
-rw-r--r--res/values-lv/strings.xml4
-rw-r--r--res/values-ms/filtershow_strings.xml4
-rw-r--r--res/values-ms/strings.xml4
-rw-r--r--res/values-nb/filtershow_strings.xml4
-rw-r--r--res/values-nb/strings.xml4
-rw-r--r--res/values-nl/filtershow_strings.xml4
-rw-r--r--res/values-nl/strings.xml4
-rw-r--r--res/values-pl/filtershow_strings.xml4
-rw-r--r--res/values-pl/strings.xml4
-rw-r--r--res/values-pt-rPT/filtershow_strings.xml4
-rw-r--r--res/values-pt-rPT/strings.xml4
-rw-r--r--res/values-pt/filtershow_strings.xml4
-rw-r--r--res/values-pt/strings.xml4
-rw-r--r--res/values-rm/strings.xml8
-rw-r--r--res/values-ro/filtershow_strings.xml4
-rw-r--r--res/values-ro/strings.xml4
-rw-r--r--res/values-ru/filtershow_strings.xml6
-rw-r--r--res/values-ru/strings.xml4
-rw-r--r--res/values-sk/filtershow_strings.xml4
-rw-r--r--res/values-sk/strings.xml4
-rw-r--r--res/values-sl/filtershow_strings.xml4
-rw-r--r--res/values-sl/strings.xml4
-rw-r--r--res/values-sr/filtershow_strings.xml4
-rw-r--r--res/values-sr/strings.xml4
-rw-r--r--res/values-sv/filtershow_strings.xml4
-rw-r--r--res/values-sv/strings.xml4
-rw-r--r--res/values-sw/filtershow_strings.xml4
-rw-r--r--res/values-sw/strings.xml4
-rw-r--r--res/values-th/filtershow_strings.xml4
-rw-r--r--res/values-th/strings.xml4
-rw-r--r--res/values-tl/filtershow_strings.xml4
-rw-r--r--res/values-tl/strings.xml4
-rw-r--r--res/values-tr/filtershow_strings.xml4
-rw-r--r--res/values-tr/strings.xml4
-rw-r--r--res/values-uk/filtershow_strings.xml4
-rw-r--r--res/values-uk/strings.xml4
-rw-r--r--res/values-v14/styles.xml4
-rw-r--r--res/values-vi/filtershow_strings.xml4
-rw-r--r--res/values-vi/strings.xml4
-rw-r--r--res/values-zh-rCN/filtershow_strings.xml4
-rw-r--r--res/values-zh-rCN/strings.xml4
-rw-r--r--res/values-zh-rTW/filtershow_strings.xml4
-rw-r--r--res/values-zh-rTW/strings.xml4
-rw-r--r--res/values-zu/filtershow_strings.xml4
-rw-r--r--res/values-zu/strings.xml4
-rw-r--r--res/values/dimens.xml4
-rw-r--r--res/values/dimensions.xml6
-rw-r--r--res/values/filtershow_ids.xml4
-rw-r--r--res/values/filtershow_strings.xml2
-rw-r--r--res/values/strings.xml8
-rw-r--r--src/com/android/camera/CameraActivity.java81
-rw-r--r--src/com/android/camera/CameraModule.java2
-rw-r--r--src/com/android/camera/PanoramaModule.java11
-rw-r--r--src/com/android/camera/PhotoModule.java193
-rw-r--r--src/com/android/camera/PreviewFrameLayout.java9
-rwxr-xr-xsrc/com/android/camera/ShutterButton.java3
-rw-r--r--src/com/android/camera/VideoModule.java92
-rw-r--r--src/com/android/camera/ui/CameraSwitcher.java52
-rw-r--r--src/com/android/camera/ui/PieRenderer.java96
-rw-r--r--src/com/android/camera/ui/RotatableLayout.java184
-rw-r--r--src/com/android/gallery3d/data/DataManager.java7
-rw-r--r--src/com/android/gallery3d/filtershow/EditorPlaceHolder.java7
-rw-r--r--src/com/android/gallery3d/filtershow/FilterShowActivity.java529
-rw-r--r--src/com/android/gallery3d/filtershow/HistoryAdapter.java32
-rw-r--r--src/com/android/gallery3d/filtershow/ImageStateAdapter.java13
-rw-r--r--src/com/android/gallery3d/filtershow/MovableLinearLayout.java91
-rw-r--r--src/com/android/gallery3d/filtershow/PanelController.java143
-rw-r--r--src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java16
-rw-r--r--src/com/android/gallery3d/filtershow/cache/ImageLoader.java25
-rw-r--r--src/com/android/gallery3d/filtershow/cache/RenderingRequest.java10
-rw-r--r--src/com/android/gallery3d/filtershow/cache/TripleBufferBitmap.java1
-rw-r--r--src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java1
-rw-r--r--src/com/android/gallery3d/filtershow/editors/BasicEditor.java8
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorCrop.java90
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorFlip.java60
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorInfo.java23
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorRedEye.java1
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorRotate.java60
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorStraighten.java60
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java1
-rw-r--r--src/com/android/gallery3d/filtershow/editors/EditorVignette.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java71
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java19
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilter.java32
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java6
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java5
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java1
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java25
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/grey.rs22
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java37
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java48
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java26
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java1
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java6
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java4
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java4
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java6
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageShow.java49
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java8
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java4
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java34
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/MasterImage.java127
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePreset.java65
-rw-r--r--src/com/android/gallery3d/filtershow/ui/FilterIconButton.java21
-rw-r--r--src/com/android/gallery3d/filtershow/ui/IconButton.java5
-rw-r--r--src/com/android/gallery3d/filtershow/ui/ImageCurves.java1
-rw-r--r--src/com/android/gallery3d/glrenderer/BasicTexture.java4
-rw-r--r--src/com/android/photos/AlbumSetFragment.java157
-rw-r--r--src/com/android/photos/BitmapRegionTileSource.java87
-rw-r--r--src/com/android/photos/FullscreenViewer.java44
-rw-r--r--src/com/android/photos/GalleryActivity.java125
-rw-r--r--src/com/android/photos/PhotoFragment.java25
-rw-r--r--src/com/android/photos/PhotoSetFragment.java136
-rw-r--r--src/com/android/photos/canvas/CanvasActivity.java4
-rw-r--r--src/com/android/photos/canvas/CanvasProvider.java6
-rw-r--r--src/com/android/photos/canvas/CanvasProviderBase.java62
-rw-r--r--src/com/android/photos/data/AlbumSetLoader.java51
-rw-r--r--src/com/android/photos/data/GalleryBitmapPool.java6
-rw-r--r--src/com/android/photos/data/NotificationWatcher.java55
-rw-r--r--src/com/android/photos/data/PhotoDatabase.java166
-rw-r--r--src/com/android/photos/data/PhotoProvider.java543
-rw-r--r--src/com/android/photos/data/PhotoSetLoader.java88
-rw-r--r--src/com/android/photos/data/SQLiteContentProvider.java264
-rw-r--r--src/com/android/photos/drawables/AutoThumbnailDrawable.java309
-rw-r--r--src/com/android/photos/drawables/DataUriThumbnailDrawable.java54
-rw-r--r--src/com/android/photos/drawables/DrawableFactory.java24
-rw-r--r--src/com/android/photos/drawables/MtpThumbnailDrawable.java61
-rw-r--r--src/com/android/photos/shims/BitmapJobDrawable.java158
-rw-r--r--src/com/android/photos/shims/MediaItemsLoader.java148
-rw-r--r--src/com/android/photos/shims/MediaSetLoader.java143
-rw-r--r--src/com/android/photos/views/BlockingGLTextureView.java427
-rw-r--r--src/com/android/photos/views/GalleryThumbnailView.java883
-rw-r--r--src/com/android/photos/views/TiledImageRenderer.java751
-rw-r--r--src/com/android/photos/views/TiledImageView.java269
-rw-r--r--src/com/google/android/pano/data/Cluster.java (renamed from src/com/google/android/canvas/data/Cluster.java)122
-rw-r--r--src/com/google/android/pano/data/util/UriUtils.java (renamed from src/com/google/android/canvas/data/util/UriUtils.java)14
-rw-r--r--src/com/google/android/pano/provider/PanoContract.java (renamed from src/com/google/android/canvas/provider/CanvasContract.java)274
-rw-r--r--src_pd/com/android/gallery3d/filtershow/editors/EditorManager.java4
-rw-r--r--src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java21
-rw-r--r--src_pd/com/android/photos/data/PhotoProviderAuthority.java21
-rw-r--r--tests/AndroidManifest.xml4
-rw-r--r--tests/src/com/android/photos/data/DataTestRunner.java36
-rw-r--r--tests/src/com/android/photos/data/PhotoDatabaseTest.java201
-rw-r--r--tests/src/com/android/photos/data/PhotoDatabaseUtils.java130
-rw-r--r--tests/src/com/android/photos/data/PhotoProviderTest.java359
-rw-r--r--tests/src/com/android/photos/data/TestHelper.java53
240 files changed, 8998 insertions, 1433 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 508aefe9f..abe76d2d5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest android:versionCode="40002"
- android:versionName="1.1.40002"
+<manifest android:versionCode="40003"
+ android:versionName="1.1.40003"
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.gallery3d">
<original-package android:name="com.android.gallery3d" />
- <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="16" />
+ <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" />
<permission android:name="com.android.gallery3d.permission.GALLERY_PROVIDER"
android:protectionLevel="signatureOrSystem" />
@@ -88,8 +88,11 @@
</intent-filter>
</activity>
- <activity android:name="com.android.gallery3d.app.Gallery" android:label="@string/app_name"
- android:configChanges="keyboardHidden|orientation|screenSize">
+ <activity android:name="com.android.photos.GalleryActivity"
+ android:label="@string/app_name"
+ android:configChanges="keyboardHidden|orientation|screenSize"
+ android:theme="@style/Theme.Photos.Gallery"
+ android:uiOptions="splitActionBarWhenNarrow">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
@@ -164,16 +167,12 @@
</intent-filter>
</activity>
- <!-- we add this activity-alias for shortcut backward compatibility -->
- <!-- Note: The alias must put after the target activity -->
- <activity-alias android:name="com.cooliris.media.Gallery"
- android:targetActivity="com.android.gallery3d.app.Gallery"
+ <activity android:name="com.android.photos.FullscreenViewer"
+ android:label="@string/app_name"
android:configChanges="keyboardHidden|orientation|screenSize"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- </intent-filter>
- </activity-alias>
+ android:theme="@style/Theme.Photos.Fullscreen"
+ android:parentActivityName="com.android.photos.GalleryActivity">
+ </activity>
<!-- This activity receives USB_DEVICE_ATTACHED intents and allows importing
media from attached MTP devices, like cameras and camera phones -->
@@ -262,6 +261,11 @@
android:exported="true"
android:permission="com.android.gallery3d.permission.GALLERY_PROVIDER"
android:authorities="com.android.gallery3d.provider" />
+ <provider
+ android:name="com.android.photos.data.PhotoProvider"
+ android:authorities="com.android.gallery3d.photoprovider"
+ android:syncable="false"
+ android:exported="false"/>
<activity android:name="com.android.gallery3d.gadget.WidgetClickHandler" />
<activity android:name="com.android.gallery3d.app.DialogPicker"
android:configChanges="keyboardHidden|orientation|screenSize"
@@ -408,10 +412,10 @@
android:theme="@style/Theme.ProxyLauncher">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="com.google.android.canvas.category.BROWSE_LAUNCHER" />
+ <category android:name="com.google.android.pano.category.BROWSE_LAUNCHER" />
</intent-filter>
<meta-data
- android:name="com.google.android.canvas.data.launcher_info"
+ android:name="com.google.android.canvas.pano.launcher_info"
android:resource="@xml/canvas_info" />
</activity>
</application>
diff --git a/CleanSpec.mk b/CleanSpec.mk
index eff98bb58..3a53b9cd2 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -48,6 +48,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Camera*)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/APPS/Gallery*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Gallery*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Gallery*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Gallery*)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
index 56adcb1e9..4200ec75e 100644
--- a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
+++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
@@ -22,6 +22,7 @@ import android.hardware.Camera;
import android.os.Build;
import android.provider.MediaStore.MediaColumns;
import android.view.View;
+import android.view.WindowManager;
import java.lang.reflect.Field;
@@ -179,6 +180,9 @@ public class ApiHelper {
public static final boolean HAS_GLES20_REQUIRED =
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+ public static final boolean HAS_ROTATION_ANIMATION =
+ hasField(WindowManager.LayoutParams.class, "rotationAnimation");
+
public static int getIntFieldIfExists(Class<?> klass, String fieldName,
Class<?> obj, int defaultVal) {
try {
diff --git a/res/drawable/filtershow_drawing.png b/res/drawable/filtershow_drawing.png
new file mode 100644
index 000000000..566773dbe
--- /dev/null
+++ b/res/drawable/filtershow_drawing.png
Binary files differ
diff --git a/res/layout-land/photo_module_content.xml b/res/layout-land/camera_controls.xml
index d734f8329..f0f3ecca2 100644
--- a/res/layout-land/photo_module_content.xml
+++ b/res/layout-land/camera_controls.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
@@ -13,23 +13,16 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- This layout is shared by phone and tablet in landscape orientation. -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/camera_app"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <include layout="@layout/preview_module_frame"/>
-
- <FrameLayout
- style="@style/CameraControls"
- android:layout_gravity="center" >
+<com.android.camera.ui.RotatableLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/camera_controls"
+ style="@style/CameraControls"
+ android:layout_gravity="center" >
<View
android:id="@+id/blocker"
android:layout_height="match_parent"
android:layout_width="@dimen/switcher_size"
- android:background="@drawable/switcher_bg"
android:clickable="true"
android:layout_gravity="right" />
@@ -40,10 +33,6 @@
android:layout_marginRight="-2dip"
android:layout_gravity="top|right"/>
-
- <include layout="@layout/review_module_control"
- android:layout_marginRight="2dip" />
-
<com.android.camera.ui.PieMenuButton
android:id="@+id/menu"
style="@style/SwitcherButton"
@@ -51,6 +40,22 @@
android:layout_gravity="right|top"
android:layout_marginRight="2dip" />
- </FrameLayout>
-
-</FrameLayout>
+ <com.android.camera.ui.CameraSwitcher
+ android:id="@+id/camera_switcher"
+ style="@style/SwitcherButton"
+ android:layout_gravity="right|bottom"
+ android:layout_marginRight="2dip"
+ android:contentDescription="@string/accessibility_mode_picker" />
+
+ <com.android.camera.ShutterButton
+ android:id="@+id/shutter_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right|center_vertical"
+ android:layout_marginRight="@dimen/shutter_offset"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_shutter_button"
+ android:focusable="true"
+ android:scaleType="center"
+ android:src="@drawable/btn_new_shutter" />
+</com.android.camera.ui.RotatableLayout>
diff --git a/res/layout-land/camera_shutter_switcher.xml b/res/layout-land/camera_shutter_switcher.xml
deleted file mode 100644
index 9c06749c7..000000000
--- a/res/layout-land/camera_shutter_switcher.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/camera_shutter_switcher"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <View
- android:id="@+id/controls"
- style="@style/CameraControls"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true" />
-
- <com.android.camera.ShutterButton
- android:id="@+id/shutter_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:layout_marginRight="@dimen/shutter_offset"
- android:clickable="true"
- android:contentDescription="@string/accessibility_shutter_button"
- android:focusable="true"
- android:scaleType="center"
- android:src="@drawable/btn_new_shutter" />
-
- <com.android.camera.ui.CameraSwitcher
- android:id="@+id/camera_switcher"
- style="@style/SwitcherButton"
- android:layout_alignBottom="@id/controls"
- android:layout_alignParentRight="true"
- android:layout_marginRight="2dip"
- android:contentDescription="@string/accessibility_mode_picker" />
-
-</RelativeLayout> \ No newline at end of file
diff --git a/res/layout-land/filtershow_activity.xml b/res/layout-land/filtershow_activity.xml
new file mode 100644
index 000000000..8cef0cef8
--- /dev/null
+++ b/res/layout-land/filtershow_activity.xml
@@ -0,0 +1,336 @@
+<?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"
+ xmlns:iconbutton="http://schemas.android.com/apk/res/com.android.gallery3d"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/mainView">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:animateLayoutChanges="true">
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <FrameLayout
+ android:id="@+id/editorContainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ <com.android.gallery3d.filtershow.imageshow.ImageShow
+ android:id="@+id/imageShow"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet
+ android:id="@+id/imageTinyPlanet"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/mainPanel"
+ android:layout_width="650dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:id="@+id/imageStatePanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_weight="1"
+ android:visibility="visible" >
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"
+ android:gravity="center"
+ android:padding="2dip"
+ android:text="@string/imageState"
+ android:textColor="@android:color/white"
+ android:textSize="24sp"
+ android:textStyle="bold" />
+
+ <ListView
+ android:id="@+id/imageStateList"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+ </ListView>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/historyPanel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1"
+ android:visibility="gone" >
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"
+ android:gravity="center"
+ android:padding="2dip"
+ android:text="@string/history"
+ android:textColor="@android:color/white"
+ android:textSize="24sp"
+ android:textStyle="bold" />
+
+ <ListView
+ android:id="@+id/operationsList"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+ </ListView>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/resetOperationsButton"
+ style="@style/FilterShowHistoryButton"
+ android:gravity="center"
+ android:text="@string/reset" />
+
+ <Button
+ android:id="@+id/saveOperationsButton"
+ style="@style/FilterShowHistoryButton"
+ android:text="@string/save"
+ android:visibility="gone" />
+ </LinearLayout>
+ </LinearLayout>
+
+
+ <FrameLayout
+ android:layout_gravity="bottom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+
+
+ <ProgressBar
+ android:id="@+id/loading"
+ style="@android:style/Widget.Holo.ProgressBar.Large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:indeterminate="true"
+ android:indeterminateOnly="true"
+ android:background="@color/background_screen" />
+
+ </FrameLayout>
+
+ <com.android.gallery3d.filtershow.CenteredLinearLayout
+ xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
+ android:id="@+id/filtersPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@color/background_main_toolbar"
+ custom:max_width="600dip"
+ android:orientation="vertical">
+
+ <FrameLayout
+ android:id="@+id/secondRowPanel"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" >
+
+ <LinearLayout
+ android:id="@+id/filterButtonsList"
+ android:layout_width="fill_parent"
+ android:layout_height="@dimen/thumbnail_size"
+ android:background="@color/background_main_toolbar"
+ android:orientation="horizontal"
+ android:visibility="gone" >
+
+ <FrameLayout
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+ <LinearLayout
+ android:id="@+id/panelAccessoryViewList"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:orientation="horizontal"
+ android:visibility="visible" />
+
+ <Button
+ android:id="@+id/applyEffect"
+ android:layout_width="wrap_content"
+ android:layout_height="94dip"
+ android:layout_gravity="center"
+ android:layout_weight="1"
+ android:background="@android:color/transparent"
+ android:gravity="center"
+ android:text="@string/apply_effect"
+ android:textSize="18dip" />
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <HorizontalScrollView
+ android:id="@+id/fxList"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/thumbnail_size"
+ android:scrollbars="none" >
+
+ <LinearLayout
+ android:id="@+id/listFilters"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="@dimen/thumbnail_margin"
+ android:orientation="horizontal" >
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <HorizontalScrollView
+ android:id="@+id/bordersList"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/thumbnail_size"
+ android:visibility="gone"
+ android:scrollbars="none" >
+
+ <LinearLayout
+ android:id="@+id/listBorders"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="@dimen/thumbnail_margin"
+ android:orientation="horizontal" >
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <HorizontalScrollView
+ android:id="@+id/geometryList"
+ android:layout_width="fill_parent"
+ android:layout_height="@dimen/thumbnail_size"
+ android:background="@color/background_main_toolbar"
+ android:visibility="gone"
+ android:scrollbars="none" >
+
+ <LinearLayout
+ android:id="@+id/listGeometry"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_gravity="left"
+ android:orientation="horizontal" />
+
+ </HorizontalScrollView>
+
+ <HorizontalScrollView
+ android:id="@+id/colorsFxList"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/background_main_toolbar"
+ android:visibility="gone"
+ android:scrollbars="none" >
+
+ <LinearLayout
+ android:id="@+id/listColorsFx"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/thumbnail_size"
+ android:background="@color/background_main_toolbar"
+ android:layout_marginLeft="@dimen/thumbnail_margin"
+ android:orientation="horizontal" >
+
+ </LinearLayout>
+ </HorizontalScrollView>
+ </FrameLayout>
+
+ <View
+ android:background="@color/toolbar_separation_line"
+ android:layout_height="1dip"
+ android:layout_width="match_parent" />
+
+ <com.android.gallery3d.filtershow.CenteredLinearLayout
+ xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ custom:max_width="400dip"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="48dip"
+ android:background="@color/background_main_toolbar" >
+
+ <ImageButton
+ android:id="@+id/fxButton"
+ android:layout_width="@dimen/thumbnail_size"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/filtershow_button_background"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_photoeditor_effects" />
+
+ <ImageButton
+ android:id="@+id/borderButton"
+ android:layout_width="@dimen/thumbnail_size"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/filtershow_button_background"
+ android:padding="2dip"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_photoeditor_border" />
+
+ <ImageButton
+ android:id="@+id/geometryButton"
+ android:layout_width="@dimen/thumbnail_size"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/filtershow_button_background"
+ android:padding="2dip"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_photoeditor_fix" />
+
+ <ImageButton
+ android:id="@+id/colorsButton"
+ android:layout_width="@dimen/thumbnail_size"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@drawable/filtershow_button_background"
+ android:padding="2dip"
+ android:scaleType="centerInside"
+ android:src="@drawable/ic_photoeditor_color" />
+ </LinearLayout>
+
+ </com.android.gallery3d.filtershow.CenteredLinearLayout>
+
+ </com.android.gallery3d.filtershow.CenteredLinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</FrameLayout>
diff --git a/res/layout-land/pano_module_capture.xml b/res/layout-land/pano_module_capture.xml
index 6cad0bf37..26cbfb1ec 100644
--- a/res/layout-land/pano_module_capture.xml
+++ b/res/layout-land/pano_module_capture.xml
@@ -15,7 +15,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/camera_app_root"
+ android:id="@+id/camera_app"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_gravity="center"
diff --git a/res/layout-land/review_module_control.xml b/res/layout-land/review_module_control.xml
index e732a2c80..9f8b0cd46 100644
--- a/res/layout-land/review_module_control.xml
+++ b/res/layout-land/review_module_control.xml
@@ -13,13 +13,16 @@
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:layout_height="match_parent"
- android:layout_width="match_parent">
- <com.android.camera.ui.RotateImageView android:id="@+id/btn_done"
+<com.android.camera.ui.RotatableLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/CameraControls"
+ android:layout_gravity="right|center_vertical"
+ android:layout_marginRight="2dip">
+ <ImageView android:id="@+id/btn_done"
style="@style/ReviewControlIcon"
android:contentDescription="@string/accessibility_review_ok"
android:visibility="gone"
+ android:scaleType="center"
android:layout_gravity="top|right"
android:background="@drawable/bg_pressed"
android:src="@drawable/ic_menu_done_holo_light" />
@@ -34,11 +37,12 @@
android:background="@drawable/bg_pressed"
android:src="@drawable/ic_btn_shutter_retake" />
- <com.android.camera.ui.RotateImageView android:id="@+id/btn_cancel"
+ <ImageView android:id="@+id/btn_cancel"
style="@style/ReviewControlIcon"
android:contentDescription="@string/accessibility_review_cancel"
android:visibility="gone"
+ android:scaleType="center"
android:layout_gravity="bottom|right"
android:background="@drawable/bg_pressed"
android:src="@drawable/ic_menu_cancel_holo_light" />
-</FrameLayout>
+</com.android.camera.ui.RotatableLayout>
diff --git a/res/layout-land/switcher_popup.xml b/res/layout-land/switcher_popup.xml
index b949f9633..fc2d7bc77 100644
--- a/res/layout-land/switcher_popup.xml
+++ b/res/layout-land/switcher_popup.xml
@@ -18,8 +18,7 @@
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignBottom="@id/camera_switcher"
- android:layout_alignRight="@id/camera_switcher"
+ android:layout_gravity="bottom|right"
android:layout_marginRight="8dip"
android:layout_marginBottom="8dip"
android:paddingLeft="8dip"
diff --git a/res/layout-land/video_module.xml b/res/layout-land/video_module.xml
deleted file mode 100644
index 972a7f901..000000000
--- a/res/layout-land/video_module.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?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.
--->
-<!-- This layout is shared by phone and tablet in landscape orientation. -->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/camera_app_root"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
- <include layout="@layout/preview_module_frame_video"/>
-
- <RelativeLayout
- style="@style/CameraControls"
- android:layout_centerVertical="true" >
-
- <View
- android:id="@+id/blocker"
- android:layout_width="@dimen/switcher_size"
- android:layout_height="match_parent"
- android:background="@drawable/switcher_bg"
- android:layout_alignParentRight="true"
- android:clickable="true" />
-
- <include layout="@layout/menu_indicators"
- android:layout_width="80dip"
- android:layout_height="80dip"
- android:layout_alignParentRight="true"
- android:layout_alignParentTop="true"
- android:layout_marginRight="-2dip"
- android:layout_marginTop="-5dip" />
-
- <include layout="@layout/bg_replacement_training_message" />
-
- <include layout="@layout/review_module_control"
- android:layout_marginRight="2dip" />
-
- <com.android.camera.ui.PieMenuButton
- android:id="@+id/menu"
- style="@style/SwitcherButton"
- android:layout_alignParentRight="true"
- android:layout_alignParentTop="true"
- android:layout_marginRight="2dip"
- android:contentDescription="@string/accessibility_menu_button" />
-
- </RelativeLayout>
-
-</RelativeLayout>
diff --git a/res/layout-port/photo_module_content.xml b/res/layout-port/camera_controls.xml
index a82a7a138..aa15da1ca 100644
--- a/res/layout-port/photo_module_content.xml
+++ b/res/layout-port/camera_controls.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
@@ -13,24 +13,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- This layout is shared by phone and tablet in landscape orientation. -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/camera_app"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <include layout="@layout/preview_module_frame"/>
-
- <FrameLayout
- style="@style/CameraControls"
- android:layout_gravity="center" >
+<com.android.camera.ui.RotatableLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/camera_controls"
+ style="@style/CameraControls"
+ android:layout_gravity="center" >
<View
android:id="@+id/blocker"
android:layout_width="match_parent"
android:layout_height="@dimen/switcher_size"
android:layout_gravity="bottom"
- android:background="@drawable/switcher_bg"
android:clickable="true" />
<include layout="@layout/menu_indicators"
@@ -40,9 +33,6 @@
android:layout_marginBottom="-2dip"
android:layout_marginRight="-5dip" />
- <include layout="@layout/review_module_control"
- android:layout_marginBottom="2dip" />
-
<com.android.camera.ui.PieMenuButton
android:id="@+id/menu"
style="@style/SwitcherButton"
@@ -50,6 +40,23 @@
android:layout_marginBottom="2dip"
android:contentDescription="@string/accessibility_menu_button" />
- </FrameLayout>
-
-</FrameLayout>
+ <com.android.camera.ui.CameraSwitcher
+ android:id="@+id/camera_switcher"
+ style="@style/SwitcherButton"
+ android:layout_gravity="bottom|left"
+ android:layout_marginBottom="2dip"
+ android:contentDescription="@string/accessibility_mode_picker" />
+
+ <com.android.camera.ShutterButton
+ android:id="@+id/shutter_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="@dimen/shutter_offset"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_shutter_button"
+ android:focusable="true"
+ android:scaleType="center"
+ android:src="@drawable/btn_new_shutter" />
+
+</com.android.camera.ui.RotatableLayout> \ No newline at end of file
diff --git a/res/layout-port/camera_shutter_switcher.xml b/res/layout-port/camera_shutter_switcher.xml
deleted file mode 100644
index db73fb080..000000000
--- a/res/layout-port/camera_shutter_switcher.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/camera_shutter_switcher"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <View
- android:id="@+id/controls"
- style="@style/CameraControls"
- android:layout_alignParentBottom="true"
- android:layout_centerHorizontal="true" />
-
- <com.android.camera.ShutterButton
- android:id="@+id/shutter_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_centerHorizontal="true"
- android:layout_marginBottom="@dimen/shutter_offset"
- android:clickable="true"
- android:contentDescription="@string/accessibility_shutter_button"
- android:focusable="true"
- android:scaleType="center"
- android:src="@drawable/btn_new_shutter" />
-
- <com.android.camera.ui.CameraSwitcher
- android:id="@+id/camera_switcher"
- style="@style/SwitcherButton"
- android:layout_alignParentBottom="true"
- android:layout_alignLeft="@id/controls"
- android:layout_marginBottom="2dip"
- android:contentDescription="@string/accessibility_mode_picker" />
-</RelativeLayout> \ No newline at end of file
diff --git a/res/layout-port/pano_module_capture.xml b/res/layout-port/pano_module_capture.xml
index 762447e77..d9c9877a2 100644
--- a/res/layout-port/pano_module_capture.xml
+++ b/res/layout-port/pano_module_capture.xml
@@ -15,7 +15,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/camera_app_root"
+ android:id="@+id/camera_app"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">
diff --git a/res/layout-port/review_module_control.xml b/res/layout-port/review_module_control.xml
index 549775430..3c4280ed2 100644
--- a/res/layout-port/review_module_control.xml
+++ b/res/layout-port/review_module_control.xml
@@ -13,13 +13,16 @@
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:layout_height="match_parent"
- android:layout_width="match_parent">
- <com.android.camera.ui.RotateImageView android:id="@+id/btn_done"
+<com.android.camera.ui.RotatableLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/CameraControls"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="2dip">
+ <ImageView android:id="@+id/btn_done"
style="@style/ReviewControlIcon"
android:contentDescription="@string/accessibility_review_ok"
android:visibility="gone"
+ android:scaleType="center"
android:layout_gravity="right|bottom"
android:background="@drawable/bg_pressed"
android:src="@drawable/ic_menu_done_holo_light" />
@@ -34,11 +37,12 @@
android:background="@drawable/bg_pressed"
android:src="@drawable/ic_btn_shutter_retake" />
- <com.android.camera.ui.RotateImageView android:id="@+id/btn_cancel"
+ <ImageView android:id="@+id/btn_cancel"
style="@style/ReviewControlIcon"
android:contentDescription="@string/accessibility_review_cancel"
android:visibility="gone"
+ android:scaleType="center"
android:layout_gravity="left|bottom"
android:background="@drawable/bg_pressed"
android:src="@drawable/ic_menu_cancel_holo_light" />
-</FrameLayout>
+</com.android.camera.ui.RotatableLayout>
diff --git a/res/layout-port/switcher_popup.xml b/res/layout-port/switcher_popup.xml
index b1481a347..8fe09a361 100644
--- a/res/layout-port/switcher_popup.xml
+++ b/res/layout-port/switcher_popup.xml
@@ -18,8 +18,7 @@
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignBottom="@id/camera_switcher"
- android:layout_alignLeft="@id/camera_switcher"
+ android:layout_gravity="bottom|left"
android:layout_marginLeft="8dip"
android:layout_marginBottom="8dip"
android:paddingLeft="16dip"
diff --git a/res/layout-port/video_module.xml b/res/layout-port/video_module.xml
deleted file mode 100644
index d8a6490d4..000000000
--- a/res/layout-port/video_module.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?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.
--->
-<!-- This layout is shared by phone and tablet in landscape orientation. -->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/camera_app_root"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
- <include layout="@layout/preview_module_frame_video"/>
-
- <RelativeLayout
- style="@style/CameraControls"
- android:layout_centerHorizontal="true" >
-
- <View
- android:id="@+id/blocker"
- android:layout_width="match_parent"
- android:layout_height="@dimen/switcher_size"
- android:background="@drawable/switcher_bg"
- android:clickable="true"
- android:layout_alignParentBottom="true" />
-
- <include layout="@layout/menu_indicators"
- android:layout_width="80dip"
- android:layout_height="80dip"
- android:layout_marginRight="-5dip"
- android:layout_marginBottom="-2dip"
- android:layout_alignParentBottom="true"
- android:layout_alignParentRight="true" />
-
- <include layout="@layout/bg_replacement_training_message"/>
-
- <include layout="@layout/review_module_control"
- android:layout_marginBottom="2dip" />
-
- <com.android.camera.ui.PieMenuButton
- android:id="@+id/menu"
- style="@style/SwitcherButton"
- android:contentDescription="@string/accessibility_menu_button"
- android:layout_alignParentBottom="true"
- android:layout_alignParentRight="true"
- android:layout_marginBottom="2dip" />
-
- </RelativeLayout>
-
-</RelativeLayout>
diff --git a/res/layout/album_set.xml b/res/layout/album_set.xml
new file mode 100644
index 000000000..5ff1d23ff
--- /dev/null
+++ b/res/layout/album_set.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <GridView
+ android:id="@id/android:list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:numColumns="auto_fit"
+ android:columnWidth="@dimen/album_set_item_width"
+ android:stretchMode="columnWidth"
+ android:drawSelectorOnTop="true"
+ android:padding="10dp"
+ android:horizontalSpacing="10dp"
+ android:verticalSpacing="10dp" />
+
+ <TextView
+ android:id="@id/android:empty"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/empty_album" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout/album_set_item.xml b/res/layout/album_set_item.xml
new file mode 100644
index 000000000..bdecd5fd1
--- /dev/null
+++ b/res/layout/album_set_item.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:background="#FFF" >
+
+ <TextView
+ android:id="@+id/album_set_item_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="10dp"
+ android:layout_marginTop="10dp"
+ android:ellipsize="end"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <TextView
+ android:id="@+id/album_set_item_date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignLeft="@+id/album_set_item_title"
+ android:layout_below="@+id/album_set_item_title"
+ android:layout_marginBottom="10dp"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <ImageView
+ android:id="@+id/album_set_item_image"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/album_set_item_image_height"
+ android:layout_below="@+id/album_set_item_date"
+ android:scaleType="centerCrop" />
+
+ <ProgressBar
+ android:id="@+id/album_set_item_upload_progress"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="invisible"
+ android:layout_alignParentBottom="true" />
+</RelativeLayout> \ No newline at end of file
diff --git a/res/layout/camera_main.xml b/res/layout/camera_main.xml
index f5240feed..710e69dbf 100644
--- a/res/layout/camera_main.xml
+++ b/res/layout/camera_main.xml
@@ -21,10 +21,12 @@
<include layout="@layout/gl_root_group" />
<FrameLayout
- android:id="@+id/main_content"
+ android:id="@+id/camera_app_root"
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <include layout="@layout/camera_shutter_switcher" />
+ <include layout="@layout/camera_controls"
+ style="@style/CameraControls"
+ android:layout_centerInParent="true" />
</RelativeLayout> \ No newline at end of file
diff --git a/res/layout/filtershow_activity.xml b/res/layout/filtershow_activity.xml
index 9606d9129..6bdd48710 100644
--- a/res/layout/filtershow_activity.xml
+++ b/res/layout/filtershow_activity.xml
@@ -22,36 +22,16 @@
android:id="@+id/mainView">
<LinearLayout
- android:id="@+id/imageStatePanel"
- android:layout_width="200dip"
- android:layout_height="match_parent"
- android:layout_gravity="right"
- android:orientation="vertical"
- android:visibility="invisible" >
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@android:color/transparent"
- android:gravity="center"
- android:padding="2dip"
- android:text="@string/imageState"
- android:textColor="@android:color/white"
- android:textSize="24sp"
- android:textStyle="bold" />
-
- <ListView
- android:id="@+id/imageStateList"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1" >
- </ListView>
- </LinearLayout>
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:animateLayoutChanges="true">
- <LinearLayout
+ <LinearLayout
android:id="@+id/mainPanel"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:layout_weight="1"
android:orientation="vertical" >
<FrameLayout
@@ -70,41 +50,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
- <com.android.gallery3d.filtershow.imageshow.ImageStraighten
- android:id="@+id/imageStraighten"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone" />
-
- <com.android.gallery3d.filtershow.imageshow.ImageCrop
- android:id="@+id/imageCrop"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone" />
-
- <com.android.gallery3d.filtershow.imageshow.ImageRotate
- android:id="@+id/imageRotate"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone" />
-
- <com.android.gallery3d.filtershow.imageshow.ImageFlip
- android:id="@+id/imageFlip"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone" />
-
<com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet
android:id="@+id/imageTinyPlanet"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
- <com.android.gallery3d.filtershow.imageshow.ImageDraw
- android:id="@+id/imageDraw"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone" />
-
<ProgressBar
android:id="@+id/loading"
style="@android:style/Widget.Holo.ProgressBar.Large"
@@ -209,37 +159,8 @@
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_gravity="left"
- android:orientation="horizontal">
-
- <com.android.gallery3d.filtershow.ui.IconButton
- android:id="@+id/straightenButton"
- android:layout_height="match_parent"
- style="@style/IconButton"
- android:drawableTop="@drawable/filtershow_button_geometry_straighten"
- android:text="@string/straighten" />
-
- <com.android.gallery3d.filtershow.ui.IconButton
- android:id="@+id/cropButton"
- android:layout_height="match_parent"
- style="@style/IconButton"
- android:drawableTop="@drawable/filtershow_button_geometry_crop"
- android:text="@string/crop" />
-
- <com.android.gallery3d.filtershow.ui.IconButton
- android:id="@+id/rotateButton"
- android:layout_height="match_parent"
- style="@style/IconButton"
- android:drawableTop="@drawable/filtershow_button_geometry_rotate"
- android:text="@string/rotate" />
-
- <com.android.gallery3d.filtershow.ui.IconButton
- android:id="@+id/flipButton"
- android:layout_height="match_parent"
- style="@style/IconButton"
- android:drawableTop="@drawable/filtershow_button_geometry_flip"
- android:text="@string/mirror" />
+ android:orientation="horizontal" />
- </LinearLayout>
</HorizontalScrollView>
<HorizontalScrollView
@@ -323,6 +244,38 @@
</com.android.gallery3d.filtershow.CenteredLinearLayout>
</com.android.gallery3d.filtershow.CenteredLinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/imageStatePanel"
+ android:layout_width="400dip"
+ android:layout_height="match_parent"
+ android:layout_gravity="right"
+ android:orientation="vertical"
+ android:layout_weight="1"
+ android:visibility="gone" >
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"
+ android:gravity="center"
+ android:padding="2dip"
+ android:text="@string/imageState"
+ android:textColor="@android:color/white"
+ android:textSize="24sp"
+ android:textStyle="bold" />
+
+ <ListView
+ android:id="@+id/imageStateList"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" >
+ </ListView>
+
+ </LinearLayout>
+
</LinearLayout>
<LinearLayout
diff --git a/res/layout/filtershow_history_operation_row.xml b/res/layout/filtershow_history_operation_row.xml
index dd9b66e93..4042f71db 100644
--- a/res/layout/filtershow_history_operation_row.xml
+++ b/res/layout/filtershow_history_operation_row.xml
@@ -18,40 +18,31 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:padding="10dip"
android:background="@drawable/filtershow_button_background">
<ImageView
- android:id="@+id/selectedMark"
- android:src="@drawable/camera_crop"
- android:background="@android:color/transparent"
- android:layout_width="32dip"
- android:layout_height="match_parent"
- android:scaleType="centerInside"
- android:visibility="invisible"
- >
- </ImageView>
+ android:id="@+id/preview"
+ android:background="@android:color/transparent"
+ android:layout_width="match_parent"
+ android:layout_height="128dip"
+ android:scaleType="centerCrop"
+ android:cropToPadding="true"
+ android:paddingTop="10dip"
+ android:visibility="visible"
+ />
<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/rowTextView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:padding="10dip"
- android:textSize="16dip" >
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/rowTextView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:gravity="center_horizontal"
+ android:padding="5dip"
+ android:textSize="16dip">
</TextView>
- <ImageView
- android:id="@+id/typeMark"
- android:src="@drawable/filtershow_button_origin"
- android:background="@android:color/transparent"
- android:layout_width="32dip"
- android:layout_height="match_parent"
- android:scaleType="centerInside"
- android:paddingRight="4dip"
- android:visibility="visible"
- >
- </ImageView>
-
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/filtershow_imagestate_row.xml b/res/layout/filtershow_imagestate_row.xml
index 2e9b1bf9b..d62f54c29 100644
--- a/res/layout/filtershow_imagestate_row.xml
+++ b/res/layout/filtershow_imagestate_row.xml
@@ -14,13 +14,25 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.gallery3d.filtershow.MovableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="128dip"
android:orientation="horizontal"
android:background="@drawable/filtershow_button_background">
+ <ImageView
+ android:id="@+id/selectedMark"
+ android:src="@drawable/camera_crop"
+ android:background="@android:color/transparent"
+ android:layout_width="32dip"
+ android:layout_height="match_parent"
+ android:scaleType="centerInside"
+ android:visibility="visible"
+ android:layout_weight="1"
+ >
+ </ImageView>
+
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/imagestate_label"
@@ -44,4 +56,4 @@
android:textStyle="bold" >
</TextView>
-</LinearLayout> \ No newline at end of file
+</com.android.gallery3d.filtershow.MovableLinearLayout> \ No newline at end of file
diff --git a/res/layout/panorama_module.xml b/res/layout/panorama_module.xml
index 901bb6b7a..9ecbd07f7 100644
--- a/res/layout/panorama_module.xml
+++ b/res/layout/panorama_module.xml
@@ -14,10 +14,10 @@
limitations under the License.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<merge 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_review" />
-</RelativeLayout>
+</merge>
diff --git a/res/layout/photo_module.xml b/res/layout/photo_module.xml
index b2ad7028f..abf094e27 100644
--- a/res/layout/photo_module.xml
+++ b/res/layout/photo_module.xml
@@ -20,10 +20,20 @@
need to be recreated in onConfigurationChanged from old photo_module to this
layout. -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/camera_app_root"
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:layout_gravity="center">
<include layout="@layout/count_down_to_capture"/>
- <include layout="@layout/photo_module_content"/>
-</FrameLayout> \ No newline at end of file
+
+ <ViewStub android:id="@+id/face_view_stub"
+ android:inflatedId="@+id/face_view"
+ android:layout="@layout/face_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"/>
+ <com.android.camera.ui.RenderOverlay
+ android:id="@+id/render_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</merge> \ No newline at end of file
diff --git a/res/layout/photo_set.xml b/res/layout/photo_set.xml
new file mode 100644
index 000000000..d929cadfb
--- /dev/null
+++ b/res/layout/photo_set.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp" >
+
+ <GridView
+ android:id="@id/android:list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="true"
+ android:numColumns="auto_fit"
+ android:stretchMode="columnWidth"
+ android:columnWidth="200dip"
+ android:horizontalSpacing="4dip"
+ android:verticalSpacing="4dip"
+ android:padding="4dip" />
+
+ <TextView
+ android:id="@id/android:empty"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/empty_album" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/res/layout/photo_set_item.xml b/res/layout/photo_set_item.xml
new file mode 100644
index 000000000..b56184e59
--- /dev/null
+++ b/res/layout/photo_set_item.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="200dip"
+ android:id="@+id/thumbnail">
+
+</ImageView> \ No newline at end of file
diff --git a/res/layout/preview_module_frame.xml b/res/layout/preview_module_frame.xml
deleted file mode 100644
index 66094c9b7..000000000
--- a/res/layout/preview_module_frame.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/frame_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1">
- <com.android.camera.PreviewFrameLayout android:id="@+id/frame"
- android:layout_centerInParent="true"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <include layout="@layout/preview_surface_view"/>
- <ViewStub android:id="@+id/face_view_stub"
- android:inflatedId="@+id/face_view"
- android:layout="@layout/face_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"/>
- <com.android.camera.ui.RenderOverlay
- android:id="@+id/render_overlay"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- </com.android.camera.PreviewFrameLayout>
- <ImageView android:id="@+id/capture_anim_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layerType="hardware"
- android:visibility="gone"/>
-</RelativeLayout>
diff --git a/res/layout/preview_surface_view.xml b/res/layout/preview_surface_view.xml
deleted file mode 100644
index cdaf0eea9..000000000
--- a/res/layout/preview_surface_view.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.
--->
-<com.android.camera.ui.PreviewSurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/preview_surface_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"/>
diff --git a/res/layout/preview_module_frame_video.xml b/res/layout/video_module.xml
index 3418faf19..790f3eb91 100644
--- a/res/layout/preview_module_frame_video.xml
+++ b/res/layout/video_module.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 The Android Open Source Project
+<!-- 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.
@@ -13,16 +13,20 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/frame_layout"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:layout_weight="1">
+<!-- This layout is shared by phone and tablet in landscape orientation. -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/camera_app_root"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent">
<com.android.camera.PreviewFrameLayout android:id="@+id/frame"
android:layout_height="match_parent"
android:layout_width="match_parent"
- android:layout_centerInParent="true">
- <include layout="@layout/preview_surface_view"/>
+ android:layout_gravity="center">
+ <com.android.camera.ui.PreviewSurfaceView
+ android:id="@+id/preview_surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"/>
<FrameLayout android:id="@+id/preview_border"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -41,7 +45,7 @@
android:layout_width="match_parent"
android:visibility="gone"
android:background="@android:color/black"/>
- <com.android.camera.ui.RotateImageView
+ <ImageView
android:id="@+id/btn_play"
style="@style/ReviewControlIcon"
android:layout_centerInParent="true"
@@ -50,11 +54,4 @@
android:onClick="onReviewPlayClicked"/>
</com.android.camera.PreviewFrameLayout>
- <ImageView android:id="@+id/capture_anim_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layerType="hardware"
- android:visibility="gone"/>
-
-</RelativeLayout>
-
+</merge>
diff --git a/res/menu/filtershow_activity_menu.xml b/res/menu/filtershow_activity_menu.xml
index 4ea7d17fd..f62bf3967 100644
--- a/res/menu/filtershow_activity_menu.xml
+++ b/res/menu/filtershow_activity_menu.xml
@@ -2,18 +2,19 @@
<item
android:id="@+id/menu_share"
android:actionProviderClass="android.widget.ShareActionProvider"
- android:showAsAction="always"
+ android:showAsAction="never"
android:enabled="false"
+ android:visible="false"
android:title="@string/share"/>
<item
android:id="@+id/undoButton"
android:icon="@drawable/filtershow_button_undo"
- android:showAsAction="never"
+ android:showAsAction="always"
android:title="@string/filtershow_undo"/>
<item
android:id="@+id/redoButton"
android:icon="@drawable/filtershow_button_redo"
- android:showAsAction="never"
+ android:showAsAction="always"
android:title="@string/filtershow_redo"/>
<item
android:id="@+id/resetHistoryButton"
diff --git a/res/menu/gallery.xml b/res/menu/gallery.xml
new file mode 100644
index 000000000..dc36787c8
--- /dev/null
+++ b/res/menu/gallery.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+ <item
+ android:id="@+id/menu_camera"
+ android:icon="@android:drawable/ic_menu_camera"
+ android:showAsAction="ifRoom"
+ android:title="@string/menu_camera"/>
+ <item
+ android:id="@+id/menu_search"
+ android:icon="@android:drawable/ic_menu_search"
+ android:showAsAction="ifRoom"
+ android:title="@string/menu_search"/>
+ <item
+ android:id="@+id/menu_settings"
+ android:icon="@android:drawable/ic_menu_preferences"
+ android:showAsAction="never"
+ android:title="@string/settings"/>
+ <item
+ android:id="@+id/menu_help"
+ android:icon="@android:drawable/ic_menu_help"
+ android:showAsAction="never"
+ android:title="@string/help"/>
+</menu> \ No newline at end of file
diff --git a/res/values-af/filtershow_strings.xml b/res/values-af/filtershow_strings.xml
index 9f5751eba..0797bfc8e 100644
--- a/res/values-af/filtershow_strings.xml
+++ b/res/values-af/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Kan die beeld nie laai nie!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Stel muurpapier"</string>
<string name="original" msgid="3524493791230430897">"Oorspronklike"</string>
<string name="borders" msgid="2067345080568684614">"Grense"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Ontdoen"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Stel terug"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Huidige prenttoestand"</string>
+ <string name="imageState" msgid="8632586742752891968">"Toegepaste uitwerkings"</string>
<string name="compare_original" msgid="8140838959007796977">"Vergelyk"</string>
<string name="apply_effect" msgid="1218288221200568947">"Pas toe"</string>
<string name="reset_effect" msgid="7712605581024929564">"Stel terug"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Outokleur"</string>
<string name="hue" msgid="6231252147971086030">"Kleur"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Skaduwees"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Ligstrepe"</string>
<string name="curvesRGB" msgid="915010781090477550">"Kurwes"</string>
<string name="vignette" msgid="934721068851885390">"Vinjet"</string>
<string name="redeye" msgid="4508883127049472069">"Rooi oog"</string>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 4dfbaf355..3b74cbd84 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Merk jou foto\'s en video\'s met die liggings waar hulle geneem is."\n\n"Ander programme kan toegang kry tot hierdie inligting saam met jou gestoorde prente."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nee dankie"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Soek"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Foto\'s"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albums"</string>
</resources>
diff --git a/res/values-am/filtershow_strings.xml b/res/values-am/filtershow_strings.xml
index cf72a53ce..87a13e888 100644
--- a/res/values-am/filtershow_strings.xml
+++ b/res/values-am/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"ምስሉን መጫን አልተቻለም!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"ልጥፍ በማዘጋጀት ላይ"</string>
<string name="original" msgid="3524493791230430897">"የመጀመሪያው"</string>
<string name="borders" msgid="2067345080568684614">"ድንበሮች"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"ቀልብስ"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"ዳግም አስጀምር"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"የአሁኑ ምስል ሁኔታ"</string>
+ <string name="imageState" msgid="8632586742752891968">"የተተገበሩ ተጽዕኖዎች"</string>
<string name="compare_original" msgid="8140838959007796977">"አወዳድር"</string>
<string name="apply_effect" msgid="1218288221200568947">"ተግብር"</string>
<string name="reset_effect" msgid="7712605581024929564">"ዳግም አስጀምር"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"ራስ-ቀለም መሙላት"</string>
<string name="hue" msgid="6231252147971086030">"የቀለም ድባብ"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"ጥላዎች"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"ድምቀቶች"</string>
<string name="curvesRGB" msgid="915010781090477550">"ጥምዞች"</string>
<string name="vignette" msgid="934721068851885390">"ቪኜት"</string>
<string name="redeye" msgid="4508883127049472069">"ቀይ አይን"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 85d320d3c..1b302e965 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"ፎቶዎችዎን እና ቪዲዮዎችዎን በተነሱበት አካባቢዎች መለያ ይስጧቸው።"\n\n"ሌሎች መተግበሪያዎች ይህንን መረጃ ከተቀመጡ ምስሎችዎ ጋር ሊደርሱበት ይችላሉ።"</string>
<string name="remember_location_no" msgid="7541394381714894896">"አይ፣ አመሰግናለሁ"</string>
<string name="remember_location_yes" msgid="862884269285964180">"አዎ"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"ካሜራ"</string>
+ <string name="menu_search" msgid="7580008232297437190">"ፍለጋ"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"ፎቶዎች"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"አልበሞች"</string>
</resources>
diff --git a/res/values-ar/filtershow_strings.xml b/res/values-ar/filtershow_strings.xml
index ee1bfca2f..9801326b4 100644
--- a/res/values-ar/filtershow_strings.xml
+++ b/res/values-ar/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"لا يمكن تحميل الصورة!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"جارٍ تعيين الخلفية"</string>
<string name="original" msgid="3524493791230430897">"أصلية"</string>
<string name="borders" msgid="2067345080568684614">"حدود"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"تراجع"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"إعادة تعيين"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"حالة الصورة الحالية"</string>
+ <string name="imageState" msgid="8632586742752891968">"التأثيرات المطبقة"</string>
<string name="compare_original" msgid="8140838959007796977">"مقارنة"</string>
<string name="apply_effect" msgid="1218288221200568947">"تطبيق"</string>
<string name="reset_effect" msgid="7712605581024929564">"إعادة تعيين"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"لون تلقائي"</string>
<string name="hue" msgid="6231252147971086030">"تدرج اللون"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"ظلال"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"تسليط الضوء"</string>
<string name="curvesRGB" msgid="915010781090477550">"المنحنيات"</string>
<string name="vignette" msgid="934721068851885390">"نقوش صورة نصفية"</string>
<string name="redeye" msgid="4508883127049472069">"العين الحمراء"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 98ebb6532..bf643cfe3 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"ضع علامة على الصور ومقاطع الفيديو التابعة لك تشير إلى المواقع التي تم التقاطها منها."\n\n"يمكن لتطبيقات أخرى الدخول إلى هذه المعلومات إلى جانب صورك المحفوظة."</string>
<string name="remember_location_no" msgid="7541394381714894896">"لا، شكرًا"</string>
<string name="remember_location_yes" msgid="862884269285964180">"نعم"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"الكاميرا"</string>
+ <string name="menu_search" msgid="7580008232297437190">"البحث"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"الصور"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"الألبومات"</string>
</resources>
diff --git a/res/values-be/filtershow_strings.xml b/res/values-be/filtershow_strings.xml
index 06af90467..fd1caee53 100644
--- a/res/values-be/filtershow_strings.xml
+++ b/res/values-be/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Не атрымлiваецца загрузіць малюнак"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Усталёўка шпалер..."</string>
<string name="original" msgid="3524493791230430897">"Арыгiнал"</string>
<string name="borders" msgid="2067345080568684614">"Межы"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Вярнуць"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Скінуць"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Бягучы стан малюнка"</string>
+ <string name="imageState" msgid="8632586742752891968">"Прымененыя эфекты"</string>
<string name="compare_original" msgid="8140838959007796977">"Параўнаць"</string>
<string name="apply_effect" msgid="1218288221200568947">"Паспрабаваць"</string>
<string name="reset_effect" msgid="7712605581024929564">"Скінуць"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Aўтаколер"</string>
<string name="hue" msgid="6231252147971086030">"Тон"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Цені"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Блікі"</string>
<string name="curvesRGB" msgid="915010781090477550">"Пэндзаль"</string>
<string name="vignette" msgid="934721068851885390">"Віньетка"</string>
<string name="redeye" msgid="4508883127049472069">"Чырвонае вока"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index fa6bd3f18..d25951293 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Адзначце на вашых фатаграфіях і відэа месцы, у якіх яны былі створаныя."\n\n"Іншыя праграмы могуць атрымаць доступ да гэтай інфармацыі разам з захаванымі выявамі."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Не, дзякуй"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Так"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Пошук"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Фатаграфіі"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Альбомы"</string>
</resources>
diff --git a/res/values-bg/filtershow_strings.xml b/res/values-bg/filtershow_strings.xml
index cc4daae25..1c907d4cf 100644
--- a/res/values-bg/filtershow_strings.xml
+++ b/res/values-bg/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Изображението не може да се зареди!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Тапетът се задава"</string>
<string name="original" msgid="3524493791230430897">"Оригинал"</string>
<string name="borders" msgid="2067345080568684614">"Контури"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Отмяна"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Повторно задаване"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Текущо състояние на изображението"</string>
+ <string name="imageState" msgid="8632586742752891968">"Приложени ефекти"</string>
<string name="compare_original" msgid="8140838959007796977">"Сравняване"</string>
<string name="apply_effect" msgid="1218288221200568947">"Прилагане"</string>
<string name="reset_effect" msgid="7712605581024929564">"Нулиране"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Авт. цвят"</string>
<string name="hue" msgid="6231252147971086030">"Нюанс"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Засенчване"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Просветляване"</string>
<string name="curvesRGB" msgid="915010781090477550">"Извивки"</string>
<string name="vignette" msgid="934721068851885390">"Винетиране"</string>
<string name="redeye" msgid="4508883127049472069">"Червени очи"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 7f870a530..650aa223a 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Поставете в снимките и видеоклиповете си маркери с местоположенията, на които са направени."\n\n"Другите приложения могат да осъществяват достъп до тази информация, както и до запазените ви изображения."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Не, благодаря"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Да"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Търсене"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Снимки"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Албуми"</string>
</resources>
diff --git a/res/values-ca/filtershow_strings.xml b/res/values-ca/filtershow_strings.xml
index 5f4b8e1a8..242464c91 100644
--- a/res/values-ca/filtershow_strings.xml
+++ b/res/values-ca/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"No es pot carregar la imatge."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"S\'està establint el fons de pantalla"</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Vores"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Desfés"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Restableix"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estat de la imatge actual"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efectes aplicats"</string>
<string name="compare_original" msgid="8140838959007796977">"Compara"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplica"</string>
<string name="reset_effect" msgid="7712605581024929564">"Restableix"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autocolor"</string>
<string name="hue" msgid="6231252147971086030">"To de color"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Ombres"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Punts brillants"</string>
<string name="curvesRGB" msgid="915010781090477550">"Corbes"</string>
<string name="vignette" msgid="934721068851885390">"Vinyeta"</string>
<string name="redeye" msgid="4508883127049472069">"Ulls vermells"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 1a9eb6ad0..e6fd8301d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Etiqueta les teves fotos i els teus vídeos amb les ubicacions des de les quals es fan."\n\n"Altres aplicacions podran accedir a aquesta informació juntament amb les imatges desades."</string>
<string name="remember_location_no" msgid="7541394381714894896">"No, gràcies"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Sí"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Càmera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Cerca"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Àlbums"</string>
</resources>
diff --git a/res/values-cs/filtershow_strings.xml b/res/values-cs/filtershow_strings.xml
index e0c59f5ba..d8681f75f 100644
--- a/res/values-cs/filtershow_strings.xml
+++ b/res/values-cs/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Obrázek nelze načíst!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Nastavování tapety"</string>
<string name="original" msgid="3524493791230430897">"Původní"</string>
<string name="borders" msgid="2067345080568684614">"Okraje"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Vrátit zpět"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Obnovit"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktuální stav obrázku"</string>
+ <string name="imageState" msgid="8632586742752891968">"Použité efekty"</string>
<string name="compare_original" msgid="8140838959007796977">"Porovnat"</string>
<string name="apply_effect" msgid="1218288221200568947">"Použít"</string>
<string name="reset_effect" msgid="7712605581024929564">"Obnovit"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autom. barva"</string>
<string name="hue" msgid="6231252147971086030">"Odstín"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Stíny"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Světlá místa"</string>
<string name="curvesRGB" msgid="915010781090477550">"Křivky"</string>
<string name="vignette" msgid="934721068851885390">"Viněta"</string>
<string name="redeye" msgid="4508883127049472069">"Červené oči"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 6702e86a5..f5449de0f 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Přidejte do fotek a videí označení míst, kde jste je pořídili."\n\n"Ostatní aplikace budou mít k těmto informacím přístup společně s přístupem k uloženým obrázkům."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Ne, děkuji"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ano"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Fotoaparát"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Hledat"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotky"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Alba"</string>
</resources>
diff --git a/res/values-da/filtershow_strings.xml b/res/values-da/filtershow_strings.xml
index bc5ed33d9..38f969872 100644
--- a/res/values-da/filtershow_strings.xml
+++ b/res/values-da/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Billedet kan ikke indlæses."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Angiver baggrund"</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Rammer"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Fortryd"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Nulstil"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Nuværende billedtilstand"</string>
+ <string name="imageState" msgid="8632586742752891968">"Anvendte effekter"</string>
<string name="compare_original" msgid="8140838959007796977">"Sammenlign"</string>
<string name="apply_effect" msgid="1218288221200568947">"Anvend"</string>
<string name="reset_effect" msgid="7712605581024929564">"Nulstil"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Automatisk farve"</string>
<string name="hue" msgid="6231252147971086030">"Nuance"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Skygger"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Fremhævninger"</string>
<string name="curvesRGB" msgid="915010781090477550">"Kurver"</string>
<string name="vignette" msgid="934721068851885390">"Vignet"</string>
<string name="redeye" msgid="4508883127049472069">"Røde øjne"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index de3dbaab1..e182e9638 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -387,4 +387,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Tag dine fotos og videoer med de placeringer, hvor de blev taget."\n\n"Andre apps kan få adgang til disse oplysninger sammen med dine gemte billeder."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nej tak"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Søg"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albummer"</string>
</resources>
diff --git a/res/values-de/filtershow_strings.xml b/res/values-de/filtershow_strings.xml
index 0c660bc2b..555d53fa0 100644
--- a/res/values-de/filtershow_strings.xml
+++ b/res/values-de/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Bild kann nicht geladen werden."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Hintergrund wird festgelegt..."</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Rahmen"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Rückgängig machen"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Zurücksetzen"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktueller Bildstatus"</string>
+ <string name="imageState" msgid="8632586742752891968">"Angewendete Effekte"</string>
<string name="compare_original" msgid="8140838959007796977">"Vergleichen"</string>
<string name="apply_effect" msgid="1218288221200568947">"Übernehmen"</string>
<string name="reset_effect" msgid="7712605581024929564">"Zurücksetzen"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autom. Farbe"</string>
<string name="hue" msgid="6231252147971086030">"Farbton"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Schatten"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Highlights"</string>
<string name="curvesRGB" msgid="915010781090477550">"Kurven"</string>
<string name="vignette" msgid="934721068851885390">"Vignettierung"</string>
<string name="redeye" msgid="4508883127049472069">"Rote Augen"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 79860a2f4..be51b2ccc 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Taggen Sie Ihre Fotos und Videos mit den Standorten, an denen sie aufgenommen wurden."\n\n"Andere Apps können auf diese Informationen und Ihre gespeicherten Bilder zugreifen."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nein"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Suchen"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Alben"</string>
</resources>
diff --git a/res/values-el/filtershow_strings.xml b/res/values-el/filtershow_strings.xml
index 40acfdc58..b22363f7d 100644
--- a/res/values-el/filtershow_strings.xml
+++ b/res/values-el/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Δεν είναι δυνατή η φόρτωση της εικόνας!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Ορισμός ταπετσαρίας…"</string>
<string name="original" msgid="3524493791230430897">"Αρχική"</string>
<string name="borders" msgid="2067345080568684614">"Σύνορα"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Αναίρεση"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Επαναφορά"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Κατάσταση τρέχουσας εικόνας"</string>
+ <string name="imageState" msgid="8632586742752891968">"Εφαρμοσμένα εφέ"</string>
<string name="compare_original" msgid="8140838959007796977">"Σύγκριση"</string>
<string name="apply_effect" msgid="1218288221200568947">"Εφαρμογή"</string>
<string name="reset_effect" msgid="7712605581024929564">"Επαναφορά"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Αυτόματο χρώμα"</string>
<string name="hue" msgid="6231252147971086030">"Απόχρωση"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Σκιές"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Φωτεινά σημεία"</string>
<string name="curvesRGB" msgid="915010781090477550">"Καμπύλες"</string>
<string name="vignette" msgid="934721068851885390">"Βινιετάρισμα"</string>
<string name="redeye" msgid="4508883127049472069">"Κόκκινα μάτια"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 08c39d497..33e654a28 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Προσθέστε ετικέτες στις φωτογραφίες και τα βίντεό σας με την τοποθεσία στην οποία έχουν τραβηχτεί."\n\n"Οι άλλες εφαρμογές μπορούν να αποκτήσουν πρόσβαση σε αυτές τις πληροφορίες μαζί με τις αποθηκευμένες σας εικόνες."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Όχι ευχαριστώ"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ναι"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Κάμερα"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Αναζήτηση"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Φωτογραφίες"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Λεύκωμα"</string>
</resources>
diff --git a/res/values-en-rGB/filtershow_strings.xml b/res/values-en-rGB/filtershow_strings.xml
index ad6e4e044..fcaf45cd6 100644
--- a/res/values-en-rGB/filtershow_strings.xml
+++ b/res/values-en-rGB/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Cannot load the image!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Setting wallpaper"</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Borders"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Undo"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Reset"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Current Image State"</string>
+ <string name="imageState" msgid="8632586742752891968">"Applied Effects"</string>
<string name="compare_original" msgid="8140838959007796977">"Compare"</string>
<string name="apply_effect" msgid="1218288221200568947">"Apply"</string>
<string name="reset_effect" msgid="7712605581024929564">"Reset"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autocolour"</string>
<string name="hue" msgid="6231252147971086030">"Hue"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Shadows"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Highlights"</string>
<string name="curvesRGB" msgid="915010781090477550">"Curves"</string>
<string name="vignette" msgid="934721068851885390">"Vignette"</string>
<string name="redeye" msgid="4508883127049472069">"Red Eye"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 587aa5444..4dc38685e 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Tag your photos and videos with the locations where they are taken."\n\n"Other apps can access this information along with your saved images."</string>
<string name="remember_location_no" msgid="7541394381714894896">"No thanks"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Yes"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Camera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Search"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Photos"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albums"</string>
</resources>
diff --git a/res/values-es-rUS/filtershow_strings.xml b/res/values-es-rUS/filtershow_strings.xml
index e3a9a8e1b..4efffad3f 100644
--- a/res/values-es-rUS/filtershow_strings.xml
+++ b/res/values-es-rUS/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"No se puede cargar la imagen."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Estableciendo fondo de pantalla..."</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Bordes"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Deshacer"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Restablecer"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estado actual de la imagen"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efectos aplicados"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
<string name="reset_effect" msgid="7712605581024929564">"Restablecer"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Color autom."</string>
<string name="hue" msgid="6231252147971086030">"Tono"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Zonas brillant."</string>
<string name="curvesRGB" msgid="915010781090477550">"Curvas"</string>
<string name="vignette" msgid="934721068851885390">"Viñeta"</string>
<string name="redeye" msgid="4508883127049472069">"Ojos rojos"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 610aa59a4..e216ece53 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Etiqueta tus fotos y videos con la ubicación donde fueron tomados."\n\n"Otras aplicaciones pueden acceder a esta información junto con tus imágenes guardadas."</string>
<string name="remember_location_no" msgid="7541394381714894896">"No, gracias"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Sí"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Cámara"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Búsqueda"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Álbumes"</string>
</resources>
diff --git a/res/values-es/filtershow_strings.xml b/res/values-es/filtershow_strings.xml
index e3820fa67..ebd8c2c2e 100644
--- a/res/values-es/filtershow_strings.xml
+++ b/res/values-es/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Error al cargar la imagen"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Estableciendo fondo de pantalla..."</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Margen"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Deshacer"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Restablecer"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estado de imagen actual"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efectos aplicados"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
<string name="reset_effect" msgid="7712605581024929564">"Restablecer"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Color automático"</string>
<string name="hue" msgid="6231252147971086030">"Tonalidad"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Lo más destacado"</string>
<string name="curvesRGB" msgid="915010781090477550">"Curvar"</string>
<string name="vignette" msgid="934721068851885390">"Viñeta"</string>
<string name="redeye" msgid="4508883127049472069">"Ojos rojos"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 47480cb72..1c22b31e5 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Etiqueta tus fotos y vídeos con las ubicaciones donde se han realizado."\n\n"Otras aplicaciones pueden acceder a esta información, así como a las imágenes guardadas."</string>
<string name="remember_location_no" msgid="7541394381714894896">"No, gracias"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Sí"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Cámara"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Buscar"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Álbumes"</string>
</resources>
diff --git a/res/values-et/filtershow_strings.xml b/res/values-et/filtershow_strings.xml
index d7be6e515..161ac9048 100644
--- a/res/values-et/filtershow_strings.xml
+++ b/res/values-et/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Pilti ei saa laadida!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Taustapildi määramine"</string>
<string name="original" msgid="3524493791230430897">"Originaal"</string>
<string name="borders" msgid="2067345080568684614">"Äärised"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Võta tagasi"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Lähtesta"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Pildi praegune olek"</string>
+ <string name="imageState" msgid="8632586742752891968">"Rakendatud efektid"</string>
<string name="compare_original" msgid="8140838959007796977">"Võrdle"</string>
<string name="apply_effect" msgid="1218288221200568947">"Rakenda"</string>
<string name="reset_effect" msgid="7712605581024929564">"Lähtesta"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autom. värvid"</string>
<string name="hue" msgid="6231252147971086030">"Värvitoon"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Varjud"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Esiletõstmine"</string>
<string name="curvesRGB" msgid="915010781090477550">"Kõverad"</string>
<string name="vignette" msgid="934721068851885390">"Vinjett"</string>
<string name="redeye" msgid="4508883127049472069">"Punasilmsus"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index a4b5a91ef..a56bd389f 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Märkige oma fotodele ja videotele jäädvustamise asukoht."\n\n"Muud rakendused pääsevad lisaks salvestatud piltidele juurde ka sellele teabele."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Ei, tänan"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Jah"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kaamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Otsing"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotod"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumid"</string>
</resources>
diff --git a/res/values-fa/filtershow_strings.xml b/res/values-fa/filtershow_strings.xml
index 30516122c..db3dae423 100644
--- a/res/values-fa/filtershow_strings.xml
+++ b/res/values-fa/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"تصویر بارگیری نمی‌شود!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"تنظیم تصویر زمینه"</string>
<string name="original" msgid="3524493791230430897">"اصلی"</string>
<string name="borders" msgid="2067345080568684614">"حاشیه‌ها"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"لغو عمل"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"بازنشانی"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"وضعیت کنونی تصویر"</string>
+ <string name="imageState" msgid="8632586742752891968">"جلوه‌های اعمال شده"</string>
<string name="compare_original" msgid="8140838959007796977">"مقایسه"</string>
<string name="apply_effect" msgid="1218288221200568947">"اعمال‌ کردن"</string>
<string name="reset_effect" msgid="7712605581024929564">"بازنشانی"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"رنگ خودکار"</string>
<string name="hue" msgid="6231252147971086030">"رنگ‌مایه"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"سایه‌ها"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"هایلایت"</string>
<string name="curvesRGB" msgid="915010781090477550">"نمودارها"</string>
<string name="vignette" msgid="934721068851885390">"محو لبه‌ها"</string>
<string name="redeye" msgid="4508883127049472069">"قرمزی چشم"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index a2e0eb708..1ed9d0f19 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"محل گرفتن عکس‌ها و ویدیوها را به آنها برچسب کنید."\n\n" سایر برنامه‌ها می‌توانند به این اطلاعات در کنار تصاویر ذخیره شده شما دسترسی پیدا کنند."</string>
<string name="remember_location_no" msgid="7541394381714894896">"نه متشکرم"</string>
<string name="remember_location_yes" msgid="862884269285964180">"بله"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"دوربین"</string>
+ <string name="menu_search" msgid="7580008232297437190">"جستجو"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"عکس‌ها"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"آلبوم‌ها"</string>
</resources>
diff --git a/res/values-fi/filtershow_strings.xml b/res/values-fi/filtershow_strings.xml
index 1c9491cc2..92892a68f 100644
--- a/res/values-fi/filtershow_strings.xml
+++ b/res/values-fi/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Kuvaa ei voi ladata."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Asetetaan taustakuvaa"</string>
<string name="original" msgid="3524493791230430897">"Alkuperäinen"</string>
<string name="borders" msgid="2067345080568684614">"Reunukset"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Kumoa"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Palauta"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Kuvan nykyinen tila"</string>
+ <string name="imageState" msgid="8632586742752891968">"Käytetyt tehosteet"</string>
<string name="compare_original" msgid="8140838959007796977">"Vertaa"</string>
<string name="apply_effect" msgid="1218288221200568947">"Käytä"</string>
<string name="reset_effect" msgid="7712605581024929564">"Palauta"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autom. värit"</string>
<string name="hue" msgid="6231252147971086030">"Sävy"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Tummat alueet"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Valokohdat"</string>
<string name="curvesRGB" msgid="915010781090477550">"Valotuskäyrät"</string>
<string name="vignette" msgid="934721068851885390">"Vinjetti"</string>
<string name="redeye" msgid="4508883127049472069">"Punasilmäisyys"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index d769c32c2..f60f7873d 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Merkitse valokuviin ja videoihin niiden kuvauspaikat."\n\n"Muut sovellukset voivat käyttää näitä tietoja tallennettujen kuviesi yhteydessä."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Ei kiitos"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Kyllä"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Haku"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Kuvat"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumit"</string>
</resources>
diff --git a/res/values-fr/filtershow_strings.xml b/res/values-fr/filtershow_strings.xml
index 868e61ac0..24685c02c 100644
--- a/res/values-fr/filtershow_strings.xml
+++ b/res/values-fr/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Impossible de charger l\'image."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Définition du fond d\'écran en cours…"</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Contours"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Annuler"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Réinitialiser"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"État actuel de l\'image"</string>
+ <string name="imageState" msgid="8632586742752891968">"Effets appliqués"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparer"</string>
<string name="apply_effect" msgid="1218288221200568947">"Appliquer"</string>
<string name="reset_effect" msgid="7712605581024929564">"Réinitialiser"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Coloration auto"</string>
<string name="hue" msgid="6231252147971086030">"Teinte"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Ombres"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Reflets"</string>
<string name="curvesRGB" msgid="915010781090477550">"Courbes"</string>
<string name="vignette" msgid="934721068851885390">"Vignetage"</string>
<string name="redeye" msgid="4508883127049472069">"Yeux rouges"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 19014f8de..dfa310bd7 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Ajoutez des tags à vos photos et à vos vidéos pour identifier l\'endroit de la prise de vue."\n\n"D\'autres applications peuvent accéder à ces informations, ainsi qu\'aux images enregistrées."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Non, merci"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Oui"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Appareil photo"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Recherche"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Photos"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albums"</string>
</resources>
diff --git a/res/values-hi/filtershow_strings.xml b/res/values-hi/filtershow_strings.xml
index 88ac1a440..6029cd1fe 100644
--- a/res/values-hi/filtershow_strings.xml
+++ b/res/values-hi/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"चित्र लोड नहीं हो सकता!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"वॉलपेपर सेट हो रहा है"</string>
<string name="original" msgid="3524493791230430897">"मूल"</string>
<string name="borders" msgid="2067345080568684614">"बॉर्डर"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"पूर्ववत करें"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"रीसेट करें"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"वर्तमान चित्र स्थिति"</string>
+ <string name="imageState" msgid="8632586742752891968">"लागू किए गए प्रभाव"</string>
<string name="compare_original" msgid="8140838959007796977">"तुलना करें"</string>
<string name="apply_effect" msgid="1218288221200568947">"लागू करें"</string>
<string name="reset_effect" msgid="7712605581024929564">"रीसेट करें"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"ऑटोकलर"</string>
<string name="hue" msgid="6231252147971086030">"ह्यू"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"छाया"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"हाइलाइट"</string>
<string name="curvesRGB" msgid="915010781090477550">"वक्र"</string>
<string name="vignette" msgid="934721068851885390">"विनेट"</string>
<string name="redeye" msgid="4508883127049472069">"रेड आई"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 307430de3..27e3a112a 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"अपने फ़ोटो और वीडियो को उन स्थानों के साथ टैग करें जहां वे लिए गए हैं."\n\n"अन्य एप्लिकेशन आपके सहेजे गए चित्रों सहित इस जानकारी का उपयोग कर सकते हैं."</string>
<string name="remember_location_no" msgid="7541394381714894896">"नहीं, धन्‍यवाद"</string>
<string name="remember_location_yes" msgid="862884269285964180">"हां"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"कैमरा"</string>
+ <string name="menu_search" msgid="7580008232297437190">"खोज"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"फ़ोटो"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"एल्बम"</string>
</resources>
diff --git a/res/values-hr/filtershow_strings.xml b/res/values-hr/filtershow_strings.xml
index ee4b51d4d..459a08ec1 100644
--- a/res/values-hr/filtershow_strings.xml
+++ b/res/values-hr/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Nije moguće učitati sliku!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Postavljanje pozadinske slike"</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Obrubi"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Poništi"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Poništi"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Trenutačno stanje slike"</string>
+ <string name="imageState" msgid="8632586742752891968">"Primijenjeni efekti"</string>
<string name="compare_original" msgid="8140838959007796977">"Usporedi"</string>
<string name="apply_effect" msgid="1218288221200568947">"Primijeni"</string>
<string name="reset_effect" msgid="7712605581024929564">"Poništi"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Automatska boja"</string>
<string name="hue" msgid="6231252147971086030">"Nijansa"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Sjenke"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Isticanja"</string>
<string name="curvesRGB" msgid="915010781090477550">"Krivulje"</string>
<string name="vignette" msgid="934721068851885390">"Vinjeta"</string>
<string name="redeye" msgid="4508883127049472069">"Crvene oči"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index cff01be45..88065e503 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Dodajte svojim fotografijama i videozapisima oznake lokacija na kojima su snimljeni."\n\n"Ostale aplikacije mogu pristupiti tim podacima s vašim spremljenim slikama."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Ne, hvala"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Da"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Fotoaparat"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Pretraživanje"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotografije"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumi"</string>
</resources>
diff --git a/res/values-hu/filtershow_strings.xml b/res/values-hu/filtershow_strings.xml
index cb0a78e20..504eabcc8 100644
--- a/res/values-hu/filtershow_strings.xml
+++ b/res/values-hu/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Nem sikerült betölteni a képet!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Háttérkép beállítása"</string>
<string name="original" msgid="3524493791230430897">"Eredeti"</string>
<string name="borders" msgid="2067345080568684614">"Szegélyek"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Visszavonás"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Visszaállítás"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktuális fényképállapot"</string>
+ <string name="imageState" msgid="8632586742752891968">"Alkalmazott hatások"</string>
<string name="compare_original" msgid="8140838959007796977">"Összehasonlítás"</string>
<string name="apply_effect" msgid="1218288221200568947">"Alkalmaz"</string>
<string name="reset_effect" msgid="7712605581024929564">"Visszaállítás"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Automat. szín"</string>
<string name="hue" msgid="6231252147971086030">"Színárnyalat"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Árnyékok"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Kiemelések"</string>
<string name="curvesRGB" msgid="915010781090477550">"Görbék"</string>
<string name="vignette" msgid="934721068851885390">"Vignetta"</string>
<string name="redeye" msgid="4508883127049472069">"Vörösszem"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 79cc85427..d7bdc0a10 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Címkézze fel a fotókat és videókat a hellyel, ahol készítette őket."\n\n"Más alkalmazások hozzáférnek ehhez az információhoz és a mentett képekhez."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nem, köszönöm."</string>
<string name="remember_location_yes" msgid="862884269285964180">"Igen"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Keresés"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotók"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumok"</string>
</resources>
diff --git a/res/values-in/filtershow_strings.xml b/res/values-in/filtershow_strings.xml
index eb30676f9..f9a118b20 100644
--- a/res/values-in/filtershow_strings.xml
+++ b/res/values-in/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Tidak dapat memuat gambar!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Menyetel wallpaper"</string>
<string name="original" msgid="3524493791230430897">"Asli"</string>
<string name="borders" msgid="2067345080568684614">"Batas"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Batalkan"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Setel Ulang"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Status Gambar Saat Ini"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efek yang Diterapkan"</string>
<string name="compare_original" msgid="8140838959007796977">"Bandingkan"</string>
<string name="apply_effect" msgid="1218288221200568947">"Terapkan"</string>
<string name="reset_effect" msgid="7712605581024929564">"Setel Ulang"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Warna Otomatis"</string>
<string name="hue" msgid="6231252147971086030">"Rona"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Bayangan"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Sorotan"</string>
<string name="curvesRGB" msgid="915010781090477550">"Kurva"</string>
<string name="vignette" msgid="934721068851885390">"Vinyet"</string>
<string name="redeye" msgid="4508883127049472069">"Mata Merah"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 7e9c58ad6..273c88d69 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Beri tag foto dan video Anda dengan lokasi tempat pengambilannya."\n\n"Aplikasi lain dapat mengakses informasi ini beserta gambar Anda yang tersimpan."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Tidak, terima kasih"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ya"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Telusuri"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Foto"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
</resources>
diff --git a/res/values-it/filtershow_strings.xml b/res/values-it/filtershow_strings.xml
index 8de522c4f..6f8fff25e 100644
--- a/res/values-it/filtershow_strings.xml
+++ b/res/values-it/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Impossibile caricare l\'immagine."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Impostazione dello sfondo"</string>
<string name="original" msgid="3524493791230430897">"Originale"</string>
<string name="borders" msgid="2067345080568684614">"Bordi"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Annulla"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Reimposta"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Stato immagine attuale"</string>
+ <string name="imageState" msgid="8632586742752891968">"Effetti applicati"</string>
<string name="compare_original" msgid="8140838959007796977">"Confronta"</string>
<string name="apply_effect" msgid="1218288221200568947">"Applica"</string>
<string name="reset_effect" msgid="7712605581024929564">"Reimposta"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Colore autom."</string>
<string name="hue" msgid="6231252147971086030">"Tonalità"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Ombre"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Alte luci"</string>
<string name="curvesRGB" msgid="915010781090477550">"Curve"</string>
<string name="vignette" msgid="934721068851885390">"Vignetta"</string>
<string name="redeye" msgid="4508883127049472069">"Occhi rossi"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 4aa7c583e..1edf32d08 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Aggiungi alle foto e ai video tag relativi alle località in cui sono stati ripresi."\n\n"Altre applicazioni possono accedere a queste informazioni e alle tue immagini salvate."</string>
<string name="remember_location_no" msgid="7541394381714894896">"No, grazie"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Sì"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Fotocamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Cerca"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Foto"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
</resources>
diff --git a/res/values-iw/filtershow_strings.xml b/res/values-iw/filtershow_strings.xml
index 36f7a51bc..e70749596 100644
--- a/res/values-iw/filtershow_strings.xml
+++ b/res/values-iw/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"לא ניתן להעלות את התמונה!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"מגדיר טפט"</string>
<string name="original" msgid="3524493791230430897">"מקור"</string>
<string name="borders" msgid="2067345080568684614">"גבולות"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"בטל"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"אפס"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"מצב תמונה נוכחית"</string>
+ <string name="imageState" msgid="8632586742752891968">"אפקטים שהוחלו"</string>
<string name="compare_original" msgid="8140838959007796977">"השווה"</string>
<string name="apply_effect" msgid="1218288221200568947">"החל"</string>
<string name="reset_effect" msgid="7712605581024929564">"אפס"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"צבע אוטומטי"</string>
<string name="hue" msgid="6231252147971086030">"גוון"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"צלליות"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"הדגשות"</string>
<string name="curvesRGB" msgid="915010781090477550">"קימורים"</string>
<string name="vignette" msgid="934721068851885390">"עמעום קצוות"</string>
<string name="redeye" msgid="4508883127049472069">"עיניים אדומות"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index f5d6888a4..843fc2ccc 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"מתייג את התמונות והסרטונים שלך לציון המקומות שבהם צולמו."\n\n"יישומים אחרים יכולים לגשת למידע זה, כולל תמונות שנשמרו."</string>
<string name="remember_location_no" msgid="7541394381714894896">"לא, תודה"</string>
<string name="remember_location_yes" msgid="862884269285964180">"כן"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"מצלמה"</string>
+ <string name="menu_search" msgid="7580008232297437190">"חיפוש"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"תמונות"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"אלבומים"</string>
</resources>
diff --git a/res/values-ja/filtershow_strings.xml b/res/values-ja/filtershow_strings.xml
index 8ad69e1cd..65b53a13c 100644
--- a/res/values-ja/filtershow_strings.xml
+++ b/res/values-ja/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"画像を読み込めません"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"壁紙を設定しています"</string>
<string name="original" msgid="3524493791230430897">"元の画像"</string>
<string name="borders" msgid="2067345080568684614">"境界"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"元に戻す"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"リセット"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"現在の画像ステータス"</string>
+ <string name="imageState" msgid="8632586742752891968">"適用済みの効果"</string>
<string name="compare_original" msgid="8140838959007796977">"比較"</string>
<string name="apply_effect" msgid="1218288221200568947">"適用"</string>
<string name="reset_effect" msgid="7712605581024929564">"リセット"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"自動色補正"</string>
<string name="hue" msgid="6231252147971086030">"色彩"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"影"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"ハイライト"</string>
<string name="curvesRGB" msgid="915010781090477550">"カーブ"</string>
<string name="vignette" msgid="934721068851885390">"周辺減光"</string>
<string name="redeye" msgid="4508883127049472069">"赤目処理"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 3ecb47572..e3403cc35 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"画像や動画に撮影場所のタグを付けることができます。"\n\n"他のアプリから、保存された画像とともに撮影場所の情報にもアクセスできるようになります。"</string>
<string name="remember_location_no" msgid="7541394381714894896">"いいえ"</string>
<string name="remember_location_yes" msgid="862884269285964180">"はい"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"カメラ"</string>
+ <string name="menu_search" msgid="7580008232297437190">"検索"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"写真"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"アルバム"</string>
</resources>
diff --git a/res/values-ko/filtershow_strings.xml b/res/values-ko/filtershow_strings.xml
index 9a7005bd7..902649f48 100644
--- a/res/values-ko/filtershow_strings.xml
+++ b/res/values-ko/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"이미지를 로드할 수 없습니다."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"배경화면 설정 중"</string>
<string name="original" msgid="3524493791230430897">"원본"</string>
<string name="borders" msgid="2067345080568684614">"테두리"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"실행취소"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"초기화"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"현재 이미지 상태"</string>
+ <string name="imageState" msgid="8632586742752891968">"적용된 효과"</string>
<string name="compare_original" msgid="8140838959007796977">"비교하기"</string>
<string name="apply_effect" msgid="1218288221200568947">"적용"</string>
<string name="reset_effect" msgid="7712605581024929564">"초기화"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"자동 색상"</string>
<string name="hue" msgid="6231252147971086030">"색조"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"그림자"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"하이라이트"</string>
<string name="curvesRGB" msgid="915010781090477550">"곡선"</string>
<string name="vignette" msgid="934721068851885390">"비네트"</string>
<string name="redeye" msgid="4508883127049472069">"적목현상 없애기"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 9141dfcbc..23b14624c 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"촬영한 위치로 사진과 동영상에 태그를 지정하세요."\n\n"다른 앱이 저장된 이미지와 더불어 이 정보에 액세스할 수 있습니다."</string>
<string name="remember_location_no" msgid="7541394381714894896">"아니요"</string>
<string name="remember_location_yes" msgid="862884269285964180">"예"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"카메라"</string>
+ <string name="menu_search" msgid="7580008232297437190">"검색"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"사진"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"앨범"</string>
</resources>
diff --git a/res/values-lt/filtershow_strings.xml b/res/values-lt/filtershow_strings.xml
index 06e6d28d0..2f91fb629 100644
--- a/res/values-lt/filtershow_strings.xml
+++ b/res/values-lt/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Nepavyksta įkelti vaizdo!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Nustatomas ekrano fonas"</string>
<string name="original" msgid="3524493791230430897">"Originalas"</string>
<string name="borders" msgid="2067345080568684614">"Kraštinės"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Anuliuoti"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Nust. iš naujo"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Dabartinė vaizdo būsena"</string>
+ <string name="imageState" msgid="8632586742752891968">"Pritaikyti efektai"</string>
<string name="compare_original" msgid="8140838959007796977">"Palyginti"</string>
<string name="apply_effect" msgid="1218288221200568947">"Taikyti"</string>
<string name="reset_effect" msgid="7712605581024929564">"Nust. iš naujo"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autom. spalva"</string>
<string name="hue" msgid="6231252147971086030">"Atspalvis"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Šešėliai"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Paryškinimai"</string>
<string name="curvesRGB" msgid="915010781090477550">"Kreivės"</string>
<string name="vignette" msgid="934721068851885390">"Vinjetė"</string>
<string name="redeye" msgid="4508883127049472069">"Raudonos akys"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index f7e1ddec9..5d7eebd6a 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Žymėkite nuotraukas ir vaizdo įrašus nurodydami vietas, kur jie buvo sukurti."\n\n"Kitos programos gali pasiekti šią informaciją kartu su išsaugotais vaizdais."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Ne, ačiū"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Taip"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Fotoaparatas"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Paieška"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Nuotraukos"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumai"</string>
</resources>
diff --git a/res/values-lv/filtershow_strings.xml b/res/values-lv/filtershow_strings.xml
index 9ac6f7f2c..155784c49 100644
--- a/res/values-lv/filtershow_strings.xml
+++ b/res/values-lv/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Nevar ielādēt attēlu."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Notiek fona tapetes iestatīšana"</string>
<string name="original" msgid="3524493791230430897">"Oriģināls"</string>
<string name="borders" msgid="2067345080568684614">"Robežas"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Atsaukt"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Atiestatīt"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Pašreizējais attēla statuss"</string>
+ <string name="imageState" msgid="8632586742752891968">"Izmantotie efekti"</string>
<string name="compare_original" msgid="8140838959007796977">"Salīdzināt"</string>
<string name="apply_effect" msgid="1218288221200568947">"Lietot"</string>
<string name="reset_effect" msgid="7712605581024929564">"Atiestatīt"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Aut. krāsu pal."</string>
<string name="hue" msgid="6231252147971086030">"Nokrāsa"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Ēnas"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Gaišās vietas"</string>
<string name="curvesRGB" msgid="915010781090477550">"Līknes"</string>
<string name="vignette" msgid="934721068851885390">"Vinjete"</string>
<string name="redeye" msgid="4508883127049472069">"Sarkano acu ef."</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index f26bdcece..5b188c889 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Atzīmē jūsu fotoattēlos un videoklipos atrašanās vietas, kurās tie tika uzņemti."\n\n"Citas lietotnes var piekļūt šai informācijai līdz ar saglabātajiem attēliem."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nē, paldies!"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Jā"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Meklēt"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotoattēli"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumi"</string>
</resources>
diff --git a/res/values-ms/filtershow_strings.xml b/res/values-ms/filtershow_strings.xml
index ee7287c94..3f583b3e8 100644
--- a/res/values-ms/filtershow_strings.xml
+++ b/res/values-ms/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Tidak dapat memuatkan imej!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Menetapkan kertas dinding"</string>
<string name="original" msgid="3524493791230430897">"Asli"</string>
<string name="borders" msgid="2067345080568684614">"Sempadan"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Buat asal"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Tetapkan semula"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Keadaan Imej Semasa"</string>
+ <string name="imageState" msgid="8632586742752891968">"Kesan Digunakan"</string>
<string name="compare_original" msgid="8140838959007796977">"Bandingkan"</string>
<string name="apply_effect" msgid="1218288221200568947">"Gunakan"</string>
<string name="reset_effect" msgid="7712605581024929564">"Tetapkan semula"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autowarna"</string>
<string name="hue" msgid="6231252147971086030">"Rona"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Bayang-bayang"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Serlahan"</string>
<string name="curvesRGB" msgid="915010781090477550">"Lengkung"</string>
<string name="vignette" msgid="934721068851885390">"Vignet"</string>
<string name="redeye" msgid="4508883127049472069">"Mata Merah"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index f17d3bbbf..31e10c630 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Teg foto dan video anda dengan lokasi tempat diambil."\n\n"Apl lain boleh mengakses maklumat ini bersama dengan imej disimpan anda."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Tidak, terima kasih"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ya"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Cari"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Foto"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
</resources>
diff --git a/res/values-nb/filtershow_strings.xml b/res/values-nb/filtershow_strings.xml
index 3f0f06ae9..a7b439665 100644
--- a/res/values-nb/filtershow_strings.xml
+++ b/res/values-nb/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Kan ikke laste inn bildet."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Angir bakgrunn …"</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Kantlinjer"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Angre"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Tilbakestill"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Nåværende bildetilstand"</string>
+ <string name="imageState" msgid="8632586742752891968">"Brukte effekter"</string>
<string name="compare_original" msgid="8140838959007796977">"Sammenlign"</string>
<string name="apply_effect" msgid="1218288221200568947">"Bruk"</string>
<string name="reset_effect" msgid="7712605581024929564">"Tilbakestill"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autofarger"</string>
<string name="hue" msgid="6231252147971086030">"Nyanse"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Skygger"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Lyse punkter"</string>
<string name="curvesRGB" msgid="915010781090477550">"Kurver"</string>
<string name="vignette" msgid="934721068851885390">"Vignettering"</string>
<string name="redeye" msgid="4508883127049472069">"Røde øyne"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 22acbaa8f..41c0297ba 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -387,4 +387,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Merk bildene og videoene med hvor de ble tatt."\n\n"Andre apper kan bruke denne informasjonen med de lagrede bildene dine."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nei takk"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Søk"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Bilder"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumer"</string>
</resources>
diff --git a/res/values-nl/filtershow_strings.xml b/res/values-nl/filtershow_strings.xml
index 8bbbe3b39..302cc22a3 100644
--- a/res/values-nl/filtershow_strings.xml
+++ b/res/values-nl/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Kan de afbeelding niet laden."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Achtergrond instellen"</string>
<string name="original" msgid="3524493791230430897">"Origineel"</string>
<string name="borders" msgid="2067345080568684614">"Randen"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Ongedaan maken"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Opnieuw instellen"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Huidige afbeeldingsstatus"</string>
+ <string name="imageState" msgid="8632586742752891968">"Toegepaste effecten"</string>
<string name="compare_original" msgid="8140838959007796977">"Vergelijken"</string>
<string name="apply_effect" msgid="1218288221200568947">"Toepassen"</string>
<string name="reset_effect" msgid="7712605581024929564">"Opnieuw instellen"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Auto-kleur"</string>
<string name="hue" msgid="6231252147971086030">"Kleurschakering"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Schaduw"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Accenten"</string>
<string name="curvesRGB" msgid="915010781090477550">"Curven"</string>
<string name="vignette" msgid="934721068851885390">"Vervloeien"</string>
<string name="redeye" msgid="4508883127049472069">"Rode ogen"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 50f83ce29..e9ba64059 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Label uw foto\'s en video\'s met de locaties waar ze zijn genomen."\n\n"Andere apps hebben toegang tot deze informatie en uw opgeslagen afbeeldingen."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nee, bedankt"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Camera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Zoeken"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Foto\'s"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albums"</string>
</resources>
diff --git a/res/values-pl/filtershow_strings.xml b/res/values-pl/filtershow_strings.xml
index 48a45cc26..880cf824b 100644
--- a/res/values-pl/filtershow_strings.xml
+++ b/res/values-pl/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Nie można wczytać zdjęcia."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Ustawiam tapetę"</string>
<string name="original" msgid="3524493791230430897">"Oryginalny"</string>
<string name="borders" msgid="2067345080568684614">"Granice"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Cofnij"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Resetuj"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Obecny stan obrazu"</string>
+ <string name="imageState" msgid="8632586742752891968">"Zastosowane efekty"</string>
<string name="compare_original" msgid="8140838959007796977">"Porównaj"</string>
<string name="apply_effect" msgid="1218288221200568947">"Zastosuj"</string>
<string name="reset_effect" msgid="7712605581024929564">"Resetuj"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autokolor"</string>
<string name="hue" msgid="6231252147971086030">"Odcień"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Cienie"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Podświetlenie"</string>
<string name="curvesRGB" msgid="915010781090477550">"Krzywe"</string>
<string name="vignette" msgid="934721068851885390">"Winietowanie"</string>
<string name="redeye" msgid="4508883127049472069">"Czerwone oczy"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 4e595e37b..bc9f6281a 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Oznacz zdjęcia i filmy informacją, gdzie zostały zrobione."\n\n"Inne aplikacje mają dostęp do tych informacji wraz z zapisanymi zdjęciami."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nie, dziękuję"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Tak"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Aparat"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Szukaj"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Zdjęcia"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumy"</string>
</resources>
diff --git a/res/values-pt-rPT/filtershow_strings.xml b/res/values-pt-rPT/filtershow_strings.xml
index e94bf45f5..389d63a95 100644
--- a/res/values-pt-rPT/filtershow_strings.xml
+++ b/res/values-pt-rPT/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Não é possível carregar a imagem!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"A definir imagem de fundo"</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Limites"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Anular"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Repor"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estado da Imagem Atual"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efeitos Aplicados"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
<string name="reset_effect" msgid="7712605581024929564">"Repor"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Cor automática"</string>
<string name="hue" msgid="6231252147971086030">"Tonalidade"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Destaques"</string>
<string name="curvesRGB" msgid="915010781090477550">"Curvas"</string>
<string name="vignette" msgid="934721068851885390">"Vinheta"</string>
<string name="redeye" msgid="4508883127049472069">"Olhos Vermelhos"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 20e3f7b3c..56a1311fe 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Insira etiquetas nas suas fotografias e vídeos com as localizações onde foram capturados."\n\n"Outras aplicações podem aceder a estas informações juntamente com as suas imagens guardadas."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Não, obrigado"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Sim"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Câmara"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Pesquisa"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotografias"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Álbuns"</string>
</resources>
diff --git a/res/values-pt/filtershow_strings.xml b/res/values-pt/filtershow_strings.xml
index 8c8027028..14a7520a2 100644
--- a/res/values-pt/filtershow_strings.xml
+++ b/res/values-pt/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Não é possível carregar a imagem!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Definindo plano de fundo"</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Bordas"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Desfazer"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Restaurar"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Estado atual da imagem"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efeitos aplicados"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
<string name="reset_effect" msgid="7712605581024929564">"Restaurar"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Cor automática"</string>
<string name="hue" msgid="6231252147971086030">"Matiz"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Destaques"</string>
<string name="curvesRGB" msgid="915010781090477550">"Curvas"</string>
<string name="vignette" msgid="934721068851885390">"Vinheta"</string>
<string name="redeye" msgid="4508883127049472069">"Olhos vermelhos"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 05409b1b0..b60e59a8f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Marque seus vídeos e fotos com os locais onde foram gerados."\n\n"Outros aplicativos podem acessar essas informações juntamente com suas imagens salvas."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Não, obrigado"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Sim"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Câmera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Pesquisar"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Álbuns"</string>
</resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
index 6fd007e9f..18e4bef8f 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -654,4 +654,12 @@
<skip />
<!-- no translation found for remember_location_yes (862884269285964180) -->
<skip />
+ <!-- no translation found for menu_camera (3476709832879398998) -->
+ <skip />
+ <!-- no translation found for menu_search (7580008232297437190) -->
+ <skip />
+ <!-- no translation found for tab_photos (9110813680630313419) -->
+ <skip />
+ <!-- no translation found for tab_albums (8079449907770685691) -->
+ <skip />
</resources>
diff --git a/res/values-ro/filtershow_strings.xml b/res/values-ro/filtershow_strings.xml
index 46be09416..68b067cc9 100644
--- a/res/values-ro/filtershow_strings.xml
+++ b/res/values-ro/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Nu se poate încărca imaginea!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Se setează imaginea de fundal"</string>
<string name="original" msgid="3524493791230430897">"Originală"</string>
<string name="borders" msgid="2067345080568684614">"Chenar"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Anulaţi"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Resetaţi"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Starea curentă a imaginii"</string>
+ <string name="imageState" msgid="8632586742752891968">"Efecte aplicate"</string>
<string name="compare_original" msgid="8140838959007796977">"Comparaţi"</string>
<string name="apply_effect" msgid="1218288221200568947">"Aplicaţi"</string>
<string name="reset_effect" msgid="7712605581024929564">"Resetaţi"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Culoare auto."</string>
<string name="hue" msgid="6231252147971086030">"Tonalitate"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Umbre"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Puncte luminoz."</string>
<string name="curvesRGB" msgid="915010781090477550">"Curbe"</string>
<string name="vignette" msgid="934721068851885390">"Vignetare"</string>
<string name="redeye" msgid="4508883127049472069">"Ochi roşii"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 71c155ae4..51059a8aa 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Etichetaţi-vă fotografiile şi videoclipurile cu locaţiile în care acestea au fost create."\n\n"Alte aplicaţii pot accesa aceste informaţii, împreună cu imaginile salvate."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nu, mulţumesc"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Da"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Cameră foto"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Căutați"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotografii"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albume"</string>
</resources>
diff --git a/res/values-ru/filtershow_strings.xml b/res/values-ru/filtershow_strings.xml
index 42d6db50f..cb18bedaa 100644
--- a/res/values-ru/filtershow_strings.xml
+++ b/res/values-ru/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Не удалось загрузить изображение."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Установка обоев…"</string>
<string name="original" msgid="3524493791230430897">"Оригинал"</string>
<string name="borders" msgid="2067345080568684614">"Границы"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Отмена"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Сброс"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Состояние изображения"</string>
+ <string name="imageState" msgid="8632586742752891968">"Эффекты"</string>
<string name="compare_original" msgid="8140838959007796977">"Сравнить"</string>
<string name="apply_effect" msgid="1218288221200568947">"Применить:"</string>
<string name="reset_effect" msgid="7712605581024929564">"Сброс"</string>
@@ -45,7 +46,7 @@
<string name="aspect5to7_effect" msgid="5122395569059384741">"5:7"</string>
<string name="aspect7to5_effect" msgid="5780001758108328143">"7:5"</string>
<string name="aspect9to16_effect" msgid="7740468012919660728">"16:9"</string>
- <string name="aspectNone_effect" msgid="6263330561046574134">"Оригинал"</string>
+ <string name="aspectNone_effect" msgid="6263330561046574134">"Вручную"</string>
<!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
<skip />
<string name="Fixed" msgid="8017376448916924565">"Постоянное"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Авторежим"</string>
<string name="hue" msgid="6231252147971086030">"Оттенок"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Тени"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Блики"</string>
<string name="curvesRGB" msgid="915010781090477550">"Кривые"</string>
<string name="vignette" msgid="934721068851885390">"Виньет-ние"</string>
<string name="redeye" msgid="4508883127049472069">"Красные глаза"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 3d77c3570..13e6ef8b4 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Информация о месте съемки будет автоматически добавляться в описание ваших фотографий и видеозаписей."\n\n"Доступ к этим данным, а также к самим фотографиям и видео смогут получить и другие приложения."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Нет, спасибо"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Да"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Поиск"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Фото"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Альбомы"</string>
</resources>
diff --git a/res/values-sk/filtershow_strings.xml b/res/values-sk/filtershow_strings.xml
index a49bd3cc8..b999871b4 100644
--- a/res/values-sk/filtershow_strings.xml
+++ b/res/values-sk/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Obrázok sa nepodarilo načítať!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Prebieha nastavovanie tapety"</string>
<string name="original" msgid="3524493791230430897">"Pôvodné"</string>
<string name="borders" msgid="2067345080568684614">"Okraje"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Späť"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Obnoviť"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktuálny stav obrázka"</string>
+ <string name="imageState" msgid="8632586742752891968">"Použité efekty"</string>
<string name="compare_original" msgid="8140838959007796977">"Porovnať"</string>
<string name="apply_effect" msgid="1218288221200568947">"Použiť"</string>
<string name="reset_effect" msgid="7712605581024929564">"Obnoviť"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autom. farba"</string>
<string name="hue" msgid="6231252147971086030">"Odtieň"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Tiene"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Najsvetlejšie tóny"</string>
<string name="curvesRGB" msgid="915010781090477550">"Krivky"</string>
<string name="vignette" msgid="934721068851885390">"Vineta"</string>
<string name="redeye" msgid="4508883127049472069">"Červené oči"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 3d924dabf..ab4250956 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Označte pre fotografie a videá polohy, kde boli zaznamenané."\n\n"Ostatné aplikácie môžu pristupovať k týmto informáciám aj k vašim uloženým snímkam."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nie, ďakujem"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Áno"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Fotoaparát"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Vyhľadávanie"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotografie"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumy"</string>
</resources>
diff --git a/res/values-sl/filtershow_strings.xml b/res/values-sl/filtershow_strings.xml
index e0539e396..61bd61e1b 100644
--- a/res/values-sl/filtershow_strings.xml
+++ b/res/values-sl/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Slike ni mogoče naložiti."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Nastavljanje ozadja"</string>
<string name="original" msgid="3524493791230430897">"Izvirnik"</string>
<string name="borders" msgid="2067345080568684614">"Obrobe"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Razveljavi"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Ponastavi"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Trenutno stanje slike"</string>
+ <string name="imageState" msgid="8632586742752891968">"Uporabljeni učinki"</string>
<string name="compare_original" msgid="8140838959007796977">"Primerjaj"</string>
<string name="apply_effect" msgid="1218288221200568947">"Uporabi"</string>
<string name="reset_effect" msgid="7712605581024929564">"Ponastavi"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Samodej. barva"</string>
<string name="hue" msgid="6231252147971086030">"Odtenek"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Sence"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Svetli deli"</string>
<string name="curvesRGB" msgid="915010781090477550">"Krivulje"</string>
<string name="vignette" msgid="934721068851885390">"Vinjeta"</string>
<string name="redeye" msgid="4508883127049472069">"Rdeče oči"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index edc5e1441..ebea3fc01 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Fotografije in videoposnetke označite z lokacijami, na katerih so posneti."\n\n"Druge aplikacije lahko dostopajo do teh podatkov skupaj s shranjenimi slikami."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Ne, hvala"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Da"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Fotoaparat"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Išči"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotografije"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albumi"</string>
</resources>
diff --git a/res/values-sr/filtershow_strings.xml b/res/values-sr/filtershow_strings.xml
index d5837d900..b6d66cb54 100644
--- a/res/values-sr/filtershow_strings.xml
+++ b/res/values-sr/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Није могуће учитати слику!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Подешавање позадине"</string>
<string name="original" msgid="3524493791230430897">"Оригинална"</string>
<string name="borders" msgid="2067345080568684614">"Ивице"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Опозови"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Поново постави"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Актуелни статус слике"</string>
+ <string name="imageState" msgid="8632586742752891968">"Примењени ефекти"</string>
<string name="compare_original" msgid="8140838959007796977">"Упореди"</string>
<string name="apply_effect" msgid="1218288221200568947">"Примени"</string>
<string name="reset_effect" msgid="7712605581024929564">"Поново постави"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Аутоматска боја"</string>
<string name="hue" msgid="6231252147971086030">"Нијанса"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Сенке"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Истицања"</string>
<string name="curvesRGB" msgid="915010781090477550">"Криве"</string>
<string name="vignette" msgid="934721068851885390">"Вињета"</string>
<string name="redeye" msgid="4508883127049472069">"Црвене очи"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index fd4bf1be0..c0b0452af 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Додајте сликама и видео снимцима ознаке са местима где су снимљени."\n\n"Друге апликације могу да приступе овим информацијама заједно са сачуваним сликама."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Не, хвала"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Да"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Претражи"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Слике"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Албуми"</string>
</resources>
diff --git a/res/values-sv/filtershow_strings.xml b/res/values-sv/filtershow_strings.xml
index f43c7d297..3644a5ff5 100644
--- a/res/values-sv/filtershow_strings.xml
+++ b/res/values-sv/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Det går inte att läsa in bilden."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Bakgrund anges"</string>
<string name="original" msgid="3524493791230430897">"Original"</string>
<string name="borders" msgid="2067345080568684614">"Ramar"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Ångra"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Återställ"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Aktuellt bildläge"</string>
+ <string name="imageState" msgid="8632586742752891968">"Effekter som används"</string>
<string name="compare_original" msgid="8140838959007796977">"Jämför"</string>
<string name="apply_effect" msgid="1218288221200568947">"Använd"</string>
<string name="reset_effect" msgid="7712605581024929564">"Återställ"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autofärg"</string>
<string name="hue" msgid="6231252147971086030">"Nyans"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Skuggor"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Högdagrar"</string>
<string name="curvesRGB" msgid="915010781090477550">"Kurvor"</string>
<string name="vignette" msgid="934721068851885390">"Vignette"</string>
<string name="redeye" msgid="4508883127049472069">"Röda ögon"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 2981c156d..86aeb62be 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Tagga dina foton och videor med de platser där de tas eller spelas in."\n\n"Andra appar kan få åtkomst till den här informationen tillsammans med dina sparade bilder."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Nej tack"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Sök"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Foton"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
</resources>
diff --git a/res/values-sw/filtershow_strings.xml b/res/values-sw/filtershow_strings.xml
index 9f26a05ab..6f66386b1 100644
--- a/res/values-sw/filtershow_strings.xml
+++ b/res/values-sw/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Haiwezi kupakia picha!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Inaweka mandhari"</string>
<string name="original" msgid="3524493791230430897">"Asili"</string>
<string name="borders" msgid="2067345080568684614">"Kingo"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Tendua"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Weka upya"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Hali ya Sasa ya Picha"</string>
+ <string name="imageState" msgid="8632586742752891968">"Madoido Yanayotumiwa"</string>
<string name="compare_original" msgid="8140838959007796977">"Linganisha"</string>
<string name="apply_effect" msgid="1218288221200568947">"Tekeleza"</string>
<string name="reset_effect" msgid="7712605581024929564">"Weka upya"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Rangi otomatiki"</string>
<string name="hue" msgid="6231252147971086030">"Rangi"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Vivuli"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Sehemu zenye ung\'avu zaidi"</string>
<string name="curvesRGB" msgid="915010781090477550">"Pindo"</string>
<string name="vignette" msgid="934721068851885390">"Vignete"</string>
<string name="redeye" msgid="4508883127049472069">"Jicho Jekundu"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index a99402c9a..d98c0ce02 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -394,4 +394,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Tambulisha picha na video zako kwa maeneo ambapo zinachukuliwa."\n\n"Programu nyingine zinaweza kufikia maelezo haya kando na picha zako zilizohifadhiwa."</string>
<string name="remember_location_no" msgid="7541394381714894896">"La, asante"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Ndiyo"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Tafuta"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Picha"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albamu"</string>
</resources>
diff --git a/res/values-th/filtershow_strings.xml b/res/values-th/filtershow_strings.xml
index 1f86b14c7..2cdaf872d 100644
--- a/res/values-th/filtershow_strings.xml
+++ b/res/values-th/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"ไม่สามารถโหลดภาพ!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"กำลังตั้งค่าวอลเปเปอร์"</string>
<string name="original" msgid="3524493791230430897">"ต้นฉบับ"</string>
<string name="borders" msgid="2067345080568684614">"ขอบ"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"เลิกทำ"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"รีเซ็ต"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"สถานะภาพปัจจุบัน"</string>
+ <string name="imageState" msgid="8632586742752891968">"เอฟเ็ฟ็กต์ที่ใช้"</string>
<string name="compare_original" msgid="8140838959007796977">"เปรียบเทียบ"</string>
<string name="apply_effect" msgid="1218288221200568947">"ใช้"</string>
<string name="reset_effect" msgid="7712605581024929564">"รีเซ็ต"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"ให้สีอัตโนมัติ"</string>
<string name="hue" msgid="6231252147971086030">"สี"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"เงา"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"ไฮไลต์"</string>
<string name="curvesRGB" msgid="915010781090477550">"เส้นโค้ง"</string>
<string name="vignette" msgid="934721068851885390">"วิกเน็ตต์"</string>
<string name="redeye" msgid="4508883127049472069">"ตาแดง"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index ba9ab8394..d22b540d4 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"แท็กรูปภาพและวิดีโอด้วยตำแหน่งที่ถ่ายภาพ"\n\n"แอปพลิเคชันอื่นๆ สามารถเข้าถึงข้อมูลนี้ตลอดจนภาพที่บันทึกไว้ของคุณได้"</string>
<string name="remember_location_no" msgid="7541394381714894896">"ไม่ ขอบคุณ"</string>
<string name="remember_location_yes" msgid="862884269285964180">"ใช่"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"กล้องถ่ายรูป"</string>
+ <string name="menu_search" msgid="7580008232297437190">"ค้นหา"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"รูปภาพ"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"อัลบั้ม"</string>
</resources>
diff --git a/res/values-tl/filtershow_strings.xml b/res/values-tl/filtershow_strings.xml
index 8ea767a23..4861dde1c 100644
--- a/res/values-tl/filtershow_strings.xml
+++ b/res/values-tl/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Hindi ma-load ang larawan!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Itinatakda ang wallpaper"</string>
<string name="original" msgid="3524493791230430897">"Orihinal"</string>
<string name="borders" msgid="2067345080568684614">"Mga Border"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"I-undo"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"I-reset"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Kasalukuyang Katayuan ng Larawan"</string>
+ <string name="imageState" msgid="8632586742752891968">"Mga Nakalapat na Effect"</string>
<string name="compare_original" msgid="8140838959007796977">"Ihambing"</string>
<string name="apply_effect" msgid="1218288221200568947">"Ilapat"</string>
<string name="reset_effect" msgid="7712605581024929564">"I-reset"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Autocolor"</string>
<string name="hue" msgid="6231252147971086030">"Hue"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Mga Shadow"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Mga Highlight"</string>
<string name="curvesRGB" msgid="915010781090477550">"Mga Kurba"</string>
<string name="vignette" msgid="934721068851885390">"Vignette"</string>
<string name="redeye" msgid="4508883127049472069">"Red Eye"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 2acc2dc76..0295ad86a 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"I-tag ang iyong mga larawan at video sa mga lokasyon kung saan kinunan ang mga iyon."\n\n"Maaaring i-access ng mga ibang app ang impormasyong ito kasama ng iyong mga na-save na larawan."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Hindi na, salamat"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Oo"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Camera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Maghanap"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Mga Larawan"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Mga Album"</string>
</resources>
diff --git a/res/values-tr/filtershow_strings.xml b/res/values-tr/filtershow_strings.xml
index cdc8189a7..63740ec27 100644
--- a/res/values-tr/filtershow_strings.xml
+++ b/res/values-tr/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Resim yüklenemiyor"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Duvar kağıdı ayarlanıyor"</string>
<string name="original" msgid="3524493791230430897">"Orijinal"</string>
<string name="borders" msgid="2067345080568684614">"Kenarlıklar"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Geri al"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Sıfırla"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Mevcut Resim Durumu"</string>
+ <string name="imageState" msgid="8632586742752891968">"Uygulanan Efektler"</string>
<string name="compare_original" msgid="8140838959007796977">"Karşılaştır"</string>
<string name="apply_effect" msgid="1218288221200568947">"Uygula"</string>
<string name="reset_effect" msgid="7712605581024929564">"Sıfırla"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Otomatik Renk"</string>
<string name="hue" msgid="6231252147971086030">"Hue"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Gölgeler"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Parlak Noktalar"</string>
<string name="curvesRGB" msgid="915010781090477550">"Eğriler"</string>
<string name="vignette" msgid="934721068851885390">"Vinyet"</string>
<string name="redeye" msgid="4508883127049472069">"Kırmızı Göz"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 71c16710a..2755fffc8 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Fotoğraflarınızı ve videolarınızı çekildikleri konumlarla etiketleyin."\n\n"Diğer uygulamalar, kaydedilen görüntülerle birlikte bu bilgilere erişebilir."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Hayır, teşekkürler"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Evet"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Ara"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Fotoğraflar"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Albümler"</string>
</resources>
diff --git a/res/values-uk/filtershow_strings.xml b/res/values-uk/filtershow_strings.xml
index a05ef9348..c23608844 100644
--- a/res/values-uk/filtershow_strings.xml
+++ b/res/values-uk/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Неможливо завантажити зображення."</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Встановлення фонового малюнка"</string>
<string name="original" msgid="3524493791230430897">"Оригінал"</string>
<string name="borders" msgid="2067345080568684614">"Облямівка"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Відмінити"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Скинути"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Поточний стан зображення"</string>
+ <string name="imageState" msgid="8632586742752891968">"Застосовані ефекти"</string>
<string name="compare_original" msgid="8140838959007796977">"Порівняти"</string>
<string name="apply_effect" msgid="1218288221200568947">"Застосувати"</string>
<string name="reset_effect" msgid="7712605581024929564">"Скинути"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Автоколір"</string>
<string name="hue" msgid="6231252147971086030">"Тон"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Тіні"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Затемнення"</string>
<string name="curvesRGB" msgid="915010781090477550">"Криві"</string>
<string name="vignette" msgid="934721068851885390">"Віньєтка"</string>
<string name="redeye" msgid="4508883127049472069">"Червоні очі"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 1af1e5628..fcae7562d 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Додавайте до своїх фотографій і відео теги про місця, де їх було зроблено."\n\n"Інші програми можуть отримувати доступ до цієї інформації, а також ваших збережених зображень."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Ні, дякую"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Так"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Пошук"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Фотографії"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Альбоми"</string>
</resources>
diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml
index 18f3440f3..c05bf30bb 100644
--- a/res/values-v14/styles.xml
+++ b/res/values-v14/styles.xml
@@ -22,4 +22,8 @@
<style name="ActionBarTwoLineItem">
<item name="android:background">?android:attr/activatedBackgroundIndicator</item>
</style>
+ <style name="Theme.Photos.Gallery" parent="android:Theme.Holo.Light">
+ </style>
+ <style name="Theme.Photos.Fullscreen" parent="android:Theme.Holo">
+ </style>
</resources>
diff --git a/res/values-vi/filtershow_strings.xml b/res/values-vi/filtershow_strings.xml
index 0f4c9d3d2..660172b78 100644
--- a/res/values-vi/filtershow_strings.xml
+++ b/res/values-vi/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Không thể tải hình ảnh!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Đang đặt hình nền"</string>
<string name="original" msgid="3524493791230430897">"Gốc"</string>
<string name="borders" msgid="2067345080568684614">"Đường viền"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Hoàn tác"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Đặt lại"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Trạng thái hình ảnh hiện tại"</string>
+ <string name="imageState" msgid="8632586742752891968">"Các hiệu ứng được áp dụng"</string>
<string name="compare_original" msgid="8140838959007796977">"So sánh"</string>
<string name="apply_effect" msgid="1218288221200568947">"Áp dụng"</string>
<string name="reset_effect" msgid="7712605581024929564">"Đặt lại"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"Màu tự động"</string>
<string name="hue" msgid="6231252147971086030">"Màu sắc"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Bóng"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Vùng sáng"</string>
<string name="curvesRGB" msgid="915010781090477550">"Đồ thị màu"</string>
<string name="vignette" msgid="934721068851885390">"Làm mờ nét ảnh"</string>
<string name="redeye" msgid="4508883127049472069">"Mắt đỏ"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 86c25030b..51cb2b14b 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Gắn thẻ cho ảnh và video của bạn với những địa điểm mà ảnh đó được chụp và video đó được quay."\n\n"Các ứng dụng khác có thể truy cập vào thông tin này cùng với các hình ảnh đã lưu của bạn."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Không, cảm ơn"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Có"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Máy ảnh"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Tìm kiếm"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Ảnh"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
</resources>
diff --git a/res/values-zh-rCN/filtershow_strings.xml b/res/values-zh-rCN/filtershow_strings.xml
index 8876ce8ef..1c060bc57 100644
--- a/res/values-zh-rCN/filtershow_strings.xml
+++ b/res/values-zh-rCN/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"无法加载该图片!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"正在设置壁纸"</string>
<string name="original" msgid="3524493791230430897">"原图"</string>
<string name="borders" msgid="2067345080568684614">"边框"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"撤消"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"重置"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"当前的图片状态"</string>
+ <string name="imageState" msgid="8632586742752891968">"运用的效果"</string>
<string name="compare_original" msgid="8140838959007796977">"比较"</string>
<string name="apply_effect" msgid="1218288221200568947">"应用"</string>
<string name="reset_effect" msgid="7712605581024929564">"重置"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"自动调整色彩"</string>
<string name="hue" msgid="6231252147971086030">"色调"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"阴影"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"强光"</string>
<string name="curvesRGB" msgid="915010781090477550">"曲线"</string>
<string name="vignette" msgid="934721068851885390">"晕影"</string>
<string name="redeye" msgid="4508883127049472069">"红眼"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index f41fa83bf..5b7ea9123 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"为您的照片和视频标明拍摄地点。"\n\n"其他应用在查看您保存的图片时将可以访问这些信息。"</string>
<string name="remember_location_no" msgid="7541394381714894896">"不用了"</string>
<string name="remember_location_yes" msgid="862884269285964180">"是"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"相机"</string>
+ <string name="menu_search" msgid="7580008232297437190">"搜索"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"照片"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"相册"</string>
</resources>
diff --git a/res/values-zh-rTW/filtershow_strings.xml b/res/values-zh-rTW/filtershow_strings.xml
index 8f205fbc0..f2a367987 100644
--- a/res/values-zh-rTW/filtershow_strings.xml
+++ b/res/values-zh-rTW/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"無法載入圖片!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"正在設定桌布"</string>
<string name="original" msgid="3524493791230430897">"原始"</string>
<string name="borders" msgid="2067345080568684614">"邊框"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"復原"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"重設"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"目前圖片狀態"</string>
+ <string name="imageState" msgid="8632586742752891968">"套用的效果"</string>
<string name="compare_original" msgid="8140838959007796977">"比較"</string>
<string name="apply_effect" msgid="1218288221200568947">"套用"</string>
<string name="reset_effect" msgid="7712605581024929564">"重設"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"自動色彩校正"</string>
<string name="hue" msgid="6231252147971086030">"色調"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"陰影"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"強光"</string>
<string name="curvesRGB" msgid="915010781090477550">"曲線"</string>
<string name="vignette" msgid="934721068851885390">"暈影"</string>
<string name="redeye" msgid="4508883127049472069">"紅眼"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 3178058db..ac46bc843 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -389,4 +389,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"為您的相片和影片標記拍攝地點。"\n\n"其他應用程式可存取這項資訊及您所儲存的相片。"</string>
<string name="remember_location_no" msgid="7541394381714894896">"不用了,謝謝"</string>
<string name="remember_location_yes" msgid="862884269285964180">"是"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"相機"</string>
+ <string name="menu_search" msgid="7580008232297437190">"搜尋"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"相片"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"相簿"</string>
</resources>
diff --git a/res/values-zu/filtershow_strings.xml b/res/values-zu/filtershow_strings.xml
index 5798c1003..22126bff6 100644
--- a/res/values-zu/filtershow_strings.xml
+++ b/res/values-zu/filtershow_strings.xml
@@ -20,6 +20,7 @@
<string name="cannot_load_image" msgid="5023634941212959976">"Ayikwazi ukulayisha isithombe!"</string>
<!-- no translation found for original_picture_text (3076213290079909698) -->
<skip />
+ <string name="setting_wallpaper" msgid="4679087092300036632">"Isetha isithombe sangemuva"</string>
<string name="original" msgid="3524493791230430897">"Oluqobo"</string>
<string name="borders" msgid="2067345080568684614">"Imingcele"</string>
<string name="filtershow_undo" msgid="6781743189243585101">"Hlehlisa"</string>
@@ -33,7 +34,7 @@
<string name="reset" msgid="9013181350779592937">"Setha kabusha"</string>
<!-- no translation found for history_original (150973253194312841) -->
<skip />
- <string name="imageState" msgid="3609930035023754855">"Isimo sesithombe samanje"</string>
+ <string name="imageState" msgid="8632586742752891968">"Imiphumela esetshenzisiwe"</string>
<string name="compare_original" msgid="8140838959007796977">"Qhathanisa"</string>
<string name="apply_effect" msgid="1218288221200568947">"Sebenzisa"</string>
<string name="reset_effect" msgid="7712605581024929564">"Setha kabusha"</string>
@@ -59,6 +60,7 @@
<string name="wbalance" msgid="6346581563387083613">"I-Autocolor"</string>
<string name="hue" msgid="6231252147971086030">"I-Hue"</string>
<string name="shadow_recovery" msgid="3928572915300287152">"Izithunzi"</string>
+ <string name="highlight_recovery" msgid="8262208470735204243">"Okubekwe obala"</string>
<string name="curvesRGB" msgid="915010781090477550">"Ukugobeka"</string>
<string name="vignette" msgid="934721068851885390">"I-Vignette"</string>
<string name="redeye" msgid="4508883127049472069">"Iso elibomvu"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index b7f817452..6d3b682cc 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -391,4 +391,8 @@
<string name="remember_location_prompt" msgid="724592331305808098">"Maka izithombe zakho namavidiyo ngezindawo lapha zithathwe khona."\n\n"Ezinye izinhlelo zokusebenza zingafinyelela lolu lwazi nezithombe zakho ezilondoloziwe."</string>
<string name="remember_location_no" msgid="7541394381714894896">"Cha ngiyabonga"</string>
<string name="remember_location_yes" msgid="862884269285964180">"Yebo"</string>
+ <string name="menu_camera" msgid="3476709832879398998">"Ikhamera"</string>
+ <string name="menu_search" msgid="7580008232297437190">"Sesha"</string>
+ <string name="tab_photos" msgid="9110813680630313419">"Izithombe"</string>
+ <string name="tab_albums" msgid="8079449907770685691">"Ama-albhamu"</string>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index cb38007e4..d2720d966 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -88,4 +88,8 @@
<dimen name="face_circle_stroke">2dip</dimen>
<dimen name="zoom_font_size">14pt</dimen>
<dimen name="shutter_offset">-22dp</dimen>
+ <dimen name="size_thumbnail">200dip</dimen>
+ <dimen name="size_preview">600dip</dimen>
+ <dimen name="navigation_bar_height">48dip</dimen>
+ <dimen name="navigation_bar_width">42dip</dimen>
</resources>
diff --git a/res/values/dimensions.xml b/res/values/dimensions.xml
index dc9e8c300..ae506807a 100644
--- a/res/values/dimensions.xml
+++ b/res/values/dimensions.xml
@@ -19,7 +19,7 @@
<dimen name="stack_photo_width">160dp</dimen>
<dimen name="stack_photo_height">120dp</dimen>
- <!-- configuration for album set page -->
+ <!-- configuration for legacy album set page -->
<integer name="albumset_rows_land">2</integer>
<integer name="albumset_rows_port">3</integer>
<dimen name="albumset_padding_top">7dp</dimen>
@@ -50,4 +50,8 @@
<!-- configuration for filtershow UI -->
<dimen name="thumbnail_size">96dip</dimen>
<dimen name="thumbnail_margin">3dip</dimen>
+
+ <!-- configuration for album set page -->
+ <dimen name="album_set_item_image_height">100dp</dimen>
+ <dimen name="album_set_item_width">160dp</dimen>
</resources>
diff --git a/res/values/filtershow_ids.xml b/res/values/filtershow_ids.xml
index 28e78166d..9380740ce 100644
--- a/res/values/filtershow_ids.xml
+++ b/res/values/filtershow_ids.xml
@@ -42,4 +42,8 @@
<item type="id" name="editorRedEye" />
<item type="id" name="imageOnlyEditor" />
<item type="id" name="vignetteEditor" />
+ <item type="id" name="editorCrop" />
+ <item type="id" name="editorFlip" />
+ <item type="id" name="editorRotate" />
+ <item type="id" name="editorStraighten" />
</resources>
diff --git a/res/values/filtershow_strings.xml b/res/values/filtershow_strings.xml
index 3e9e35508..80d0fc6b9 100644
--- a/res/values/filtershow_strings.xml
+++ b/res/values/filtershow_strings.xml
@@ -62,7 +62,7 @@
<!-- Image state panel -->
<!-- Text for the image state panel title [CHAR LIMIT=50] -->
- <string name="imageState">Current Image State</string>
+ <string name="imageState">Applied Effects</string>
<!-- Additional filters buttons -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fae3466cc..263b8b15b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1002,4 +1002,12 @@ CHAR LIMIT = NONE] -->
<!-- Positive answer for first run dialog asking if the user wants to remember photo locations [CHAR LIMIT = 20] -->
<string name="remember_location_yes">Yes</string>
+ <!-- Menu item to launch the camera app [CHAR LIMIT=25] -->
+ <string name="menu_camera">Camera</string>
+ <!-- Menu item to search for photos [CHAR LIMIT=25] -->
+ <string name="menu_search">Search</string>
+ <!-- Title for the all photos tab [CHAR LIMIT=25] -->
+ <string name="tab_photos">Photos</string>
+ <!-- Title for the albums tab [CHAR LIMIT=25] -->
+ <string name="tab_albums">Albums</string>
</resources>
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index a207b2b71..24c49ba8a 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -19,8 +19,8 @@ package com.android.camera;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.content.Context;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
@@ -30,16 +30,18 @@ import android.os.Bundle;
import android.os.IBinder;
import android.provider.MediaStore;
import android.provider.Settings;
-import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.camera.ui.CameraSwitcher;
+import com.android.camera.ui.RotatableLayout;
import com.android.gallery3d.R;
import com.android.gallery3d.app.PhotoPage;
import com.android.gallery3d.common.ApiHelper;
@@ -56,12 +58,14 @@ public class CameraActivity extends ActivityBase
private FrameLayout mFrame;
private ShutterButton mShutter;
private CameraSwitcher mSwitcher;
- private View mShutterSwitcher;
+ private View mCameraControls;
private View mControlsBackground;
+ private View mPieMenuButton;
private Drawable[] mDrawables;
private int mCurrentModuleIndex;
private MotionEvent mDown;
private boolean mAutoRotateScreen;
+ private int mHeightOrWidth = -1;
private MyOrientationEventListener mOrientationListener;
// The degrees of the device rotated clockwise from its natural orientation.
@@ -92,7 +96,7 @@ public class CameraActivity extends ActivityBase
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.camera_main);
- mFrame = (FrameLayout) findViewById(R.id.main_content);
+ mFrame = (FrameLayout) findViewById(R.id.camera_app_root);
mDrawables = new Drawable[DRAW_IDS.length];
for (int i = 0; i < DRAW_IDS.length; i++) {
mDrawables[i] = getResources().getDrawable(DRAW_IDS[i]);
@@ -113,10 +117,13 @@ public class CameraActivity extends ActivityBase
}
public void init() {
- mControlsBackground = findViewById(R.id.controls);
- mShutterSwitcher = findViewById(R.id.camera_shutter_switcher);
+ boolean landscape = Util.getDisplayRotation(this) % 180 == 90;
+ setMargins(landscape);
+ mControlsBackground = findViewById(R.id.blocker);
+ mCameraControls = findViewById(R.id.camera_controls);
mShutter = (ShutterButton) findViewById(R.id.shutter_button);
mSwitcher = (CameraSwitcher) findViewById(R.id.camera_switcher);
+ mPieMenuButton = findViewById(R.id.menu);
int totaldrawid = (LightCycleHelper.hasLightCycleCapture(this)
? DRAW_IDS.length : DRAW_IDS.length - 1);
if (!ApiHelper.HAS_OLD_PANORAMA) totaldrawid--;
@@ -215,6 +222,8 @@ public class CameraActivity extends ActivityBase
mCurrentModule = LightCycleHelper.createPanoramaModule();
break;
}
+ showPieMenuButton(mCurrentModule.needsPieMenu());
+
openModule(mCurrentModule, canReuse);
mCurrentModule.onOrientationChanged(mLastRawOrientation);
if (mMediaSaveService != null) {
@@ -224,6 +233,18 @@ public class CameraActivity extends ActivityBase
getCameraScreenNail().setOnFrameDrawnOneShot(mOnFrameDrawn);
}
+ public void showPieMenuButton(boolean show) {
+ if (show) {
+ findViewById(R.id.blocker).setVisibility(View.VISIBLE);
+ findViewById(R.id.menu).setVisibility(View.VISIBLE);
+ findViewById(R.id.on_screen_indicators).setVisibility(View.VISIBLE);
+ } else {
+ findViewById(R.id.blocker).setVisibility(View.INVISIBLE);
+ findViewById(R.id.menu).setVisibility(View.INVISIBLE);
+ findViewById(R.id.on_screen_indicators).setVisibility(View.INVISIBLE);
+ }
+ }
+
private Runnable mOnFrameDrawn = new Runnable() {
@Override
@@ -266,13 +287,13 @@ public class CameraActivity extends ActivityBase
}
public void hideUI() {
- mControlsBackground.setVisibility(View.INVISIBLE);
+ mCameraControls.setVisibility(View.INVISIBLE);
hideSwitcher();
mShutter.setVisibility(View.GONE);
}
public void showUI() {
- mControlsBackground.setVisibility(View.VISIBLE);
+ mCameraControls.setVisibility(View.VISIBLE);
showSwitcher();
mShutter.setVisibility(View.VISIBLE);
// Force a layout change to show shutter button
@@ -297,23 +318,22 @@ public class CameraActivity extends ActivityBase
@Override
public void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
+ boolean landscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE);
+ setMargins(landscape);
+ mCurrentModule.onConfigurationChanged(config);
+ }
+ private void setMargins(boolean landscape) {
ViewGroup appRoot = (ViewGroup) findViewById(R.id.content);
- // remove old switcher, shutter and shutter icon
- View cameraControlsView = findViewById(R.id.camera_shutter_switcher);
- appRoot.removeView(cameraControlsView);
-
- // create new layout with the current orientation
- LayoutInflater inflater = getLayoutInflater();
- inflater.inflate(R.layout.camera_shutter_switcher, appRoot);
- init();
-
- if (mShowCameraAppView) {
- showUI();
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) appRoot.getLayoutParams();
+ int navBarWidth = getResources().getDimensionPixelSize(R.dimen.navigation_bar_width);
+ int navBarHeight = getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
+ if (landscape) {
+ lp.setMargins(navBarHeight, 0, navBarHeight - navBarWidth, 0);
} else {
- hideUI();
+ lp.setMargins(0, navBarHeight, 0, 0);
}
- mCurrentModule.onConfigurationChanged(config);
+ appRoot.setLayoutParams(lp);
}
@Override
@@ -362,9 +382,23 @@ public class CameraActivity extends ActivityBase
hideUI();
}
super.onFullScreenChanged(full);
+ if (ApiHelper.HAS_ROTATION_ANIMATION) {
+ setRotationAnimation(full);
+ }
mCurrentModule.onFullScreenChanged(full);
}
+ private void setRotationAnimation(boolean fullscreen) {
+ int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
+ if (fullscreen) {
+ rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
+ }
+ Window win = getWindow();
+ WindowManager.LayoutParams winParams = win.getAttributes();
+ winParams.rotationAnimation = rotationAnimation;
+ win.setAttributes(winParams);
+ }
+
@Override
protected void onStop() {
super.onStop();
@@ -440,9 +474,10 @@ public class CameraActivity extends ActivityBase
}
if ((mSwitcher != null) && mSwitcher.showsPopup() && !mSwitcher.isInsidePopup(m)) {
return mSwitcher.onTouch(null, m);
+ } else if ((mSwitcher != null) && mSwitcher.isInsidePopup(m)) {
+ return superDispatchTouchEvent(m);
} else {
- return mShutterSwitcher.dispatchTouchEvent(m)
- || mCurrentModule.dispatchTouchEvent(m);
+ return mCurrentModule.dispatchTouchEvent(m);
}
}
diff --git a/src/com/android/camera/CameraModule.java b/src/com/android/camera/CameraModule.java
index 37eabd0a3..aa057b916 100644
--- a/src/com/android/camera/CameraModule.java
+++ b/src/com/android/camera/CameraModule.java
@@ -68,6 +68,8 @@ public interface CameraModule {
public boolean needsSwitcher();
+ public boolean needsPieMenu();
+
public void onOrientationChanged(int orientation);
public void onShowSwitcherPopup();
diff --git a/src/com/android/camera/PanoramaModule.java b/src/com/android/camera/PanoramaModule.java
index 4edc68657..d12c8286e 100644
--- a/src/com/android/camera/PanoramaModule.java
+++ b/src/com/android/camera/PanoramaModule.java
@@ -714,13 +714,13 @@ public class PanoramaModule implements CameraModule,
}
private void createContentView() {
- mActivity.getLayoutInflater().inflate(R.layout.panorama_module, (ViewGroup) mRootView);
+ mActivity.getLayoutInflater().inflate(R.layout.panorama_module, (ViewGroup) mRootView, true);
Resources appRes = mActivity.getResources();
- mCaptureLayout = (LinearLayout) mRootView.findViewById(R.id.camera_app_root);
+ mCaptureLayout = (LinearLayout) mRootView.findViewById(R.id.camera_app);
mIndicatorColor = appRes.getColor(R.color.pano_progress_indication);
mReviewBackground = appRes.getColor(R.color.review_background);
mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast);
- mPanoLayout = (ViewGroup) mRootView.findViewById(R.id.pano_layout);
+ mPanoLayout = (ViewGroup) mRootView.findViewById(R.id.camera_app_root);
mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog);
setViews(appRes);
}
@@ -1308,6 +1308,11 @@ public class PanoramaModule implements CameraModule,
}
@Override
+ public boolean needsPieMenu() {
+ return false;
+ }
+
+ @Override
public void onShowSwitcherPopup() {
}
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 15f40461d..4049aa573 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -52,7 +52,9 @@ import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.SurfaceHolder;
import android.view.View;
+import android.view.ViewStub;
import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -66,11 +68,8 @@ import com.android.camera.ui.CountDownView;
import com.android.camera.ui.FaceView;
import com.android.camera.ui.PieRenderer;
import com.android.camera.ui.PopupManager;
-import com.android.camera.ui.PreviewSurfaceView;
import com.android.camera.ui.RenderOverlay;
-import com.android.camera.ui.Rotatable;
import com.android.camera.ui.RotateTextToast;
-import com.android.camera.ui.TwoStateImageView;
import com.android.camera.ui.ZoomRenderer;
import com.android.gallery3d.R;
import com.android.gallery3d.common.ApiHelper;
@@ -92,7 +91,6 @@ public class PhotoModule
FocusOverlayManager.Listener,
CameraPreference.OnPreferenceChangedListener,
LocationManager.Listener,
- PreviewFrameLayout.OnSizeChangedListener,
ShutterButton.OnShutterButtonListener,
SurfaceHolder.Callback,
PieRenderer.PieListener,
@@ -172,18 +170,15 @@ public class PhotoModule
private ShutterButton mShutterButton;
private boolean mFaceDetectionStarted = false;
- private PreviewFrameLayout mPreviewFrameLayout;
private Object mSurfaceTexture;
private CountDownView mCountDownView;
- // for API level 10
- private PreviewSurfaceView mPreviewSurfaceView;
private volatile SurfaceHolder mCameraSurfaceHolder;
private FaceView mFaceView;
private RenderOverlay mRenderOverlay;
- private Rotatable mReviewCancelButton;
- private Rotatable mReviewDoneButton;
+ private View mReviewCancelButton;
+ private View mReviewDoneButton;
private View mReviewRetakeButton;
// mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
@@ -212,6 +207,16 @@ public class PhotoModule
}
};
+ private final View.OnLayoutChangeListener mLayoutChangeListener =
+ new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ onScreenSizeChanged(right - left, bottom - top);
+ }
+ };
+ private int mPreviewWidth = 0;
+ private int mPreviewHeight = 0;
private final StringBuilder mBuilder = new StringBuilder();
private final Formatter mFormatter = new Formatter(mBuilder);
private final Object[] mFormatterArgs = new Object[1];
@@ -288,7 +293,6 @@ public class PhotoModule
private PhotoController mPhotoControl;
private ZoomRenderer mZoomRenderer;
-
private String mSceneMode;
private Toast mNotSelectableToast;
@@ -466,7 +470,17 @@ public class PhotoModule
mCameraStartUpThread = new CameraStartUpThread();
mCameraStartUpThread.start();
- mActivity.getLayoutInflater().inflate(R.layout.photo_module, (ViewGroup) mRootView);
+ mActivity.getLayoutInflater().inflate(R.layout.photo_module,
+ (ViewGroup) mRootView, true);
+ mRootView.addOnLayoutChangeListener(mLayoutChangeListener);
+ if (ApiHelper.HAS_FACE_DETECTION) {
+ ViewStub faceViewStub = (ViewStub) mRootView
+ .findViewById(R.id.face_view_stub);
+ if (faceViewStub != null) {
+ faceViewStub.inflate();
+ mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
+ }
+ }
// Surface texture is from camera screen nail and startPreview needs it.
// This must be done before startPreview.
@@ -487,7 +501,6 @@ public class PhotoModule
initializeControlByIntent();
mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
- initializeMiscControls();
mLocationManager = new LocationManager(mActivity, this);
initOnScreenIndicator();
mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
@@ -557,10 +570,10 @@ public class PhotoModule
if (isImageCaptureIntent()) {
if (mReviewCancelButton != null) {
- mGestures.addTouchReceiver((View) mReviewCancelButton);
+ mGestures.addTouchReceiver(mReviewCancelButton);
}
if (mReviewDoneButton != null) {
- mGestures.addTouchReceiver((View) mReviewDoneButton);
+ mGestures.addTouchReceiver(mReviewDoneButton);
}
}
}
@@ -585,9 +598,17 @@ public class PhotoModule
initializePhotoControl();
// These depend on camera parameters.
- setPreviewFrameLayoutAspectRatio();
- mFocusManager.setPreviewSize(mPreviewFrameLayout.getWidth(),
- mPreviewFrameLayout.getHeight());
+ int width = mRootView.getWidth();
+ int height = mRootView.getHeight();
+ mFocusManager.setPreviewSize(width, height);
+ // Full-screen screennail
+ if (Util.getDisplayRotation(mActivity) % 180 == 0) {
+ ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(width, height);
+ } else {
+ ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(height, width);
+ }
+ // Set touch focus listener.
+ mActivity.setSingleTapUpListener(mRootView);
loadCameraPreferences();
initializeZoom();
updateOnScreenIndicators();
@@ -595,6 +616,21 @@ public class PhotoModule
onFullScreenChanged(mActivity.isInCameraApp());
}
+ public void onScreenSizeChanged(int width, int height) {
+ // Full-screen screennail
+ int w = width;
+ int h = height;
+ if (Util.getDisplayRotation(mActivity) % 180 != 0) {
+ w = height;
+ h = width;
+ }
+ if (mPreviewWidth != w || mPreviewHeight != h) {
+ Log.d(TAG, "Preview size changed.");
+ if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
+ ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(w, h);
+ }
+ }
+
private void initializePhotoControl() {
loadCameraPreferences();
if (mPhotoControl != null) {
@@ -795,7 +831,7 @@ public class PhotoModule
}
private void initOnScreenIndicator() {
- mOnScreenIndicators = mRootView.findViewById(R.id.on_screen_indicators);
+ mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators);
mExposureIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_exposure_indicator);
mFlashIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_flash_indicator);
mSceneIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_scenemode_indicator);
@@ -1217,11 +1253,6 @@ public class PhotoModule
}
return;
}
- if (full) {
- mPreviewSurfaceView.expand();
- } else {
- mPreviewSurfaceView.shrink();
- }
}
@Override
@@ -1586,10 +1617,9 @@ public class PhotoModule
mCountDownView.cancelCountDown();
// Close the camera now because other activities may need to use it.
closeCamera();
- if (mSurfaceTexture != null) {
- ((CameraScreenNail) mActivity.mCameraScreenNail).releaseSurfaceTexture();
- mSurfaceTexture = null;
- }
+ // Release surface texture.
+ ((CameraScreenNail) mActivity.mCameraScreenNail).releaseSurfaceTexture();
+ mSurfaceTexture = null;
resetScreenOn();
// Clear UI.
@@ -1615,6 +1645,9 @@ public class PhotoModule
mHandler.removeMessages(OPEN_CAMERA_FAIL);
mHandler.removeMessages(CAMERA_DISABLED);
+ mRootView.removeOnLayoutChangeListener(mLayoutChangeListener);
+ mPreviewWidth = 0;
+ mPreviewHeight = 0;
mPendingSwitchCameraId = -1;
if (mFocusManager != null) mFocusManager.removeMessages();
MediaSaveService s = mActivity.getMediaSaveService();
@@ -1624,8 +1657,8 @@ public class PhotoModule
}
private void initializeControlByIntent() {
- mBlocker = mRootView.findViewById(R.id.blocker);
- mMenu = mRootView.findViewById(R.id.menu);
+ mBlocker = mActivity.findViewById(R.id.blocker);
+ mMenu = mActivity.findViewById(R.id.menu);
mMenu.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -1638,23 +1671,22 @@ public class PhotoModule
}
});
if (mIsImageCaptureIntent) {
-
mActivity.hideSwitcher();
- // Cannot use RotateImageView for "done" and "cancel" button because
- // the tablet layout uses RotateLayout, which cannot be cast to
- // RotateImageView.
- mReviewDoneButton = (Rotatable) mRootView.findViewById(R.id.btn_done);
- mReviewCancelButton = (Rotatable) mRootView.findViewById(R.id.btn_cancel);
- mReviewRetakeButton = mRootView.findViewById(R.id.btn_retake);
- ((View) mReviewCancelButton).setVisibility(View.VISIBLE);
-
- ((View) mReviewDoneButton).setOnClickListener(new OnClickListener() {
+ ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
+ mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
+
+ mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
+ mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
+ mReviewRetakeButton = mActivity.findViewById(R.id.btn_retake);
+ mReviewCancelButton.setVisibility(View.VISIBLE);
+
+ mReviewDoneButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onReviewDoneClicked(v);
}
});
- ((View) mReviewCancelButton).setOnClickListener(new OnClickListener() {
+ mReviewCancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onReviewCancelClicked(v);
@@ -1668,13 +1700,6 @@ public class PhotoModule
}
});
- // Not grayed out upon disabled, to make the follow-up fade-out
- // effect look smooth. Note that the review done button in tablet
- // layout is not a TwoStateImageView.
- if (mReviewDoneButton instanceof TwoStateImageView) {
- ((TwoStateImageView) mReviewDoneButton).enableFilter(false);
- }
-
setupCaptureParams();
}
}
@@ -1701,62 +1726,10 @@ public class PhotoModule
}
}
- private void initializeMiscControls() {
- // startPreview needs this.
- mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame);
- // Set touch focus listener.
- mActivity.setSingleTapUpListener(mPreviewFrameLayout);
-
- mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
- mPreviewFrameLayout.setOnSizeChangedListener(this);
- mPreviewFrameLayout.setOnLayoutChangeListener(mActivity);
- if (!ApiHelper.HAS_SURFACE_TEXTURE) {
- mPreviewSurfaceView =
- (PreviewSurfaceView) mRootView.findViewById(R.id.preview_surface_view);
- mPreviewSurfaceView.setVisibility(View.VISIBLE);
- mPreviewSurfaceView.getHolder().addCallback(this);
- }
- }
-
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.v(TAG, "onConfigurationChanged");
setDisplayOrientation();
-
- // Only the views in photo_module_content need to be removed and recreated
- // i.e. CountDownView won't be recreated
- ViewGroup viewGroup = (ViewGroup) mRootView.findViewById(R.id.camera_app);
- viewGroup.removeAllViews();
- LayoutInflater inflater = mActivity.getLayoutInflater();
- inflater.inflate(R.layout.photo_module_content, (ViewGroup) viewGroup);
-
- // from onCreate()
- initializeControlByIntent();
-
- initializeFocusManager();
- initializeMiscControls();
- loadCameraPreferences();
-
- // from initializeFirstTime()
- mShutterButton = mActivity.getShutterButton();
- mShutterButton.setOnShutterButtonListener(this);
- initializeZoom();
- initOnScreenIndicator();
- updateOnScreenIndicators();
- if (mFaceView != null) {
- mFaceView.clear();
- mFaceView.setVisibility(View.VISIBLE);
- mFaceView.setDisplayOrientation(mDisplayOrientation);
- CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
- mFaceView.setMirror(info.facing == CameraInfo.CAMERA_FACING_FRONT);
- mFaceView.resume();
- mFocusManager.setFaceView(mFaceView);
- }
- initializeRenderOverlay();
- onFullScreenChanged(mActivity.isInCameraApp());
- if (mJpegImageData != null) { // Jpeg data found, picture has been taken.
- showPostCaptureAlert();
- }
}
@Override
@@ -2259,7 +2232,7 @@ public class PhotoModule
if (mIsImageCaptureIntent) {
mOnScreenIndicators.setVisibility(View.GONE);
mMenu.setVisibility(View.GONE);
- Util.fadeIn((View) mReviewDoneButton);
+ Util.fadeIn(mReviewDoneButton);
mShutterButton.setVisibility(View.INVISIBLE);
Util.fadeIn(mReviewRetakeButton);
}
@@ -2269,7 +2242,7 @@ public class PhotoModule
if (mIsImageCaptureIntent) {
mOnScreenIndicators.setVisibility(View.VISIBLE);
mMenu.setVisibility(View.VISIBLE);
- Util.fadeOut((View) mReviewDoneButton);
+ Util.fadeOut(mReviewDoneButton);
mShutterButton.setVisibility(View.VISIBLE);
Util.fadeOut(mReviewRetakeButton);
}
@@ -2285,7 +2258,6 @@ public class PhotoModule
mLocationManager.recordLocation(recordLocation);
setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
- setPreviewFrameLayoutAspectRatio();
updateOnScreenIndicators();
}
@@ -2432,12 +2404,6 @@ public class PhotoModule
Util.FOCUS_MODE_CONTINUOUS_PICTURE);
}
- // PreviewFrameLayout size has changed.
- @Override
- public void onSizeChanged(int width, int height) {
- if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
- }
-
@Override
public void onCountDownFinished() {
mSnapshotOnIdle = false;
@@ -2445,17 +2411,16 @@ public class PhotoModule
mFocusManager.onShutterUp();
}
- void setPreviewFrameLayoutAspectRatio() {
- // Set the preview frame aspect ratio according to the picture size.
- Size size = mParameters.getPictureSize();
- mPreviewFrameLayout.setAspectRatio((double) size.width / size.height);
- }
-
@Override
public boolean needsSwitcher() {
return !mIsImageCaptureIntent;
}
+ @Override
+ public boolean needsPieMenu() {
+ return true;
+ }
+
public void showPopup(AbstractSettingPopup popup) {
mActivity.hideUI();
mBlocker.setVisibility(View.INVISIBLE);
diff --git a/src/com/android/camera/PreviewFrameLayout.java b/src/com/android/camera/PreviewFrameLayout.java
index 87e3c8dfc..03ef91c60 100644
--- a/src/com/android/camera/PreviewFrameLayout.java
+++ b/src/com/android/camera/PreviewFrameLayout.java
@@ -54,15 +54,6 @@ public class PreviewFrameLayout extends RelativeLayout implements LayoutChangeNo
@Override
protected void onFinishInflate() {
mBorder = findViewById(R.id.preview_border);
- if (ApiHelper.HAS_FACE_DETECTION) {
- ViewStub faceViewStub = (ViewStub) findViewById(R.id.face_view_stub);
- /* preview_frame_video.xml does not have face view stub, so we need to
- * check that.
- */
- if (faceViewStub != null) {
- faceViewStub.inflate();
- }
- }
}
public void setAspectRatio(double ratio) {
diff --git a/src/com/android/camera/ShutterButton.java b/src/com/android/camera/ShutterButton.java
index a1bbb1a0d..228fc51a3 100755
--- a/src/com/android/camera/ShutterButton.java
+++ b/src/com/android/camera/ShutterButton.java
@@ -17,11 +17,14 @@
package com.android.camera;
import android.content.Context;
+import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
+import com.android.camera.ui.RotatableLayout;
+
/**
* A button designed to be used for the on-screen shutter button.
* It's currently an {@code ImageView} that can call a delegate when the
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 245ef5990..7bda657a2 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -73,7 +73,6 @@ import com.android.camera.ui.Rotatable;
import com.android.camera.ui.RotateImageView;
import com.android.camera.ui.RotateLayout;
import com.android.camera.ui.RotateTextToast;
-import com.android.camera.ui.TwoStateImageView;
import com.android.camera.ui.ZoomRenderer;
import com.android.gallery3d.R;
import com.android.gallery3d.common.ApiHelper;
@@ -142,14 +141,13 @@ public class VideoModule implements CameraModule,
private SurfaceHolder.Callback mSurfaceViewCallback;
private PreviewSurfaceView mPreviewSurfaceView;
private CameraScreenNail.OnFrameDrawnListener mFrameDrawnListener;
- private View mReviewControl;
// An review image having same size as preview. It is displayed when
// recording is stopped in capture intent.
private ImageView mReviewImage;
- private Rotatable mReviewCancelButton;
- private Rotatable mReviewDoneButton;
- private RotateImageView mReviewPlayButton;
+ private View mReviewCancelButton;
+ private View mReviewDoneButton;
+ private View mReviewPlayButton;
private ShutterButton mShutterButton;
private TextView mRecordingTimeView;
private RotateLayout mBgLearningMessageRotater;
@@ -414,13 +412,13 @@ public class VideoModule implements CameraModule,
if (isVideoCaptureIntent()) {
if (mReviewCancelButton != null) {
- mGestures.addTouchReceiver((View) mReviewCancelButton);
+ mGestures.addTouchReceiver(mReviewCancelButton);
}
if (mReviewDoneButton != null) {
- mGestures.addTouchReceiver((View) mReviewDoneButton);
+ mGestures.addTouchReceiver(mReviewDoneButton);
}
if (mReviewPlayButton != null) {
- mGestures.addTouchReceiver((View) mReviewPlayButton);
+ mGestures.addTouchReceiver(mReviewPlayButton);
}
}
}
@@ -449,7 +447,7 @@ public class VideoModule implements CameraModule,
mContentResolver = mActivity.getContentResolver();
- mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView);
+ mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true);
// Surface texture is from camera screen nail and startPreview needs it.
// This must be done before startPreview.
@@ -589,8 +587,7 @@ public class VideoModule implements CameraModule,
private void setOrientationIndicator(int orientation, boolean animation) {
Rotatable[] indicators = {
- mBgLearningMessageRotater,
- mReviewDoneButton, mReviewPlayButton};
+ mBgLearningMessageRotater};
for (Rotatable indicator : indicators) {
if (indicator != null) indicator.setOrientation(orientation, animation);
}
@@ -598,14 +595,6 @@ public class VideoModule implements CameraModule,
mGestures.setOrientation(orientation);
}
- // We change the orientation of the review cancel button only for tablet
- // UI because there's a label along with the X icon. For phone UI, we
- // don't change the orientation because there's only a symmetrical X
- // icon.
- if (mReviewCancelButton instanceof RotateLayout) {
- mReviewCancelButton.setOrientation(orientation, animation);
- }
-
// We change the orientation of the linearlayout only for phone UI because when in portrait
// the width is not enough.
if (mLabelsLinearLayout != null) {
@@ -1636,7 +1625,6 @@ public class VideoModule implements CameraModule,
mActivity.hideSwitcher();
mRecordingTimeView.setText("");
mRecordingTimeView.setVisibility(View.VISIBLE);
- if (mReviewControl != null) mReviewControl.setVisibility(View.GONE);
// The camera is not allowed to be accessed in older api levels during
// recording. It is therefore necessary to hide the zoom UI on older
// platforms.
@@ -1650,7 +1638,6 @@ public class VideoModule implements CameraModule,
mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
mActivity.showSwitcher();
mRecordingTimeView.setVisibility(View.GONE);
- if (mReviewControl != null) mReviewControl.setVisibility(View.VISIBLE);
if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING
&& mParameters.isZoomSupported()) {
// TODO: enable zoom UI here.
@@ -1679,7 +1666,7 @@ public class VideoModule implements CameraModule,
Util.fadeOut(mShutterButton);
- Util.fadeIn((View) mReviewDoneButton);
+ Util.fadeIn(mReviewDoneButton);
Util.fadeIn(mReviewPlayButton);
mMenu.setVisibility(View.GONE);
mOnScreenIndicators.setVisibility(View.GONE);
@@ -1695,7 +1682,7 @@ public class VideoModule implements CameraModule,
mOnScreenIndicators.setVisibility(View.VISIBLE);
enableCameraControls(true);
- Util.fadeOut((View) mReviewDoneButton);
+ Util.fadeOut(mReviewDoneButton);
Util.fadeOut(mReviewPlayButton);
Util.fadeIn(mShutterButton);
@@ -2115,8 +2102,8 @@ public class VideoModule implements CameraModule,
}
private void initializeControlByIntent() {
- mBlocker = mRootView.findViewById(R.id.blocker);
- mMenu = mRootView.findViewById(R.id.menu);
+ mBlocker = mActivity.findViewById(R.id.blocker);
+ mMenu = mActivity.findViewById(R.id.menu);
mMenu.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -2125,46 +2112,40 @@ public class VideoModule implements CameraModule,
}
}
});
- mOnScreenIndicators = mRootView.findViewById(R.id.on_screen_indicators);
- mFlashIndicator = (ImageView) mRootView.findViewById(R.id.menu_flash_indicator);
+ mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators);
+ mFlashIndicator = (ImageView) mActivity.findViewById(R.id.menu_flash_indicator);
if (mIsVideoCaptureIntent) {
mActivity.hideSwitcher();
+ ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
+ mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
// Cannot use RotateImageView for "done" and "cancel" button because
// the tablet layout uses RotateLayout, which cannot be cast to
// RotateImageView.
- mReviewDoneButton = (Rotatable) mRootView.findViewById(R.id.btn_done);
- mReviewCancelButton = (Rotatable) mRootView.findViewById(R.id.btn_cancel);
- mReviewPlayButton = (RotateImageView) mRootView.findViewById(R.id.btn_play);
+ mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
+ mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
+ mReviewPlayButton = mActivity.findViewById(R.id.btn_play);
- ((View) mReviewCancelButton).setVisibility(View.VISIBLE);
+ mReviewCancelButton.setVisibility(View.VISIBLE);
- ((View) mReviewDoneButton).setOnClickListener(new OnClickListener() {
+ mReviewDoneButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onReviewDoneClicked(v);
}
});
- ((View) mReviewCancelButton).setOnClickListener(new OnClickListener() {
+ mReviewCancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onReviewCancelClicked(v);
}
});
- ((View) mReviewPlayButton).setOnClickListener(new OnClickListener() {
+ mReviewPlayButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onReviewPlayClicked(v);
}
});
-
-
- // Not grayed out upon disabled, to make the follow-up fade-out
- // effect look smooth. Note that the review done button in tablet
- // layout is not a TwoStateImageView.
- if (mReviewDoneButton instanceof TwoStateImageView) {
- ((TwoStateImageView) mReviewDoneButton).enableFilter(false);
- }
}
}
@@ -2203,28 +2184,8 @@ public class VideoModule implements CameraModule,
@Override
public void onConfigurationChanged(Configuration newConfig) {
+ Log.v(TAG, "onConfigurationChanged");
setDisplayOrientation();
- // Change layout in response to configuration change
- LayoutInflater inflater = mActivity.getLayoutInflater();
- ((ViewGroup) mRootView).removeAllViews();
- inflater.inflate(R.layout.video_module, (ViewGroup) mRootView);
-
- // from onCreate()
- initializeControlByIntent();
- initializeOverlay();
- initializeSurfaceView();
- initializeMiscControls();
- showTimeLapseUI(mCaptureTimeLapse);
- initializeVideoSnapshot();
-
- // from onResume()
- showVideoSnapshotUI(false);
- initializeZoom();
- onFullScreenChanged(mActivity.isInCameraApp());
- updateOnScreenIndicators();
- if (mIsVideoCaptureIntent && mVideoFileDescriptor != null) {
- showCaptureResult();
- }
}
@Override
@@ -2785,6 +2746,11 @@ public class VideoModule implements CameraModule,
}
@Override
+ public boolean needsPieMenu() {
+ return true;
+ }
+
+ @Override
public void onPieOpened(int centerX, int centerY) {
mActivity.cancelActivityTouchHandling();
mActivity.setSwipingEnabled(false);
diff --git a/src/com/android/camera/ui/CameraSwitcher.java b/src/com/android/camera/ui/CameraSwitcher.java
index 8d2cd7117..ce4f85003 100644
--- a/src/com/android/camera/ui/CameraSwitcher.java
+++ b/src/com/android/camera/ui/CameraSwitcher.java
@@ -19,19 +19,23 @@ package com.android.camera.ui;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
+import android.widget.FrameLayout.LayoutParams;
import android.widget.LinearLayout;
+import com.android.camera.Util;
import com.android.gallery3d.R;
import com.android.gallery3d.common.ApiHelper;
@@ -119,6 +123,13 @@ public class CameraSwitcher extends RotateImageView
(ViewGroup) getParent());
LinearLayout content = (LinearLayout) mParent.findViewById(R.id.content);
mPopup = content;
+ // Set the gravity of the popup, so that it shows up at the right position
+ // on screen
+ LayoutParams lp = ((LayoutParams) mPopup.getLayoutParams());
+ lp.gravity = ((LayoutParams) mParent.findViewById(R.id.camera_switcher)
+ .getLayoutParams()).gravity;
+ mPopup.setLayoutParams(lp);
+
mPopup.setVisibility(View.INVISIBLE);
mNeedsAnimationSetup = true;
for (int i = mDrawIds.length - 1; i >= 0; i--) {
@@ -129,7 +140,7 @@ public class CameraSwitcher extends RotateImageView
item.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- onCameraSelected(index);
+ if (showsPopup()) onCameraSelected(index);
}
});
switch (mDrawIds[i]) {
@@ -162,10 +173,14 @@ public class CameraSwitcher extends RotateImageView
public boolean isInsidePopup(MotionEvent evt) {
if (!showsPopup()) return false;
- return evt.getX() >= mPopup.getLeft()
- && evt.getX() < mPopup.getRight()
- && evt.getY() >= mPopup.getTop()
- && evt.getY() < mPopup.getBottom();
+ int topLeft[] = new int[2];
+ mPopup.getLocationOnScreen(topLeft);
+ int left = topLeft[0];
+ int top = topLeft[1];
+ int bottom = top + mPopup.getHeight();
+ int right = left + mPopup.getWidth();
+ return evt.getX() >= left && evt.getX() < right
+ && evt.getY() >= top && evt.getY() < bottom;
}
private void hidePopup() {
@@ -177,6 +192,16 @@ public class CameraSwitcher extends RotateImageView
mParent.setOnTouchListener(null);
}
+ @Override
+ public void onConfigurationChanged(Configuration config) {
+ if (showsPopup()) {
+ ((ViewGroup) mParent).removeView(mPopup);
+ mPopup = null;
+ initPopup();
+ mPopup.setVisibility(View.VISIBLE);
+ }
+ }
+
private void showSwitcher() {
mShowingPopup = true;
if (mPopup == null) {
@@ -213,15 +238,22 @@ public class CameraSwitcher extends RotateImageView
}
private void updateInitialTranslations() {
- if (getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_PORTRAIT) {
+ int orientation = Util.getDisplayRotation((Activity) getContext());
+ if (orientation == 0) {
mTranslationX = -getWidth() / 2;
mTranslationY = getHeight();
- } else {
+ } else if (orientation == 90) {
mTranslationX = getWidth();
mTranslationY = getHeight() / 2;
+ } else if (orientation == 180) {
+ mTranslationX = getWidth();
+ mTranslationY = -getHeight() / 2;
+ } else {
+ mTranslationX = -getWidth();
+ mTranslationY = -getHeight() / 2;
}
}
+
private void popupAnimationSetup() {
if (!ApiHelper.HAS_VIEW_PROPERTY_ANIMATOR) {
return;
@@ -243,8 +275,10 @@ public class CameraSwitcher extends RotateImageView
@Override
public void onAnimationEnd(Animator animation) {
// Verify that we weren't canceled
- if (!showsPopup()) {
+ if (!showsPopup() && mPopup != null) {
mPopup.setVisibility(View.INVISIBLE);
+ ((ViewGroup) mParent).removeView(mPopup);
+ mPopup = null;
}
}
};
diff --git a/src/com/android/camera/ui/PieRenderer.java b/src/com/android/camera/ui/PieRenderer.java
index 95597b2a1..b60d9f668 100644
--- a/src/com/android/camera/ui/PieRenderer.java
+++ b/src/com/android/camera/ui/PieRenderer.java
@@ -16,8 +16,6 @@
package com.android.camera.ui;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -37,7 +35,6 @@ import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import com.android.gallery3d.R;
-import com.android.gallery3d.common.ApiHelper;
import java.util.ArrayList;
import java.util.List;
@@ -63,6 +60,8 @@ public class PieRenderer extends OverlayRenderer
private static final int SCALING_DOWN_TIME = 100;
private static final int DISAPPEAR_TIMEOUT = 200;
private static final int DIAL_HORIZONTAL = 157;
+ // fade out timings
+ private static final int PIE_FADE_OUT_DURATION = 600;
private static final long PIE_FADE_IN_DURATION = 200;
private static final long PIE_XFADE_DURATION = 200;
@@ -117,6 +116,7 @@ public class PieRenderer extends OverlayRenderer
private boolean mOpening;
private LinearAnimation mXFade;
private LinearAnimation mFadeIn;
+ private FadeOutAnimation mFadeOut;
private volatile boolean mFocusCancelled;
private Handler mHandler = new Handler() {
@@ -347,21 +347,35 @@ public class PieRenderer extends OverlayRenderer
return (float) (360 - 180 * angle / Math.PI);
}
- private void startFadeOut() {
- if (ApiHelper.HAS_VIEW_PROPERTY_ANIMATOR) {
- mOverlay.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- deselect();
- show(false);
- mOverlay.setAlpha(1);
- super.onAnimationEnd(animation);
- }
- }).setDuration(PIE_SELECT_FADE_DURATION);
- } else {
- deselect();
- show(false);
+ private void startFadeOut(final PieItem item) {
+ if (mFadeIn != null) {
+ mFadeIn.cancel();
}
+ if (mXFade != null) {
+ mXFade.cancel();
+ }
+ mFadeOut = new FadeOutAnimation();
+ mFadeOut.setDuration(PIE_FADE_OUT_DURATION);
+ mFadeOut.setAnimationListener(new AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ item.performClick();
+ mFadeOut = null;
+ deselect();
+ show(false);
+ mOverlay.setAlpha(1);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+ mFadeOut.startNow();
+ mOverlay.startAnimation(mFadeOut);
}
@Override
@@ -371,6 +385,8 @@ public class PieRenderer extends OverlayRenderer
alpha = mXFade.getValue();
} else if (mFadeIn != null) {
alpha = mFadeIn.getValue();
+ } else if (mFadeOut != null) {
+ alpha = mFadeOut.getValue();
}
int state = canvas.save();
if (mFadeIn != null) {
@@ -390,7 +406,11 @@ public class PieRenderer extends OverlayRenderer
}
if (mOpenItem != null) {
for (PieItem inner : mOpenItem.getItems()) {
- drawItem(canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
+ if (mFadeOut != null) {
+ drawItem(canvas, inner, alpha);
+ } else {
+ drawItem(canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
+ }
}
}
canvas.restoreToCount(state);
@@ -404,12 +424,20 @@ public class PieRenderer extends OverlayRenderer
int state = canvas.save();
float r = getDegrees(item.getStartAngle());
canvas.rotate(r, mCenter.x, mCenter.y);
+ if (mFadeOut != null) {
+ p.setAlpha((int)(255 * alpha));
+ }
canvas.drawPath(item.getPath(), p);
+ if (mFadeOut != null) {
+ p.setAlpha(255);
+ }
canvas.restoreToCount(state);
}
- alpha = alpha * (item.isEnabled() ? 1 : 0.3f);
- // draw the item view
- item.setAlpha(alpha);
+ if (mFadeOut == null) {
+ alpha = alpha * (item.isEnabled() ? 1 : 0.3f);
+ // draw the item view
+ item.setAlpha(alpha);
+ }
item.draw(canvas);
}
}
@@ -451,8 +479,7 @@ public class PieRenderer extends OverlayRenderer
show(false);
} else if (!mOpening
&& !item.hasItems()) {
- item.performClick();
- startFadeOut();
+ startFadeOut(item);
mTapMode = false;
}
return true;
@@ -527,6 +554,9 @@ public class PieRenderer extends OverlayRenderer
mCurrentItem.setSelected(false);
mOpenItem = mCurrentItem;
mOpening = true;
+ if (mFadeIn != null) {
+ mFadeIn.cancel();
+ }
mXFade = new LinearAnimation(1, 0);
mXFade.setDuration(PIE_XFADE_DURATION);
mXFade.setAnimationListener(new AnimationListener() {
@@ -780,6 +810,26 @@ public class PieRenderer extends OverlayRenderer
}
}
+ private class FadeOutAnimation extends Animation {
+
+ private float mAlpha;
+
+ public float getValue() {
+ return mAlpha;
+ }
+
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ if (interpolatedTime < 0.2) {
+ mAlpha = 1;
+ } else if (interpolatedTime < 0.3) {
+ mAlpha = 0;
+ } else {
+ mAlpha = 1 - (interpolatedTime - 0.3f) / 0.7f;
+ }
+ }
+ }
+
private class ScaleAnimation extends Animation {
private float mFrom = 1f;
private float mTo = 1f;
diff --git a/src/com/android/camera/ui/RotatableLayout.java b/src/com/android/camera/ui/RotatableLayout.java
new file mode 100644
index 000000000..4edec5dd7
--- /dev/null
+++ b/src/com/android/camera/ui/RotatableLayout.java
@@ -0,0 +1,184 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.camera.Util;
+
+/* RotatableLayout rotates itself as well as all its children when orientation
+ * changes. Specifically, when going from portrait to landscape, camera
+ * controls move from the bottom of the screen to right side of the screen
+ * (i.e. counter clockwise). Similarly, when the screen changes to portrait, we
+ * need to move the controls from right side to the bottom of the screen, which
+ * is a clockwise rotation.
+ */
+
+public class RotatableLayout extends FrameLayout {
+
+ private static final String TAG = "RotatableLayout";
+ private int mPrevRotation;
+ public RotatableLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public RotatableLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public RotatableLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onFinishInflate() { // get initial orientation
+ mPrevRotation = Util.getDisplayRotation((Activity) getContext());
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration config) {
+ super.onConfigurationChanged(config);
+ // Change the size of the layout
+ ViewGroup.LayoutParams lp = getLayoutParams();
+ int width = lp.width;
+ int height = lp.height;
+ lp.height = width;
+ lp.width = height;
+ setLayoutParams(lp);
+ // rotate all the children
+ int rotation = Util.getDisplayRotation((Activity) getContext());
+ boolean clockwise = isClockWiseRotation(mPrevRotation, rotation);
+ mPrevRotation = rotation;
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ rotate(child, clockwise);
+ }
+ }
+
+ public static boolean isClockWiseRotation(int prevRotation, int currentRotation) {
+ if (prevRotation == (currentRotation + 90) % 360) {
+ return true;
+ }
+ return false;
+ }
+
+ public static void rotate(View view, boolean isClockwise) {
+ if (isClockwise) {
+ rotateClockwise(view);
+ } else {
+ rotateCounterClockwise(view);
+ }
+ }
+
+ private static boolean contains(int value, int mask) {
+ return (value & mask) == mask;
+ }
+
+ public static void rotateClockwise(View view) {
+ if (view == null) return;
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ int gravity = lp.gravity;
+ int ngravity = 0;
+ // rotate gravity
+ if (contains(gravity, Gravity.LEFT)) {
+ ngravity |= Gravity.TOP;
+ }
+ if (contains(gravity, Gravity.RIGHT)) {
+ ngravity |= Gravity.BOTTOM;
+ }
+ if (contains(gravity, Gravity.TOP)) {
+ ngravity |= Gravity.RIGHT;
+ }
+ if (contains(gravity, Gravity.BOTTOM)) {
+ ngravity |= Gravity.LEFT;
+ }
+ if (contains(gravity, Gravity.CENTER)) {
+ ngravity |= Gravity.CENTER;
+ }
+ if (contains(gravity, Gravity.CENTER_HORIZONTAL)) {
+ ngravity |= Gravity.CENTER_VERTICAL;
+ }
+ if (contains(gravity, Gravity.CENTER_VERTICAL)) {
+ ngravity |= Gravity.CENTER_HORIZONTAL;
+ }
+ lp.gravity = ngravity;
+ int ml = lp.leftMargin;
+ int mr = lp.rightMargin;
+ int mt = lp.topMargin;
+ int mb = lp.bottomMargin;
+ lp.leftMargin = mb;
+ lp.rightMargin = mt;
+ lp.topMargin = ml;
+ lp.bottomMargin = mr;
+ int width = lp.width;
+ int height = lp.height;
+ lp.width = height;
+ lp.height = width;
+ view.setLayoutParams(lp);
+ }
+
+ public static void rotateCounterClockwise(View view) {
+ if (view == null) return;
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ int gravity = lp.gravity;
+ int ngravity = 0;
+ // change gravity
+ if (contains(gravity, Gravity.RIGHT)) {
+ ngravity |= Gravity.TOP;
+ }
+ if (contains(gravity, Gravity.LEFT)) {
+ ngravity |= Gravity.BOTTOM;
+ }
+ if (contains(gravity, Gravity.TOP)) {
+ ngravity |= Gravity.LEFT;
+ }
+ if (contains(gravity, Gravity.BOTTOM)) {
+ ngravity |= Gravity.RIGHT;
+ }
+ if (contains(gravity, Gravity.CENTER)) {
+ ngravity |= Gravity.CENTER;
+ }
+ if (contains(gravity, Gravity.CENTER_HORIZONTAL)) {
+ ngravity |= Gravity.CENTER_VERTICAL;
+ }
+ if (contains(gravity, Gravity.CENTER_VERTICAL)) {
+ ngravity |= Gravity.CENTER_HORIZONTAL;
+ }
+ lp.gravity = ngravity;
+ int ml = lp.leftMargin;
+ int mr = lp.rightMargin;
+ int mt = lp.topMargin;
+ int mb = lp.bottomMargin;
+ lp.leftMargin = mt;
+ lp.rightMargin = mb;
+ lp.topMargin = mr;
+ lp.bottomMargin = ml;
+ int width = lp.width;
+ int height = lp.height;
+ lp.width = height;
+ lp.height = width;
+ view.setLayoutParams(lp);
+ }
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java
index 8fcf4008a..38865e9f1 100644
--- a/src/com/android/gallery3d/data/DataManager.java
+++ b/src/com/android/gallery3d/data/DataManager.java
@@ -16,13 +16,13 @@
package com.android.gallery3d.data;
+import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.app.StitchingChangeListener;
-import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
import com.android.gallery3d.data.MediaSet.ItemConsumer;
@@ -65,6 +65,11 @@ public class DataManager implements StitchingChangeListener {
// to prevent concurrency issue.
public static final Object LOCK = new Object();
+ public static DataManager from(Context context) {
+ GalleryApp app = (GalleryApp) context.getApplicationContext();
+ return app.getDataManager();
+ }
+
private static final String TAG = "DataManager";
// This is the path for the media set seen by the user at top level.
diff --git a/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java b/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java
index dee9d2e8d..735803c71 100644
--- a/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java
+++ b/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java
@@ -1,7 +1,5 @@
package com.android.gallery3d.filtershow;
-import android.content.Context;
-import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
@@ -9,7 +7,6 @@ import com.android.gallery3d.filtershow.cache.ImageLoader;
import com.android.gallery3d.filtershow.editors.Editor;
import com.android.gallery3d.filtershow.imageshow.ImageShow;
-import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Vector;
@@ -79,4 +76,8 @@ public class EditorPlaceHolder {
public void setImageLoader(ImageLoader imageLoader) {
mImageLoader = imageLoader;
}
+
+ public Editor getEditor(int editorId) {
+ return mEditors.get(editorId);
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index 7e8a3f582..33fdef5cc 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -44,28 +44,42 @@ import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
-import android.widget.*;
+import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ShareActionProvider;
import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
+import android.widget.Toast;
import com.android.gallery3d.R;
import com.android.gallery3d.data.LocalAlbum;
import com.android.gallery3d.filtershow.cache.FilteringPipeline;
import com.android.gallery3d.filtershow.cache.ImageLoader;
import com.android.gallery3d.filtershow.editors.BasicEditor;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.editors.EditorDraw;
+import com.android.gallery3d.filtershow.editors.EditorFlip;
+import com.android.gallery3d.filtershow.editors.EditorInfo;
import com.android.gallery3d.filtershow.editors.EditorManager;
import com.android.gallery3d.filtershow.editors.EditorRedEye;
-import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+import com.android.gallery3d.filtershow.editors.EditorRotate;
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
-import com.android.gallery3d.filtershow.filters.*;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+import com.android.gallery3d.filtershow.filters.FilterColorBorderRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterFxRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterImageBorderRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.filters.ImageFilterBorder;
+import com.android.gallery3d.filtershow.filters.ImageFilterRS;
+import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
import com.android.gallery3d.filtershow.imageshow.ImageCrop;
-import com.android.gallery3d.filtershow.imageshow.ImageDraw;
-import com.android.gallery3d.filtershow.imageshow.ImageFlip;
-import com.android.gallery3d.filtershow.imageshow.ImageRedEye;
-import com.android.gallery3d.filtershow.imageshow.ImageRotate;
import com.android.gallery3d.filtershow.imageshow.ImageShow;
-import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
import com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet;
import com.android.gallery3d.filtershow.imageshow.ImageZoom;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
@@ -98,29 +112,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
private final PanelController mPanelController = new PanelController();
private ImageLoader mImageLoader = null;
private ImageShow mImageShow = null;
- private ImageDraw mImageDraw = null;
- private ImageStraighten mImageStraighten = null;
- private ImageCrop mImageCrop = null;
- private ImageRotate mImageRotate = null;
- private ImageFlip mImageFlip = null;
private ImageTinyPlanet mImageTinyPlanet = null;
- private View mListFx = null;
- private View mListBorders = null;
- private View mListGeometry = null;
- private View mListColors = null;
- private View mListFilterButtons = null;
private View mSaveButton = null;
- private ImageButton mFxButton = null;
- private ImageButton mBorderButton = null;
- private ImageButton mGeometryButton = null;
- private ImageButton mColorsButton = null;
-
- private LinearLayout listColors = null;
- private LinearLayout listFilters = null;
- private LinearLayout listBorders = null;
-
private EditorPlaceHolder mEditorPlaceHolder = new EditorPlaceHolder(this);
private static final int SELECT_PICTURE = 1;
@@ -128,12 +123,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
protected static final boolean ANIMATE_PANELS = true;
private static int mImageBorderSize = 4; // in percent
+ private boolean mShowingTinyPlanet = false;
private boolean mShowingHistoryPanel = false;
private boolean mShowingImageStatePanel = false;
private final Vector<ImageShow> mImageViews = new Vector<ImageShow>();
- private final Vector<View> mListViews = new Vector<View>();
- private final Vector<ImageButton> mBottomPanelButtons = new Vector<ImageButton>();
private ShareActionProvider mShareActionProvider;
private File mSharedOutputFile = null;
@@ -141,7 +135,6 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
private boolean mSharingImage = false;
private WeakReference<ProgressDialog> mSavingProgressDialog;
- private static final int SEEK_BAR_MAX = 600;
private LoadBitmapTask mLoadBitmapTask;
private FilterIconButton mNullFxFilter;
@@ -152,31 +145,25 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setResources();
- Resources res = getResources();
setupMasterImage();
- ImageFilterRS.setRenderScriptContext(this);
+ setDefaultValues();
+ fillEditors();
- ImageShow.setDefaultBackgroundColor(res.getColor(R.color.background_screen));
- // TODO: get those values from XML.
- ImageZoom.setZoomedSize(getPixelsFromDip(256));
- FramedTextButton.setTextSize((int) getPixelsFromDip(14));
- FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
- FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
- ImageShow.setTextSize((int) getPixelsFromDip(12));
- ImageShow.setTextPadding((int) getPixelsFromDip(10));
- ImageShow.setOriginalTextMargin((int) getPixelsFromDip(4));
- ImageShow.setOriginalTextSize((int) getPixelsFromDip(18));
- ImageShow.setOriginalText(res.getString(R.string.original_picture_text));
- mIconSeedSize = res.getDimensionPixelSize(R.dimen.thumbnail_size);
+ loadXML();
+ if (getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE) {
+ mShowingImageStatePanel = true;
+ }
- Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
- int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
- Spline.setCurveHandle(curveHandle, curveHandleSize);
- Spline.setCurveWidth((int) getPixelsFromDip(3));
+ setDefaultPreset();
+
+ processIntent();
+ }
+ private void loadXML() {
setContentView(R.layout.filtershow_activity);
+
ActionBar actionBar = getActionBar();
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
actionBar.setCustomView(R.layout.filtershow_actionbar);
@@ -189,123 +176,95 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
}
});
- mImageLoader = new ImageLoader(this, getApplicationContext());
-
- listFilters = (LinearLayout) findViewById(R.id.listFilters);
- listBorders = (LinearLayout) findViewById(R.id.listBorders);
- listColors = (LinearLayout) findViewById(R.id.listColorsFx);
-
mImageShow = (ImageShow) findViewById(R.id.imageShow);
- mImageStraighten = (ImageStraighten) findViewById(R.id.imageStraighten);
- mImageCrop = (ImageCrop) findViewById(R.id.imageCrop);
- mImageRotate = (ImageRotate) findViewById(R.id.imageRotate);
- mImageFlip = (ImageFlip) findViewById(R.id.imageFlip);
mImageTinyPlanet = (ImageTinyPlanet) findViewById(R.id.imageTinyPlanet);
- mImageDraw = (ImageDraw) findViewById(R.id.imageDraw);
-
- mImageCrop.setAspectTextSize((int) getPixelsFromDip(18));
- ImageCrop.setTouchTolerance((int) getPixelsFromDip(25));
- ImageCrop.setMinCropSize((int) getPixelsFromDip(55));
mImageViews.add(mImageShow);
- mImageViews.add(mImageStraighten);
- mImageViews.add(mImageCrop);
- mImageViews.add(mImageRotate);
- mImageViews.add(mImageFlip);
mImageViews.add(mImageTinyPlanet);
- mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
- mEditorPlaceHolder.addEditor(new EditorDraw());
- mEditorPlaceHolder.addEditor(new BasicEditor());
- mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
- mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
- mEditorPlaceHolder.addEditor(new EditorRedEye());
- EditorManager.addEditors(mEditorPlaceHolder);
- mEditorPlaceHolder.setOldViews(mImageViews);
- mEditorPlaceHolder.setImageLoader(mImageLoader);
+ setupEditors();
mEditorPlaceHolder.hide();
- mListFx = findViewById(R.id.fxList);
- mListBorders = findViewById(R.id.bordersList);
- mListGeometry = findViewById(R.id.geometryList);
- mListFilterButtons = findViewById(R.id.filterButtonsList);
- mListColors = findViewById(R.id.colorsFxList);
- mListViews.add(mListFx);
- mListViews.add(mListBorders);
- mListViews.add(mListGeometry);
- mListViews.add(mListFilterButtons);
- mListViews.add(mListColors);
-
- mFxButton = (ImageButton) findViewById(R.id.fxButton);
- mBorderButton = (ImageButton) findViewById(R.id.borderButton);
- mGeometryButton = (ImageButton) findViewById(R.id.geometryButton);
- mColorsButton = (ImageButton) findViewById(R.id.colorsButton);
-
- mBottomPanelButtons.add(mFxButton);
- mBottomPanelButtons.add(mBorderButton);
- mBottomPanelButtons.add(mGeometryButton);
- mBottomPanelButtons.add(mColorsButton);
-
mImageShow.setImageLoader(mImageLoader);
- mImageStraighten.setImageLoader(mImageLoader);
- mImageCrop.setImageLoader(mImageLoader);
- mImageRotate.setImageLoader(mImageLoader);
- mImageFlip.setImageLoader(mImageLoader);
mImageTinyPlanet.setImageLoader(mImageLoader);
- mImageDraw.setImageLoader(mImageLoader);
+ mPanelController.clear();
mPanelController.setActivity(this);
mPanelController.setEditorPlaceHolder(mEditorPlaceHolder);
mPanelController.addImageView(findViewById(R.id.imageShow));
- mPanelController.addImageView(findViewById(R.id.imageStraighten));
- mPanelController.addImageView(findViewById(R.id.imageCrop));
- mPanelController.addImageView(findViewById(R.id.imageRotate));
- mPanelController.addImageView(findViewById(R.id.imageFlip));
mPanelController.addImageView(findViewById(R.id.imageTinyPlanet));
- mPanelController.addImageView(findViewById(R.id.imageDraw));
-
- mPanelController.addPanel(mFxButton, mListFx, 0);
- mPanelController.addPanel(mBorderButton, mListBorders, 1);
- mPanelController.addPanel(mGeometryButton, mListGeometry, 2);
- mPanelController.addComponent(mGeometryButton, findViewById(R.id.straightenButton));
- mPanelController.addComponent(mGeometryButton, findViewById(R.id.cropButton));
- mPanelController.addComponent(mGeometryButton, findViewById(R.id.rotateButton));
- mPanelController.addComponent(mGeometryButton, findViewById(R.id.flipButton));
-
- mPanelController.addPanel(mColorsButton, mListColors, 3);
-
- Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
-
- FiltersManager filtersManager = FiltersManager.getManager();
- filtersManager.addEffects(filtersRepresentations);
+ mPanelController.addPanel(R.id.fxButton, R.id.fxList, 0);
+ mPanelController.addPanel(R.id.borderButton, R.id.bordersList, 1);
+ mPanelController.addPanel(R.id.geometryButton, R.id.geometryList, 2);
+ mPanelController.addPanel(R.id.colorsButton, R.id.colorsFxList, 3);
- for (FilterRepresentation representation : filtersRepresentations) {
- setupFilterRepresentationButton(representation, listColors, mColorsButton);
- }
+ fillFx((LinearLayout) findViewById(R.id.listFilters), R.id.fxButton);
+ LoadBordersTask loadBorders = new LoadBordersTask((LinearLayout) findViewById(R.id.listBorders));
+ loadBorders.execute();
+ fillGeometry();
+ fillFilters();
mPanelController.addView(findViewById(R.id.applyEffect));
+
findViewById(R.id.resetOperationsButton).setOnClickListener(
createOnClickResetOperationsButton());
ListView operationsList = (ListView) findViewById(R.id.operationsList);
operationsList.setAdapter(mMasterImage.getHistory());
operationsList.setOnItemClickListener(this);
+
ListView imageStateList = (ListView) findViewById(R.id.imageStateList);
imageStateList.setAdapter(mMasterImage.getState());
mImageLoader.setAdapter(mMasterImage.getHistory());
- fillListImages(listFilters);
- LoadBordersTask loadBorders = new LoadBordersTask(listBorders);
- loadBorders.execute();
-
mPanelController.setRowPanel(findViewById(R.id.secondRowPanel));
mPanelController.setUtilityPanel(this, findViewById(R.id.filterButtonsList),
findViewById(R.id.panelAccessoryViewList),
findViewById(R.id.applyEffect));
- mPanelController.setCurrentPanel(mFxButton);
+ mPanelController.setCurrentPanel(R.id.fxButton);
+ }
+
+ private void fillPanel(Vector<FilterRepresentation> representations, int layoutId, int buttonId) {
+ ImageButton button = (ImageButton) findViewById(buttonId);
+ LinearLayout layout = (LinearLayout) findViewById(layoutId);
+
+ for (FilterRepresentation representation : representations) {
+ setupFilterRepresentationButton(representation, layout, button);
+ }
+ }
+
+ private void fillFilters() {
+ Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
+ FiltersManager filtersManager = FiltersManager.getManager();
+ filtersManager.addEffects(filtersRepresentations);
+ fillPanel(filtersRepresentations, R.id.listColorsFx, R.id.colorsButton);
+ }
+
+ private void fillGeometry() {
+ Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
+ FiltersManager filtersManager = FiltersManager.getManager();
+
+ GeometryMetadata geo = new GeometryMetadata();
+ int[] editorsId = geo.getEditorIds();
+ for (int i = 0; i < editorsId.length; i++) {
+ int editorId = editorsId[i];
+ GeometryMetadata geometry = new GeometryMetadata(geo);
+ geometry.setEditorId(editorId);
+ EditorInfo editorInfo = (EditorInfo) mEditorPlaceHolder.getEditor(editorId);
+ geometry.setTextId(editorInfo.getTextId());
+ geometry.setOverlayId(editorInfo.getOverlayId());
+ geometry.setOverlayOnly(editorInfo.getOverlayOnly());
+ filtersRepresentations.add(geometry);
+ }
+
+ filtersManager.addTools(filtersRepresentations);
+ fillPanel(filtersRepresentations, R.id.listGeometry, R.id.geometryButton);
+ }
+
+ private void processIntent() {
Intent intent = getIntent();
if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -342,16 +301,71 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
}
mImageShow.getImagePreset().mGeoData.setCropExtras(mCropExtras);
- mImageCrop.setExtras(mCropExtras);
+ // FIXME: moving to editors breaks the crop action
+ EditorCrop crop = (EditorCrop) mEditorPlaceHolder.getEditor(EditorCrop.ID);
+
+ crop.setExtras(mCropExtras);
String s = getString(R.string.Fixed);
- mImageCrop.setAspectString(s);
- mImageCrop.setCropActionFlag(true);
+ crop.setAspectString(s);
+ crop.setCropActionFlag(true);
mPanelController.setFixedAspect(mCropExtras.getAspectX() > 0
&& mCropExtras.getAspectY() > 0);
}
}
}
+ private void setupEditors() {
+ mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
+ EditorManager.addEditors(mEditorPlaceHolder);
+ mEditorPlaceHolder.setOldViews(mImageViews);
+ mEditorPlaceHolder.setImageLoader(mImageLoader);
+ }
+
+ private void fillEditors() {
+ mEditorPlaceHolder.addEditor(new EditorDraw());
+ mEditorPlaceHolder.addEditor(new BasicEditor());
+ mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
+ mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
+ mEditorPlaceHolder.addEditor(new EditorRedEye());
+ mEditorPlaceHolder.addEditor(new EditorCrop());
+ mEditorPlaceHolder.addEditor(new EditorFlip());
+ mEditorPlaceHolder.addEditor(new EditorRotate());
+ mEditorPlaceHolder.addEditor(new EditorStraighten());
+ }
+
+ private void setDefaultValues() {
+ ImageFilter.setActivityForMemoryToasts(this);
+ ImageFilterRS.setRenderScriptContext(this);
+
+ Resources res = getResources();
+ ImageFilterBorder filterBorder = (ImageFilterBorder) FiltersManager.getManager().getFilter(ImageFilterBorder.class);
+ filterBorder.setResources(res);
+
+ ImageShow.setDefaultBackgroundColor(res.getColor(R.color.background_screen));
+ // TODO: get those values from XML.
+ ImageZoom.setZoomedSize(getPixelsFromDip(256));
+ FramedTextButton.setTextSize((int) getPixelsFromDip(14));
+ FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
+ FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
+ ImageShow.setTextSize((int) getPixelsFromDip(12));
+ ImageShow.setTextPadding((int) getPixelsFromDip(10));
+ ImageShow.setOriginalTextMargin((int) getPixelsFromDip(4));
+ ImageShow.setOriginalTextSize((int) getPixelsFromDip(18));
+ ImageShow.setOriginalText(res.getString(R.string.original_picture_text));
+ mIconSeedSize = res.getDimensionPixelSize(R.dimen.thumbnail_size);
+ // TODO: pick correct value
+ // MasterImage.setIconSeedSize(mIconSeedSize);
+
+ Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
+ int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
+ Spline.setCurveHandle(curveHandle, curveHandleSize);
+ Spline.setCurveWidth((int) getPixelsFromDip(3));
+
+ ImageCrop.setAspectTextSize((int) getPixelsFromDip(18));
+ ImageCrop.setTouchTolerance((int) getPixelsFromDip(25));
+ ImageCrop.setMinCropSize((int) getPixelsFromDip(55));
+ }
+
private void startLoadBitmap(Uri uri) {
final View filters = findViewById(R.id.filtersPanel);
final View loading = findViewById(R.id.loading);
@@ -360,8 +374,9 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
filters.setVisibility(View.INVISIBLE);
loading.setVisibility(View.VISIBLE);
- View tinyPlanetView = findViewById(R.id.tinyplanetButton);
+ View tinyPlanetView = findViewById(EditorTinyPlanet.ID);
if (tinyPlanetView != null) {
+ mShowingTinyPlanet = false;
tinyPlanetView.setVisibility(View.GONE);
}
mLoadBitmapTask = new LoadBitmapTask(tinyPlanetView);
@@ -406,12 +421,14 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
if (i == 0) {
filter.setName(getString(R.string.none));
}
- FilterIconButton b = setupFilterRepresentationButton(filter, mList, mBorderButton);
+ ImageButton borderButton = (ImageButton) findViewById(R.id.borderButton);
+ FilterIconButton b = setupFilterRepresentationButton(filter, mList, borderButton);
if (i == 0) {
mNullBorderFilter = b;
mNullBorderFilter.setSelected(true);
}
}
+ fillButtonIcons();
}
}
@@ -440,6 +457,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
return;
}
if (values[0]) {
+ mShowingTinyPlanet = true;
mTinyPlanetButton.setVisibility(View.VISIBLE);
}
}
@@ -473,46 +491,53 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
float previewScale = (float) largeBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
pipeline.setPreviewScaleFactor(previewScale);
- Bitmap bmap = mImageLoader.getOriginalBitmapSmall();
- if (bmap != null && bmap.getWidth() > 0 && bmap.getHeight() > 0) {
- float w = bmap.getWidth();
- float h = bmap.getHeight();
- float f = mIconSeedSize / Math.min(w, h);
- w = w * f;
- h = h * f;
- bmap = Bitmap.createScaledBitmap(bmap, (int) w, (int) h, true);
+ fillButtonIcons();
+ MasterImage.getImage().setOriginalGeometry(largeBitmap);
+ mLoadBitmapTask = null;
- int num_colors_buttons = listColors.getChildCount();
- for (int i = 0; i < num_colors_buttons; i++) {
- FilterIconButton b = (FilterIconButton) listColors.getChildAt(i);
+ if (mAction == CROP_ACTION) {
+ mPanelController.showComponent(findViewById(EditorCrop.ID));
+ } else if (mAction == TINY_PLANET_ACTION) {
+ mPanelController.showComponent(findViewById(EditorTinyPlanet.ID));
+ }
- b.setIcon(bmap);
- }
- int num_filters_buttons = listFilters.getChildCount();
- for (int i = 0; i < num_filters_buttons; i++) {
- FilterIconButton b = (FilterIconButton) listFilters.getChildAt(i);
+ super.onPostExecute(result);
+ }
- b.setIcon(bmap);
- }
- int num_borders_buttons = listBorders.getChildCount();
- for (int i = 0; i < num_borders_buttons; i++) {
- FilterIconButton b = (FilterIconButton) listBorders.getChildAt(i);
+ }
- b.setIcon(bmap);
- }
+ private void fillButtonIcons() {
+ Bitmap bmap = mImageLoader.getOriginalBitmapSmall();
+ if (bmap != null && bmap.getWidth() > 0 && bmap.getHeight() > 0) {
+ float w = bmap.getWidth();
+ float h = bmap.getHeight();
+ float f = mIconSeedSize / Math.min(w, h);
+ w = w * f;
+ h = h * f;
+ bmap = Bitmap.createScaledBitmap(bmap, (int) w, (int) h, true);
+
+ LinearLayout listColors = (LinearLayout) findViewById(R.id.listColorsFx);
+ int num_colors_buttons = listColors.getChildCount();
+ for (int i = 0; i < num_colors_buttons; i++) {
+ FilterIconButton b = (FilterIconButton) listColors.getChildAt(i);
+ b.setIcon(bmap);
+ }
+ LinearLayout listFilters = (LinearLayout) findViewById(R.id.listFilters);
+ int num_filters_buttons = listFilters.getChildCount();
+ for (int i = 0; i < num_filters_buttons; i++) {
+ FilterIconButton b = (FilterIconButton) listFilters.getChildAt(i);
+ b.setIcon(bmap);
}
- mLoadBitmapTask = null;
- if (mAction == CROP_ACTION) {
- mPanelController.showComponent(findViewById(R.id.cropButton));
- } else if (mAction == TINY_PLANET_ACTION) {
- mPanelController.showComponent(findViewById(R.id.tinyplanetButton));
+ LinearLayout listBorders = (LinearLayout) findViewById(R.id.listBorders);
+ int num_borders_buttons = listBorders.getChildCount();
+ for (int i = 0; i < num_borders_buttons; i++) {
+ FilterIconButton b = (FilterIconButton) listBorders.getChildAt(i);
+ b.setIcon(bmap);
}
- super.onPostExecute(result);
}
-
}
@Override
@@ -520,6 +545,12 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
if (mLoadBitmapTask != null) {
mLoadBitmapTask.cancel(false);
}
+ // TODO: Using singletons is a bad design choice for many of these
+ // due static reference leaks and in general. Please refactor.
+ MasterImage.reset();
+ FilteringPipeline.reset();
+ ImageFilter.resetStatics();
+ FiltersManager.reset();
super.onDestroy();
}
@@ -669,6 +700,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.undoButton: {
+ mPanelController.resetParameters();
HistoryAdapter adapter = mMasterImage.getHistory();
int position = adapter.undo();
mMasterImage.onHistoryItemClick(position);
@@ -720,16 +752,13 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
String text = representation.getName();
icon.setup(text, mPanelController, panel);
icon.setFilterRepresentation(representation);
- if (representation instanceof FilterTinyPlanetRepresentation) {
- // needed to hide tinyplanet on startup
- icon.setId(R.id.tinyplanetButton);
- }
+ icon.setId(representation.getEditorId());
mPanelController.addComponent(button, icon);
panel.addView(icon);
return icon;
}
- private void fillListImages(LinearLayout listFilters) {
+ private void fillFx(LinearLayout listFilters, int buttonId) {
// TODO: use listview
// TODO: load the filters straight from the filesystem
@@ -760,9 +789,6 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
R.string.ffx_x_process
};
- ImagePreset preset = new ImagePreset(getString(R.string.history_original)); // empty
- preset.setImageLoader(mImageLoader);
-
BitmapFactory.Options o = new BitmapFactory.Options();
o.inScaled = false;
@@ -773,21 +799,28 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
fxArray[p++] = fx;
}
+ ImageButton button = (ImageButton) findViewById(buttonId);
+
FilterFxRepresentation nullFx = new FilterFxRepresentation(getString(R.string.none), 0, R.string.none);
- mNullFxFilter = setupFilterRepresentationButton(nullFx, listFilters, mFxButton);
+ mNullFxFilter = setupFilterRepresentationButton(nullFx, listFilters, button);
mNullFxFilter.setSelected(true);
Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
FiltersManager.getManager().addLooks(filtersRepresentations);
for (FilterRepresentation representation : filtersRepresentations) {
- setupFilterRepresentationButton(representation, listFilters, mFxButton);
+ setupFilterRepresentationButton(representation, listFilters, button);
}
for (int i = 0; i < p; i++) {
- setupFilterRepresentationButton(fxArray[i], listFilters, mFxButton);
+ setupFilterRepresentationButton(fxArray[i], listFilters, button);
}
+ }
+ public void setDefaultPreset() {
// Default preset (original)
+ ImagePreset preset = new ImagePreset(getString(R.string.history_original)); // empty
+ preset.setImageLoader(mImageLoader);
+
mMasterImage.setPreset(preset, true);
}
@@ -795,12 +828,6 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
// Some utility functions
// TODO: finish the cleanup.
- public void showOriginalViews(boolean value) {
- for (ImageShow views : mImageViews) {
- views.showOriginal(value);
- }
- }
-
public void invalidateViews() {
for (ImageShow views : mImageViews) {
views.invalidate();
@@ -808,12 +835,6 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
}
}
- public void hideListViews() {
- for (View view : mListViews) {
- view.setVisibility(View.GONE);
- }
- }
-
public void hideImageViews() {
for (View view : mImageViews) {
view.setVisibility(View.GONE);
@@ -821,34 +842,6 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
mEditorPlaceHolder.hide();
}
- public void unselectBottomPanelButtons() {
- for (ImageButton button : mBottomPanelButtons) {
- button.setSelected(false);
- }
- }
-
- public void unselectPanelButtons(Vector<ImageButton> buttons) {
- for (ImageButton button : buttons) {
- button.setSelected(false);
- }
- }
-
- public void disableFilterButtons() {
- for (ImageButton b : mBottomPanelButtons) {
- b.setEnabled(false);
- b.setClickable(false);
- b.setAlpha(0.4f);
- }
- }
-
- public void enableFilterButtons() {
- for (ImageButton b : mBottomPanelButtons) {
- b.setEnabled(true);
- b.setClickable(true);
- b.setAlpha(1.0f);
- }
- }
-
// //////////////////////////////////////////////////////////////////////////////
// imageState panel...
@@ -857,44 +850,19 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
}
private void toggleImageStatePanel() {
- final View view = findViewById(R.id.mainPanel);
final View viewList = findViewById(R.id.imageStatePanel);
if (mShowingHistoryPanel) {
- findViewById(R.id.historyPanel).setVisibility(View.INVISIBLE);
+ findViewById(R.id.historyPanel).setVisibility(View.GONE);
mShowingHistoryPanel = false;
}
- int translate = translateMainPanel(viewList);
if (!mShowingImageStatePanel) {
mShowingImageStatePanel = true;
- if (PanelController.useAnimations()) {
- view.animate().setDuration(200).x(translate)
- .withLayer().withEndAction(new Runnable() {
- @Override
- public void run() {
- viewList.setAlpha(0);
- viewList.setVisibility(View.VISIBLE);
- viewList.animate().setDuration(100)
- .alpha(1.0f).start();
- }
- }).start();
- } else {
- view.setX(translate);
- viewList.setAlpha(0);
- viewList.setVisibility(View.VISIBLE);
- viewList.animate().setDuration(100)
- .alpha(1.0f).start();
- }
+ viewList.setVisibility(View.VISIBLE);
} else {
mShowingImageStatePanel = false;
- viewList.setVisibility(View.INVISIBLE);
- if (PanelController.useAnimations()) {
- view.animate().setDuration(200).x(0).withLayer()
- .start();
- } else {
- view.setX(0);
- }
+ viewList.setVisibility(View.GONE);
}
invalidateOptionsMenu();
}
@@ -903,13 +871,28 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
- setResources();
+ setDefaultValues();
+ loadXML();
+ if (getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE) {
+ mShowingImageStatePanel = true;
+ }
if (mShowingHistoryPanel) {
toggleHistoryPanel();
}
+ if (mShowingTinyPlanet == false) {
+ View tinyPlanetView = findViewById(EditorTinyPlanet.ID);
+ if (tinyPlanetView != null) {
+ tinyPlanetView.setVisibility(View.GONE);
+ }
+ }
+ final View loading = findViewById(R.id.loading);
+ loading.setVisibility(View.GONE);
}
public void setupMasterImage() {
+ mImageLoader = new ImageLoader(this, getApplicationContext());
+
HistoryAdapter mHistoryAdapter = new HistoryAdapter(
this, R.layout.filtershow_history_operation_row,
R.id.rowTextView);
@@ -931,16 +914,19 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
final View viewList = findViewById(R.id.historyPanel);
if (mShowingImageStatePanel) {
- findViewById(R.id.imageStatePanel).setVisibility(View.INVISIBLE);
- mShowingImageStatePanel = false;
+ findViewById(R.id.imageStatePanel).setVisibility(View.GONE);
}
int translate = translateMainPanel(viewList);
if (!mShowingHistoryPanel) {
mShowingHistoryPanel = true;
- if (PanelController.useAnimations()) {
- view.animate().setDuration(200).x(translate)
- .withLayer().withEndAction(new Runnable() {
+ if (getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_PORTRAIT) {
+ // If portrait, always remove the state panel
+ mShowingImageStatePanel = false;
+ if (PanelController.useAnimations()) {
+ view.animate().setDuration(200).x(translate)
+ .withLayer().withEndAction(new Runnable() {
@Override
public void run() {
viewList.setAlpha(0);
@@ -949,21 +935,35 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
.alpha(1.0f).start();
}
}).start();
+ } else {
+ view.setX(translate);
+ viewList.setAlpha(0);
+ viewList.setVisibility(View.VISIBLE);
+ viewList.animate().setDuration(100)
+ .alpha(1.0f).start();
+ }
} else {
- view.setX(translate);
- viewList.setAlpha(0);
+ findViewById(R.id.filtersPanel).setVisibility(View.GONE);
viewList.setVisibility(View.VISIBLE);
- viewList.animate().setDuration(100)
- .alpha(1.0f).start();
}
} else {
mShowingHistoryPanel = false;
- viewList.setVisibility(View.INVISIBLE);
- if (PanelController.useAnimations()) {
- view.animate().setDuration(200).x(0).withLayer()
- .start();
+ if (getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_PORTRAIT) {
+ viewList.setVisibility(View.INVISIBLE);
+ if (PanelController.useAnimations()) {
+ view.animate().setDuration(200).x(0).withLayer()
+ .start();
+ } else {
+ view.setX(0);
+ }
} else {
- view.setX(0);
+ viewList.setVisibility(View.GONE);
+ findViewById(R.id.filtersPanel).setVisibility(View.VISIBLE);
+ // In landscape, bring back the state panel if it was there
+ if (mShowingImageStatePanel) {
+ findViewById(R.id.imageStatePanel).setVisibility(View.VISIBLE);
+ }
}
}
invalidateOptionsMenu();
@@ -1001,6 +1001,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
}
}
+ public PanelController getPanelController() {
+ return mPanelController;
+ }
+
public void cannotLoadImage() {
CharSequence text = getString(R.string.cannot_load_image);
Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
@@ -1142,11 +1146,6 @@ public class FilterShowActivity extends Activity implements OnItemClickListener,
finish();
}
- private void setResources() {
- ImageFilterBorder filterBorder = (ImageFilterBorder) FiltersManager.getManager().getFilter(ImageFilterBorder.class);
- filterBorder.setResources(getResources());
- }
-
static {
System.loadLibrary("jni_filtershow_filters");
}
diff --git a/src/com/android/gallery3d/filtershow/HistoryAdapter.java b/src/com/android/gallery3d/filtershow/HistoryAdapter.java
index 057ab382e..a10e66ba4 100644
--- a/src/com/android/gallery3d/filtershow/HistoryAdapter.java
+++ b/src/com/android/gallery3d/filtershow/HistoryAdapter.java
@@ -17,7 +17,8 @@
package com.android.gallery3d.filtershow;
import android.content.Context;
-import android.util.Log;
+import android.graphics.Bitmap;
+import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
@@ -196,30 +197,15 @@ public class HistoryAdapter extends ArrayAdapter<ImagePreset> {
if (itemView != null) {
itemView.setText(item.historyName());
}
- ImageView markView = (ImageView) view.findViewById(R.id.selectedMark);
- if (position == mCurrentPresetPosition) {
- markView.setVisibility(View.VISIBLE);
- } else {
- markView.setVisibility(View.INVISIBLE);
+ ImageView preview = (ImageView) view.findViewById(R.id.preview);
+ Bitmap bmp = item.getPreviewImage();
+ if (bmp != null) {
+ preview.setImageBitmap(bmp);
}
- ImageView typeView = (ImageView) view.findViewById(R.id.typeMark);
- // TODO: use type of last filter, not a string, to discriminate.
- if (position == getCount() - 1) {
- typeView.setImageResource(R.drawable.ic_photoeditor_effects);
- } else if (item.historyName().equalsIgnoreCase(mBorders)) {
- typeView.setImageResource(R.drawable.ic_photoeditor_border);
- } else if (item.historyName().equalsIgnoreCase(mStraighten)) {
- typeView.setImageResource(R.drawable.ic_photoeditor_fix);
- } else if (item.historyName().equalsIgnoreCase(mCrop)) {
- typeView.setImageResource(R.drawable.ic_photoeditor_fix);
- } else if (item.historyName().equalsIgnoreCase(mRotate)) {
- typeView.setImageResource(R.drawable.ic_photoeditor_fix);
- } else if (item.historyName().equalsIgnoreCase(mMirror)) {
- typeView.setImageResource(R.drawable.ic_photoeditor_fix);
- } else if (item.isFx()) {
- typeView.setImageResource(R.drawable.ic_photoeditor_effects);
+ if (position == mCurrentPresetPosition) {
+ view.setBackgroundColor(Color.WHITE);
} else {
- typeView.setImageResource(R.drawable.ic_photoeditor_color);
+ view.setBackgroundResource(R.drawable.filtershow_button_background);
}
}
diff --git a/src/com/android/gallery3d/filtershow/ImageStateAdapter.java b/src/com/android/gallery3d/filtershow/ImageStateAdapter.java
index 58e0035bc..62633e26e 100644
--- a/src/com/android/gallery3d/filtershow/ImageStateAdapter.java
+++ b/src/com/android/gallery3d/filtershow/ImageStateAdapter.java
@@ -21,11 +21,13 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
+import android.widget.ImageView;
import android.widget.TextView;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.filters.FilterRepresentation;
import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
public class ImageStateAdapter extends ArrayAdapter<FilterRepresentation> {
private static final String LOGTAG = "ImageStateAdapter";
@@ -36,13 +38,20 @@ public class ImageStateAdapter extends ArrayAdapter<FilterRepresentation> {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- View view = convertView;
+ MovableLinearLayout view = (MovableLinearLayout) convertView;
if (view == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
- view = inflater.inflate(R.layout.filtershow_imagestate_row, null);
+ view = (MovableLinearLayout) inflater.inflate(R.layout.filtershow_imagestate_row, null);
}
FilterRepresentation filter = getItem(position);
+ view.setFilterRepresentation(filter);
+ ImageView markView = (ImageView) view.findViewById(R.id.selectedMark);
+ if (filter == MasterImage.getImage().getCurrentFilterRepresentation()) {
+ markView.setVisibility(View.VISIBLE);
+ } else {
+ markView.setVisibility(View.INVISIBLE);
+ }
if (filter != null) {
TextView itemLabel = (TextView) view.findViewById(R.id.imagestate_label);
itemLabel.setText(filter.getName());
diff --git a/src/com/android/gallery3d/filtershow/MovableLinearLayout.java b/src/com/android/gallery3d/filtershow/MovableLinearLayout.java
new file mode 100644
index 000000000..9eddb41f1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/MovableLinearLayout.java
@@ -0,0 +1,91 @@
+/*
+ * 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.gallery3d.filtershow;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class MovableLinearLayout extends LinearLayout {
+
+ private Point mTouchDown = new Point();
+ private FilterRepresentation mFilterRepresentation;
+ private int mTouchSlope = 3;
+ private static final String LOGTAG = "MovableLinearLayout";
+
+ public MovableLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ private void resetView() {
+ setTranslationX(0);
+ mTouchDown.x = 0;
+ mTouchDown.y = 0;
+ setAlpha(1.0f);
+ setBackgroundResource(R.drawable.filtershow_button_background);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int ex = (int) event.getX();
+ int ey = (int) event.getY();
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ mTouchDown.x = ex;
+ mTouchDown.y = ey;
+ FilterShowActivity activity = (FilterShowActivity) getContext();
+ activity.getPanelController().showComponentWithRepresentation(mFilterRepresentation);
+ }
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ int delta = ex - mTouchDown.x;
+ if (delta > 0 && (delta - getTranslationX()) > mTouchSlope) {
+ setTranslationX(delta);
+ float alpha = (getWidth() - getTranslationX()) / getWidth();
+ int backgroundColor = Color.argb((int) (1.0f - alpha * 255), 255, 0, 0);
+ setBackgroundColor(backgroundColor);
+ setAlpha(alpha);
+ }
+ }
+ if (event.getAction() == MotionEvent.ACTION_UP
+ || event.getAction() == MotionEvent.ACTION_CANCEL) {
+ if (getTranslationX() > getWidth() / 4) {
+ delete(mFilterRepresentation);
+ } else {
+ resetView();
+ }
+ }
+ return true;
+ }
+
+ private void delete(FilterRepresentation filterRepresentation) {
+ FilterShowActivity activity = (FilterShowActivity) getContext();
+ activity.getPanelController().removeFilterRepresentation(filterRepresentation);
+ }
+
+ public void setFilterRepresentation(FilterRepresentation filterRepresentation) {
+ mFilterRepresentation = filterRepresentation;
+ resetView();
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java
index 5bda246da..3c4470ad0 100644
--- a/src/com/android/gallery3d/filtershow/PanelController.java
+++ b/src/com/android/gallery3d/filtershow/PanelController.java
@@ -16,7 +16,6 @@
package com.android.gallery3d.filtershow;
-import android.annotation.TargetApi;
import android.content.Context;
import android.text.Html;
import android.view.View;
@@ -25,18 +24,21 @@ import android.view.ViewPropertyAnimator;
import android.widget.LinearLayout;
import android.widget.TextView;
+import android.util.Log;
+
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.editors.Editor;
+import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
import com.android.gallery3d.filtershow.filters.FilterRepresentation;
import com.android.gallery3d.filtershow.filters.ImageFilter;
import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet;
-import com.android.gallery3d.filtershow.imageshow.ImageCrop;
import com.android.gallery3d.filtershow.imageshow.ImageShow;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
import com.android.gallery3d.filtershow.presets.ImagePreset;
import com.android.gallery3d.filtershow.ui.FilterIconButton;
import java.util.HashMap;
+import java.util.Set;
import java.util.Vector;
public class PanelController implements OnClickListener {
@@ -46,7 +48,6 @@ public class PanelController implements OnClickListener {
private static int HORIZONTAL_MOVE = 1;
private static final int ANIM_DURATION = 200;
private static final String LOGTAG = "PanelController";
- private boolean mDisableFilterButtons = false;
private boolean mFixedAspect = false;
public static boolean useAnimations() {
@@ -156,7 +157,6 @@ public class PanelController implements OnClickListener {
private String mEffectName = null;
private int mParameterValue = 0;
private boolean mShowParameterValue = false;
- boolean firstTimeCropDisplayed = true;
public UtilityPanel(Context context, View view, View accessoryViewList,
View textView) {
@@ -267,6 +267,13 @@ public class PanelController implements OnClickListener {
private FilterShowActivity mActivity = null;
private EditorPlaceHolder mEditorPlaceHolder = null;
+ public void clear() {
+ mPanels.clear();
+ mViews.clear();
+ mFilters.clear();
+ mImageViews.clear();
+ }
+
public void setActivity(FilterShowActivity activity) {
mActivity = activity;
}
@@ -276,7 +283,18 @@ public class PanelController implements OnClickListener {
mViews.put(view, new ViewType(view, COMPONENT));
}
- public void addPanel(View view, View container, int position) {
+ public View getViewFromId(int viewId) {
+ for (View view : mPanels.keySet()) {
+ if (view.getId() == viewId) {
+ return view;
+ }
+ }
+ return null;
+ }
+
+ public void addPanel(int viewId, int containerId, int position) {
+ View view = mActivity.findViewById(viewId);
+ View container = mActivity.findViewById(containerId);
mPanels.put(view, new Panel(view, container, position));
view.setOnClickListener(this);
mViews.put(view, new ViewType(view, PANEL));
@@ -312,10 +330,6 @@ public class PanelController implements OnClickListener {
}
}
- if (mDisableFilterButtons) {
- mActivity.enableFilterButtons();
- mDisableFilterButtons = false;
- }
}
public boolean onBackPressed() {
@@ -330,12 +344,6 @@ public class PanelController implements OnClickListener {
if (mCurrentEditor != null) {
mCurrentEditor.reflectCurrentFilter();
}
-
- if (mDisableFilterButtons) {
- mActivity.enableFilterButtons();
- mActivity.resetHistory();
- mDisableFilterButtons = false;
- }
return false;
}
@@ -347,8 +355,8 @@ public class PanelController implements OnClickListener {
mUtilityPanel.setShowParameter(s);
}
- public void setCurrentPanel(View panel) {
- showPanel(panel);
+ public void setCurrentPanel(int panelId) {
+ showPanel(getViewFromId(panelId));
}
public void setRowPanel(View rowPanel) {
@@ -447,6 +455,34 @@ public class PanelController implements OnClickListener {
return MasterImage.getImage().getPreset();
}
+ public void setEffectName(String ename) {
+ mUtilityPanel.setEffectName(ename);
+ }
+
+ public void removeFilterRepresentation(FilterRepresentation filterRepresentation) {
+ if (filterRepresentation == null) {
+ Log.v(LOGTAG, "RemoveFilterRepresentation: " + filterRepresentation);
+ return;
+ }
+ ImagePreset oldPreset = MasterImage.getImage().getPreset();
+ ImagePreset copy = new ImagePreset(oldPreset);
+ copy.removeFilter(filterRepresentation);
+ MasterImage.getImage().setPreset(copy, true);
+ if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
+ FilterRepresentation lastRepresentation = copy.getLastRepresentation();
+ MasterImage.getImage().setCurrentFilterRepresentation(lastRepresentation);
+ }
+ // Now let's reset the panel
+ if (mUtilityPanel == null || !mUtilityPanel.selected()) {
+ return;
+ }
+ showPanel(mCurrentPanel);
+ mCurrentImage.select();
+ if (mCurrentEditor != null) {
+ mCurrentEditor.reflectCurrentFilter();
+ }
+ }
+
public void useFilterRepresentation(FilterRepresentation filterRepresentation) {
if (filterRepresentation == null) {
return;
@@ -470,6 +506,23 @@ public class PanelController implements OnClickListener {
MasterImage.getImage().setCurrentFilterRepresentation(filterRepresentation);
}
+ public void showComponentWithRepresentation(FilterRepresentation filterRepresentation) {
+ if (filterRepresentation == null) {
+ return;
+ }
+ Set<View> views = mViews.keySet();
+ for (View view : views) {
+ if (view instanceof FilterIconButton) {
+ FilterIconButton button = (FilterIconButton) view;
+ if (button.getFilterRepresentation().getFilterClass() == filterRepresentation.getFilterClass()) {
+ MasterImage.getImage().setCurrentFilterRepresentation(filterRepresentation);
+ showComponent(view);
+ return;
+ }
+ }
+ }
+ }
+
public void showComponent(View view) {
boolean doPanelTransition = true;
@@ -532,59 +585,21 @@ public class PanelController implements OnClickListener {
return;
}
- switch (view.getId()) {
- case R.id.tinyplanetButton: {
- mCurrentImage = showImageView(R.id.imageTinyPlanet);
- String ename = mCurrentImage.getContext().getString(R.string.tinyplanet);
- mUtilityPanel.setEffectName(ename);
- if (!mDisableFilterButtons) {
- mActivity.disableFilterButtons();
- mDisableFilterButtons = true;
- }
- break;
- }
- case R.id.straightenButton: {
- mCurrentImage = showImageView(R.id.imageStraighten);
- String ename = mCurrentImage.getContext().getString(R.string.straighten);
- mUtilityPanel.setEffectName(ename);
- break;
- }
- case R.id.cropButton: {
- mCurrentImage = showImageView(R.id.imageCrop);
- String ename = mCurrentImage.getContext().getString(R.string.crop);
- mUtilityPanel.setEffectName(ename);
- mUtilityPanel.setShowParameter(false);
- if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed) {
- ((ImageCrop) mCurrentImage).clear();
- mUtilityPanel.firstTimeCropDisplayed = false;
- ((ImageCrop) mCurrentImage).setFixedAspect(mFixedAspect);
- }
- break;
- }
- case R.id.rotateButton: {
- mCurrentImage = showImageView(R.id.imageRotate);
- String ename = mCurrentImage.getContext().getString(R.string.rotate);
- mUtilityPanel.setEffectName(ename);
- break;
- }
- case R.id.flipButton: {
- mCurrentImage = showImageView(R.id.imageFlip);
- String ename = mCurrentImage.getContext().getString(R.string.mirror);
- mUtilityPanel.setEffectName(ename);
- mUtilityPanel.setShowParameter(false);
- break;
- }
- case R.id.applyEffect: {
+ int id = view.getId();
+ if (id == EditorTinyPlanet.ID) {
+ mCurrentImage = showImageView(R.id.imageTinyPlanet);
+ String ename = mCurrentImage.getContext().getString(R.string.tinyplanet);
+ mUtilityPanel.setEffectName(ename);
+
+ } else {
+ if (id == R.id.applyEffect) {
if (MasterImage.getImage().getCurrentFilter() instanceof ImageFilterTinyPlanet) {
mActivity.saveImage();
} else {
- if (mCurrentImage instanceof ImageCrop) {
- ((ImageCrop) mCurrentImage).saveAndSetPreset();
- }
showPanel(mCurrentPanel);
}
MasterImage.getImage().invalidateFiltersOnly();
- break;
+
}
}
mCurrentImage.select();
diff --git a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
index 7d5b52921..17c19ebe0 100644
--- a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
+++ b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
@@ -22,7 +22,6 @@ import android.os.Process;
import android.support.v8.renderscript.*;
import android.util.Log;
-import com.android.gallery3d.filtershow.filters.BaseFiltersManager;
import com.android.gallery3d.filtershow.filters.FiltersManager;
import com.android.gallery3d.filtershow.filters.ImageFilterRS;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
@@ -31,7 +30,7 @@ import com.android.gallery3d.filtershow.presets.ImagePreset;
public class FilteringPipeline implements Handler.Callback {
- private final static FilteringPipeline gPipeline = new FilteringPipeline();
+ private static FilteringPipeline sPipeline;
private static final String LOGTAG = "FilteringPipeline";
private ImagePreset mPreviousGeometryPreset = null;
private ImagePreset mPreviousFiltersPreset = null;
@@ -117,7 +116,10 @@ public class FilteringPipeline implements Handler.Callback {
}
public static FilteringPipeline getPipeline() {
- return gPipeline;
+ if (sPipeline == null) {
+ sPipeline = new FilteringPipeline();
+ }
+ return sPipeline;
}
public synchronized void setOriginal(Bitmap bitmap) {
@@ -247,11 +249,11 @@ public class FilteringPipeline implements Handler.Callback {
setPresetParameters(preset);
if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
- bitmap = MasterImage.getImage().getImageLoader().getScaleOneImageForPreset(null, preset, request.getBounds(), request.getDestination(), false);
+ bitmap = MasterImage.getImage().getImageLoader().getScaleOneImageForPreset(null, preset,
+ request.getBounds(), request.getDestination(), false);
if (bitmap == null) {
return;
}
- bitmap = preset.applyGeometry(bitmap);
}
if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
@@ -337,4 +339,8 @@ public class FilteringPipeline implements Handler.Callback {
public float getPreviewScaleFactor() {
return mPreviewScaleFactor;
}
+
+ public static void reset() {
+ sPipeline = null;
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
index df4f3fd3a..a8191cfd8 100644
--- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -18,16 +18,15 @@ package com.android.gallery3d.filtershow.cache;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.Bitmap.CompressFormat;
import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore;
@@ -35,16 +34,13 @@ import android.util.Log;
import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMeta;
-
import com.android.gallery3d.R;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInvalidFormatException;
import com.android.gallery3d.exif.ExifParser;
import com.android.gallery3d.exif.ExifTag;
-import com.android.gallery3d.filtershow.CropExtras;
import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.HistoryAdapter;
-import com.android.gallery3d.filtershow.imageshow.ImageCrop;
import com.android.gallery3d.filtershow.imageshow.ImageShow;
import com.android.gallery3d.filtershow.presets.ImagePreset;
import com.android.gallery3d.filtershow.tools.BitmapTask;
@@ -52,7 +48,6 @@ import com.android.gallery3d.filtershow.tools.SaveCopyTask;
import com.android.gallery3d.util.InterruptableOutputStream;
import com.android.gallery3d.util.XmpUtilHelper;
-import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -62,6 +57,8 @@ import java.io.OutputStream;
import java.util.Vector;
import java.util.concurrent.locks.ReentrantLock;
+
+// TODO: this class has waaaay to much bitmap copying. Cleanup.
public class ImageLoader {
private static final String LOGTAG = "ImageLoader";
@@ -313,6 +310,7 @@ public class ImageLoader {
// decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
+ o2.inMutable = true;
Utils.closeSilently(is);
is = mContext.getContentResolver().openInputStream(uri);
@@ -374,9 +372,9 @@ public class ImageLoader {
mLoadingLock.lock();
Bitmap bmp = mZoomCache.getImage(imagePreset, bounds);
if (force || bmp == null) {
- BitmapFactory.Options options = null;
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inMutable = true;
if (destination != null) {
- options = new BitmapFactory.Options();
if (bounds.width() > destination.width()) {
int sampleSize = 1;
int w = bounds.width();
@@ -393,15 +391,14 @@ public class ImageLoader {
return bmp;
}
if (bmp != null) {
- // TODO: this workaround for RS might not be needed ultimately
- Bitmap bmp2 = bmp.copy(Bitmap.Config.ARGB_8888, true);
float scaleFactor = imagePreset.getScaleFactor();
- imagePreset.setScaleFactor(1.0f);
- bmp2 = imagePreset.apply(bmp2);
+ float scale = (float) bmp.getWidth() / (float) getOriginalBounds().width();
+ imagePreset.setScaleFactor(scale);
+ bmp = imagePreset.apply(bmp);
imagePreset.setScaleFactor(scaleFactor);
- mZoomCache.setImage(imagePreset, bounds, bmp2);
+ mZoomCache.setImage(imagePreset, bounds, bmp);
mLoadingLock.unlock();
- return bmp2;
+ return bmp;
}
}
mLoadingLock.unlock();
diff --git a/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java b/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
index 3ec74e266..e81f47fe1 100644
--- a/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
+++ b/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
@@ -27,6 +27,7 @@ public class RenderingRequest {
private boolean mIsDirect = false;
private Bitmap mBitmap = null;
private ImagePreset mImagePreset = null;
+ private ImagePreset mOriginalImagePreset = null;
private RenderingRequestCaller mCaller = null;
private Rect mBounds = null;
private Rect mDestination = null;
@@ -61,6 +62,7 @@ public class RenderingRequest {
request.setBitmap(bitmap);
ImagePreset passedPreset = new ImagePreset(preset);
passedPreset.setImageLoader(MasterImage.getImage().getImageLoader());
+ request.setOriginalImagePreset(preset);
if (type == PARTIAL_RENDERING) {
request.setBounds(bounds);
@@ -137,4 +139,12 @@ public class RenderingRequest {
public void setDestination(Rect destination) {
mDestination = destination;
}
+
+ public ImagePreset getOriginalImagePreset() {
+ return mOriginalImagePreset;
+ }
+
+ public void setOriginalImagePreset(ImagePreset originalImagePreset) {
+ mOriginalImagePreset = originalImagePreset;
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/cache/TripleBufferBitmap.java b/src/com/android/gallery3d/filtershow/cache/TripleBufferBitmap.java
index cc14bf65f..5d169c612 100644
--- a/src/com/android/gallery3d/filtershow/cache/TripleBufferBitmap.java
+++ b/src/com/android/gallery3d/filtershow/cache/TripleBufferBitmap.java
@@ -17,7 +17,6 @@
package com.android.gallery3d.filtershow.cache;
import android.graphics.Bitmap;
-import com.android.gallery3d.app.Log;
public class TripleBufferBitmap {
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java
index 0acedb5dd..dd4df7dc8 100644
--- a/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java
@@ -20,7 +20,6 @@ import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
diff --git a/src/com/android/gallery3d/filtershow/editors/BasicEditor.java b/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
index 48aa5925a..fb09101a6 100644
--- a/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
+++ b/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
@@ -16,16 +16,14 @@
package com.android.gallery3d.filtershow.editors;
-import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.filters.*;
-
import android.content.Context;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
-import com.android.gallery3d.filtershow.imageshow.MasterImage;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterBasicRepresentation;
/**
* The basic editor that all the one parameter filters
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorCrop.java b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
new file mode 100644
index 000000000..947fccb82
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
@@ -0,0 +1,90 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.imageshow.ImageCrop;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorCrop extends Editor implements EditorInfo {
+ public static final int ID = R.id.editorCrop;
+ private static final String LOGTAG = "EditorCrop";
+
+ ImageCrop mImageCrop;
+ private String mAspectString = "";
+ private boolean mCropActionFlag = false;
+ private CropExtras mCropExtras = null;
+
+ public EditorCrop() {
+ super(ID);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ if (mImageCrop == null) {
+ // TODO: need this for now because there's extra state in ImageCrop.
+ // all the state instead should be in the representation.
+ // Same thing for the other geometry editors.
+ mImageCrop = new ImageCrop(context);
+ }
+ mView = mImageShow = mImageCrop;
+ mImageCrop.setImageLoader(MasterImage.getImage().getImageLoader());
+ mImageCrop.setEditor(this);
+ mImageCrop.syncLocalToMasterGeometry();
+ mImageCrop.setCropActionFlag(mCropActionFlag);
+ if (mCropActionFlag) {
+ mImageCrop.setExtras(mCropExtras);
+ mImageCrop.setAspectString(mAspectString);
+ mImageCrop.clear();
+ } else {
+ mImageCrop.setExtras(null);
+ }
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.crop;
+ }
+
+ @Override
+ public int getOverlayId() {
+ return R.drawable.filtershow_button_geometry_crop;
+ }
+
+ @Override
+ public boolean getOverlayOnly() {
+ return true;
+ }
+
+ public void setExtras(CropExtras cropExtras) {
+ mCropExtras = cropExtras;
+ }
+
+ public void setAspectString(String s) {
+ mAspectString = s;
+ }
+
+ public void setCropActionFlag(boolean b) {
+ mCropActionFlag = b;
+ }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorFlip.java b/src/com/android/gallery3d/filtershow/editors/EditorFlip.java
new file mode 100644
index 000000000..94ab2ee29
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorFlip.java
@@ -0,0 +1,60 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.ImageFlip;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorFlip extends Editor implements EditorInfo {
+ public static final int ID = R.id.editorFlip;
+ ImageFlip mImageFlip;
+
+ public EditorFlip() {
+ super(ID);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ if (mImageFlip == null) {
+ mImageFlip = new ImageFlip(context);
+ }
+ mView = mImageShow = mImageFlip;
+ mImageFlip.setImageLoader(MasterImage.getImage().getImageLoader());
+ mImageFlip.setEditor(this);
+ mImageFlip.syncLocalToMasterGeometry();
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.mirror;
+ }
+
+ @Override
+ public int getOverlayId() {
+ return R.drawable.filtershow_button_geometry_flip;
+ }
+
+ @Override
+ public boolean getOverlayOnly() {
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorInfo.java b/src/com/android/gallery3d/filtershow/editors/EditorInfo.java
new file mode 100644
index 000000000..75afe49c2
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorInfo.java
@@ -0,0 +1,23 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+public interface EditorInfo {
+ public int getTextId();
+ public int getOverlayId();
+ public boolean getOverlayOnly();
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java b/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java
index c37102b37..8e9e705d7 100644
--- a/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java
+++ b/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java
@@ -17,7 +17,6 @@
package com.android.gallery3d.filtershow.editors;
import android.content.Context;
-import android.util.Log;
import android.widget.FrameLayout;
import com.android.gallery3d.R;
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorRotate.java b/src/com/android/gallery3d/filtershow/editors/EditorRotate.java
new file mode 100644
index 000000000..0032399a3
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorRotate.java
@@ -0,0 +1,60 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.ImageRotate;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorRotate extends Editor implements EditorInfo {
+ public static final int ID = R.id.editorRotate;
+ ImageRotate mImageRotate;
+
+ public EditorRotate() {
+ super(ID);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ if (mImageRotate == null) {
+ mImageRotate = new ImageRotate(context);
+ }
+ mView = mImageShow = mImageRotate;
+ mImageRotate.setImageLoader(MasterImage.getImage().getImageLoader());
+ mImageRotate.setEditor(this);
+ mImageRotate.syncLocalToMasterGeometry();
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.rotate;
+ }
+
+ @Override
+ public int getOverlayId() {
+ return R.drawable.filtershow_button_geometry_rotate;
+ }
+
+ @Override
+ public boolean getOverlayOnly() {
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorStraighten.java b/src/com/android/gallery3d/filtershow/editors/EditorStraighten.java
new file mode 100644
index 000000000..46419704b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorStraighten.java
@@ -0,0 +1,60 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorStraighten extends Editor implements EditorInfo {
+ public static final int ID = R.id.editorStraighten;
+ ImageStraighten mImageStraighten;
+
+ public EditorStraighten() {
+ super(ID);
+ }
+
+ @Override
+ public void createEditor(Context context, FrameLayout frameLayout) {
+ super.createEditor(context, frameLayout);
+ if (mImageStraighten == null) {
+ mImageStraighten = new ImageStraighten(context);
+ }
+ mView = mImageShow = mImageStraighten;
+ mImageStraighten.setImageLoader(MasterImage.getImage().getImageLoader());
+ mImageStraighten.setEditor(this);
+ mImageStraighten.syncLocalToMasterGeometry();
+ }
+
+ @Override
+ public int getTextId() {
+ return R.string.straighten;
+ }
+
+ @Override
+ public int getOverlayId() {
+ return R.drawable.filtershow_button_geometry_straighten;
+ }
+
+ @Override
+ public boolean getOverlayOnly() {
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java b/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java
index d21950912..c0fcdffd6 100644
--- a/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java
+++ b/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java
@@ -17,7 +17,6 @@
package com.android.gallery3d.filtershow.editors;
import android.content.Context;
-import android.util.Log;
import android.widget.FrameLayout;
import com.android.gallery3d.R;
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorVignette.java b/src/com/android/gallery3d/filtershow/editors/EditorVignette.java
index a60c1681e..a7d99e49b 100644
--- a/src/com/android/gallery3d/filtershow/editors/EditorVignette.java
+++ b/src/com/android/gallery3d/filtershow/editors/EditorVignette.java
@@ -17,12 +17,10 @@
package com.android.gallery3d.filtershow.editors;
import android.content.Context;
-import android.util.Log;
import android.widget.FrameLayout;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.filters.FilterRepresentation;
-import com.android.gallery3d.filtershow.filters.FilterTinyPlanetRepresentation;
import com.android.gallery3d.filtershow.filters.FilterVignetteRepresentation;
import com.android.gallery3d.filtershow.imageshow.ImageVignette;
diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
index 377bd2b6f..1fe2ac666 100644
--- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
+++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
@@ -13,49 +13,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.gallery3d.filtershow.filters;
-import com.android.gallery3d.filtershow.cache.ImageLoader;
-
import java.util.HashMap;
+import java.util.Map;
import java.util.Vector;
-public class BaseFiltersManager {
-
- private static final String LOGTAG = "BaseFiltersManager";
- private static HashMap<Class, ImageFilter> mFilters = new HashMap<Class, ImageFilter>();
+public abstract class BaseFiltersManager {
+ protected HashMap<Class, ImageFilter> mFilters = null;
- protected BaseFiltersManager() {
- Vector<ImageFilter> filters = new Vector<ImageFilter>();
- addFilters(filters);
- for (ImageFilter filter : filters) {
- mFilters.put(filter.getClass(), filter);
- }
- }
+ protected void addFilters(Map<Class, ImageFilter> filters) {
+ filters.put(ImageFilterTinyPlanet.class, new ImageFilterTinyPlanet());
+ filters.put(ImageFilterRedEye.class, new ImageFilterRedEye());
+ filters.put(ImageFilterWBalance.class, new ImageFilterWBalance());
+ filters.put(ImageFilterExposure.class, new ImageFilterExposure());
+ filters.put(ImageFilterVignette.class, new ImageFilterVignette());
+ filters.put(ImageFilterContrast.class, new ImageFilterContrast());
+ filters.put(ImageFilterShadows.class, new ImageFilterShadows());
+ filters.put(ImageFilterHighlights.class, new ImageFilterHighlights());
+ filters.put(ImageFilterVibrance.class, new ImageFilterVibrance());
+ filters.put(ImageFilterSharpen.class, new ImageFilterSharpen());
+ filters.put(ImageFilterCurves.class, new ImageFilterCurves());
+ filters.put(ImageFilterDraw.class, new ImageFilterDraw());
+ filters.put(ImageFilterHue.class, new ImageFilterHue());
+ filters.put(ImageFilterSaturated.class, new ImageFilterSaturated());
+ filters.put(ImageFilterBwFilter.class, new ImageFilterBwFilter());
+ filters.put(ImageFilterNegative.class, new ImageFilterNegative());
+ filters.put(ImageFilterEdge.class, new ImageFilterEdge());
+ filters.put(ImageFilterKMeans.class, new ImageFilterKMeans());
+ filters.put(ImageFilterFx.class, new ImageFilterFx());
+ filters.put(ImageFilterBorder.class, new ImageFilterBorder());
+ filters.put(ImageFilterParametricBorder.class, new ImageFilterParametricBorder());
+ filters.put(ImageFilterGeometry.class, new ImageFilterGeometry());
- protected void addFilters(Vector<ImageFilter> filters) {
- filters.add(new ImageFilterTinyPlanet());
- filters.add(new ImageFilterRedEye());
- filters.add(new ImageFilterWBalance());
- filters.add(new ImageFilterExposure());
- filters.add(new ImageFilterVignette());
- filters.add(new ImageFilterContrast());
- filters.add(new ImageFilterShadows());
- filters.add(new ImageFilterHighlights());
- filters.add(new ImageFilterVibrance());
- filters.add(new ImageFilterSharpen());
- filters.add(new ImageFilterCurves());
- filters.add(new ImageFilterDraw());
- filters.add(new ImageFilterHue());
- filters.add(new ImageFilterSaturated());
- filters.add(new ImageFilterBwFilter());
- filters.add(new ImageFilterNegative());
- filters.add(new ImageFilterEdge());
- filters.add(new ImageFilterKMeans());
- filters.add(new ImageFilterFx());
- filters.add(new ImageFilterBorder());
- filters.add(new ImageFilterParametricBorder());
}
public ImageFilter getFilter(Class c) {
@@ -79,12 +69,11 @@ public class BaseFiltersManager {
}
public void addLooks(Vector<FilterRepresentation> representations) {
- // subclass can add representations
+ // Override
}
public void addEffects(Vector<FilterRepresentation> representations) {
representations.add(getRepresentation(ImageFilterTinyPlanet.class));
- representations.add(getRepresentation(ImageFilterRedEye.class));
representations.add(getRepresentation(ImageFilterWBalance.class));
representations.add(getRepresentation(ImageFilterExposure.class));
representations.add(getRepresentation(ImageFilterVignette.class));
@@ -94,7 +83,6 @@ public class BaseFiltersManager {
representations.add(getRepresentation(ImageFilterVibrance.class));
representations.add(getRepresentation(ImageFilterSharpen.class));
representations.add(getRepresentation(ImageFilterCurves.class));
- representations.add(getRepresentation(ImageFilterDraw.class));
representations.add(getRepresentation(ImageFilterHue.class));
representations.add(getRepresentation(ImageFilterSaturated.class));
representations.add(getRepresentation(ImageFilterBwFilter.class));
@@ -103,6 +91,11 @@ public class BaseFiltersManager {
representations.add(getRepresentation(ImageFilterKMeans.class));
}
+ public void addTools(Vector<FilterRepresentation> representations) {
+ representations.add(getRepresentation(ImageFilterRedEye.class));
+ representations.add(getRepresentation(ImageFilterDraw.class));
+ }
+
public void resetBitmapsRS() {
for (Class c : mFilters.keySet()) {
ImageFilter filter = mFilters.get(c);
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
index 8b8504bbc..dc59b0cfc 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
@@ -54,6 +54,8 @@ public class FilterDrawRepresentation extends FilterRepresentation {
setTextId(R.string.imageDraw);
setButtonId(R.id.drawOnImageButton);
setEditorId(EditorDraw.ID);
+ setOverlayId(R.drawable.filtershow_drawing);
+ setOverlayOnly(true);
}
@Override
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
index 70d016f69..3f823ea1e 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
@@ -17,7 +17,6 @@
package com.android.gallery3d.filtershow.filters;
import android.graphics.RectF;
-import android.util.Log;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.editors.EditorRedEye;
@@ -31,6 +30,7 @@ public class FilterRedEyeRepresentation extends FilterPointRepresentation {
super("RedEye",R.string.redeye,EditorRedEye.ID);
setFilterClass(ImageFilterRedEye.class);
setOverlayId(R.drawable.photoeditor_effect_redeye);
+ setOverlayOnly(true);
}
public void addRect(RectF rect, RectF bounds) {
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
index 83f2a1b87..e0dc905e3 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
@@ -18,7 +18,6 @@ package com.android.gallery3d.filtershow.filters;
import com.android.gallery3d.app.Log;
import com.android.gallery3d.filtershow.editors.BasicEditor;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
public class FilterRepresentation implements Cloneable {
private static final String LOGTAG = "FilterRepresentation";
@@ -30,6 +29,7 @@ public class FilterRepresentation implements Cloneable {
private int mEditorId = BasicEditor.ID;
private int mButtonId = 0;
private int mOverlayId = 0;
+ private boolean mOverlayOnly = false;
private boolean mShowEditingControls = true;
private boolean mShowParameterValue = true;
private boolean mShowUtilityPanel = true;
@@ -58,6 +58,7 @@ public class FilterRepresentation implements Cloneable {
representation.setEditorId(getEditorId());
representation.setButtonId(getButtonId());
representation.setOverlayId(getOverlayId());
+ representation.setOverlayOnly(getOverlayOnly());
representation.setShowEditingControls(showEditingControls());
representation.setShowParameterValue(showParameterValue());
representation.setShowUtilityPanel(showUtilityPanel());
@@ -77,6 +78,7 @@ public class FilterRepresentation implements Cloneable {
&& representation.mEditorId == mEditorId
&& representation.mButtonId == mButtonId
&& representation.mOverlayId == mOverlayId
+ && representation.mOverlayOnly == mOverlayOnly
&& representation.mShowEditingControls == mShowEditingControls
&& representation.mShowParameterValue == mShowParameterValue
&& representation.mShowUtilityPanel == mShowUtilityPanel) {
@@ -181,10 +183,23 @@ public class FilterRepresentation implements Cloneable {
mOverlayId = overlayId;
}
- public int getEditorId() {
+ public boolean getOverlayOnly() {
+ return mOverlayOnly;
+ }
+
+ public void setOverlayOnly(boolean value) {
+ mOverlayOnly = value;
+ }
+
+ final public int getEditorId() {
return mEditorId;
}
+ public int[] getEditorIds() {
+ return new int[] {
+ mEditorId };
+ }
+
public void setEditorId(int editorId) {
mEditorId = editorId;
}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
index 614c6a01d..b4eac0f84 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
@@ -18,9 +18,9 @@ package com.android.gallery3d.filtershow.filters;
import android.graphics.Bitmap;
import android.graphics.Matrix;
+import android.widget.Toast;
-import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.editors.BasicEditor;
+import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
import com.android.gallery3d.filtershow.presets.ImagePreset;
@@ -31,6 +31,30 @@ public abstract class ImageFilter implements Cloneable {
protected String mName = "Original";
private final String LOGTAG = "ImageFilter";
+ // TODO: Temporary, for dogfood note memory issues with toasts for better
+ // feedback. Remove this when filters actually work in low memory
+ // situations.
+ private static FilterShowActivity sActivity = null;
+
+ public static void setActivityForMemoryToasts(FilterShowActivity activity) {
+ sActivity = activity;
+ }
+
+ public static void resetStatics() {
+ sActivity = null;
+ }
+
+ public void displayLowMemoryToast() {
+ if (sActivity != null) {
+ sActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ Toast.makeText(sActivity, "Memory too low for filter " + getName() +
+ ", please file a bug report", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+ }
+
public void setName(String name) {
mName = name;
}
@@ -45,8 +69,8 @@ public abstract class ImageFilter implements Cloneable {
}
/**
- * Called on small bitmaps to create button icons for each filter.
- * Override this to provide filter-specific button icons.
+ * Called on small bitmaps to create button icons for each filter. Override
+ * this to provide filter-specific button icons.
*/
public Bitmap iconApply(Bitmap bitmap, float scaleFactor, int quality) {
return apply(bitmap, scaleFactor, quality);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
index 70e7f2220..e156afcb9 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
@@ -24,9 +24,6 @@ import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
-
import java.util.HashMap;
public class ImageFilterBorder extends ImageFilter {
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
index aa4cf22e6..daa1cd347 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
@@ -17,9 +17,7 @@
package com.android.gallery3d.filtershow.filters;
import android.graphics.Bitmap;
-import android.util.Log;
-import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.ui.Spline;
public class ImageFilterCurves extends ImageFilter {
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
index 6d7614ec9..1fd9071f7 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
@@ -27,8 +27,6 @@ import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
-import android.util.Log;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation.StrokeData;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
index 820ec3e51..c2a7b7bfd 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
@@ -18,9 +18,6 @@ package com.android.gallery3d.filtershow.filters;
import android.graphics.Bitmap;
-import com.android.gallery3d.filtershow.editors.BasicEditor;
-import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
-
public class ImageFilterFx extends ImageFilter {
private static final String TAG = "ImageFilterFx";
private FilterFxRepresentation mParameters = null;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
index 329ca81b9..cbb443f75 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
@@ -50,10 +50,6 @@ public class ImageFilterGeometry extends ImageFilter {
return filter;
}
- public void setGeometryMetadata(GeometryMetadata m) {
- mGeometry = m;
- }
-
native protected void nativeApplyFilterFlip(Bitmap src, int srcWidth, int srcHeight,
Bitmap dst, int dstWidth, int dstHeight, int flip);
@@ -68,7 +64,7 @@ public class ImageFilterGeometry extends ImageFilter {
@Override
public void useRepresentation(FilterRepresentation representation) {
-
+ mGeometry = (GeometryMetadata) representation;
}
@Override
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
index 12f032dcd..0022a9eae 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
@@ -16,10 +16,9 @@
package com.android.gallery3d.filtershow.filters;
-import com.android.gallery3d.R;
-
import android.graphics.Bitmap;
-import android.util.Log;
+
+import com.android.gallery3d.R;
public class ImageFilterHighlights extends SimpleImageFilter {
private static final String LOGTAG = "ImageFilterVignette";
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
index 6f785ef54..29e6d162f 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
@@ -20,7 +20,6 @@ import android.graphics.Bitmap;
import android.text.format.Time;
import com.android.gallery3d.R;
-import com.android.gallery3d.ingest.ui.MtpThumbnailTileView;
public class ImageFilterKMeans extends SimpleImageFilter {
private int mSeed = 0;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
index 316a286e8..3a7878eb1 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
@@ -18,13 +18,10 @@ package com.android.gallery3d.filtershow.filters;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
-import com.android.gallery3d.R;
-
public class ImageFilterParametricBorder extends ImageFilter {
private FilterColorBorderRepresentation mParameters = null;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
index 74712be47..4373c950a 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -20,6 +20,7 @@ import android.app.Activity;
import android.graphics.Bitmap;
import android.support.v8.renderscript.*;
import android.util.Log;
+import com.android.gallery3d.R;
public abstract class ImageFilterRS extends ImageFilter {
private final String LOGTAG = "ImageFilterRS";
@@ -89,6 +90,11 @@ public abstract class ImageFilterRS extends ImageFilter {
Log.e(LOGTAG, "Illegal argument? " + e);
} catch (android.renderscript.RSRuntimeException e) {
Log.e(LOGTAG, "RS runtime exception ? " + e);
+ } catch (java.lang.OutOfMemoryError e) {
+ // Many of the renderscript filters allocated large (>16Mb resources) in order to apply.
+ System.gc();
+ displayLowMemoryToast();
+ Log.e(LOGTAG, "not enough memory for filter " + getName(), e);
}
return bitmap;
}
@@ -113,4 +119,23 @@ public abstract class ImageFilterRS extends ImageFilter {
sOldBitmap = null;
}
+ public Allocation convertRGBAtoA(Bitmap bitmap) {
+ Type.Builder tb_a8 = new Type.Builder(mRS, Element.U8(mRS));
+ ScriptC_grey greyConvert = new ScriptC_grey(mRS, mResources, R.raw.grey);
+
+ Allocation bitmapTemp = Allocation.createFromBitmap(mRS, bitmap);
+
+ if (bitmapTemp.getType().getElement().isCompatible(Element.U8(mRS))) {
+ return bitmapTemp;
+ }
+
+ tb_a8.setX(bitmapTemp.getType().getX());
+ tb_a8.setY(bitmapTemp.getType().getY());
+ Allocation bitmapAlloc = Allocation.createTyped(mRS, tb_a8.create());
+ greyConvert.forEach_RGBAtoA(bitmapTemp, bitmapAlloc);
+
+ return bitmapAlloc;
+
+ }
+
}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
index 702cc664c..276da139c 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
@@ -24,9 +24,7 @@ import android.graphics.RectF;
import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMeta;
-import com.android.gallery3d.R;
import com.android.gallery3d.app.Log;
-import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
import com.android.gallery3d.filtershow.presets.ImagePreset;
/**
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
index 9ff737e32..0a7ee3ca4 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
@@ -19,8 +19,6 @@ package com.android.gallery3d.filtershow.filters;
import android.graphics.Bitmap;
import android.graphics.Matrix;
-import com.android.gallery3d.app.Log;
-
public class ImageFilterVignette extends SimpleImageFilter {
private static final String LOGTAG = "ImageFilterVignette";
diff --git a/src/com/android/gallery3d/filtershow/filters/grey.rs b/src/com/android/gallery3d/filtershow/filters/grey.rs
new file mode 100644
index 000000000..e01880360
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/grey.rs
@@ -0,0 +1,22 @@
+ /*
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.gallery3d.filtershow.filters)
+
+uchar __attribute__((kernel)) RGBAtoA(uchar4 in) {
+ return in.r;
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java b/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java
index b4ca8e1f1..a3296779e 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java
@@ -46,15 +46,15 @@ public class EclipseControl {
private float mDownRadiusY;
private Matrix mScrToImg;
- private final static int HAN_CENTER = 0;
- private final static int HAN_NORTH = 7;
- private final static int HAN_NE = 8;
- private final static int HAN_EAST = 1;
- private final static int HAN_SE = 2;
- private final static int HAN_SOUTH = 3;
- private final static int HAN_SW = 4;
- private final static int HAN_WEST = 5;
- private final static int HAN_NW = 6;
+ public final static int HAN_CENTER = 0;
+ public final static int HAN_NORTH = 7;
+ public final static int HAN_NE = 8;
+ public final static int HAN_EAST = 1;
+ public final static int HAN_SE = 2;
+ public final static int HAN_SOUTH = 3;
+ public final static int HAN_SW = 4;
+ public final static int HAN_WEST = 5;
+ public final static int HAN_NW = 6;
public EclipseControl(Context context) {
mSliderColor = context.getResources().getColor(R.color.slider_line_color);
@@ -158,7 +158,24 @@ public class EclipseControl {
}
}
- void paintPoint(Canvas canvas, float x, float y) {
+ public void paintGrayPoint(Canvas canvas, float x, float y) {
+ if (x == Float.NaN) {
+ return;
+ }
+
+ Paint paint = new Paint();
+
+ paint.setStyle(Paint.Style.FILL);
+ paint.setColor(Color.BLUE);
+ int[] colors3 = new int[] {
+ Color.GRAY, Color.LTGRAY, 0x66000000, 0 };
+ RadialGradient g = new RadialGradient(x, y, mCenterDotSize, colors3, new float[] {
+ 0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+ paint.setShader(g);
+ canvas.drawCircle(x, y, mCenterDotSize, paint);
+ }
+
+ public void paintPoint(Canvas canvas, float x, float y) {
if (x == Float.NaN) {
return;
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
index a3645d6f5..c7d08d887 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
@@ -23,10 +23,14 @@ import android.graphics.RectF;
import com.android.gallery3d.filtershow.CropExtras;
import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
+import com.android.gallery3d.filtershow.editors.EditorFlip;
+import com.android.gallery3d.filtershow.editors.EditorRotate;
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
import com.android.gallery3d.filtershow.filters.ImageFilterGeometry;
-public class GeometryMetadata {
- private static final ImageFilterGeometry mImageFilter = new ImageFilterGeometry();
+public class GeometryMetadata extends FilterRepresentation {
private static final String LOGTAG = "GeometryMetadata";
private float mScaleFactor = 1.0f;
private float mRotation = 0;
@@ -59,9 +63,25 @@ public class GeometryMetadata {
}
public GeometryMetadata() {
+ super("GeometryMetadata");
+ setFilterClass(ImageFilterGeometry.class);
+ setEditorId(EditorCrop.ID);
+ setName("Crop");
+ setTextId(0);
+ }
+
+ @Override
+ public int[] getEditorIds() {
+ return new int[] {
+ EditorCrop.ID,
+ EditorStraighten.ID,
+ EditorRotate.ID,
+ EditorFlip.ID
+ };
}
public GeometryMetadata(GeometryMetadata g) {
+ super("GeometryMetadata");
set(g);
}
@@ -86,15 +106,6 @@ public class GeometryMetadata {
return false;
}
- public Bitmap apply(Bitmap original, float scaleFactor, int quality) {
- if (!hasModifications()) {
- return original;
- }
- mImageFilter.setGeometryMetadata(this);
- Bitmap m = mImageFilter.apply(original, scaleFactor, quality);
- return m;
- }
-
public void set(GeometryMetadata g) {
mScaleFactor = g.mScaleFactor;
mRotation = g.mRotation;
@@ -170,7 +181,7 @@ public class GeometryMetadata {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(FilterRepresentation o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
@@ -436,4 +447,17 @@ public class GeometryMetadata {
m.preRotate(-straighten, photo.centerX(), photo.centerY());
return m;
}
+
+ @Override
+ public void useParametersFrom(FilterRepresentation a) {
+ GeometryMetadata data = (GeometryMetadata) a;
+ set(data);
+ }
+
+ @Override
+ public FilterRepresentation clone() throws CloneNotSupportedException {
+ GeometryMetadata representation = (GeometryMetadata) super.clone();
+ representation.useParametersFrom(this);
+ return representation;
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
index 284bfde76..2ea6f6a42 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
@@ -26,17 +26,15 @@ import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
import com.android.gallery3d.filtershow.ui.FramedTextButton;
public class ImageCrop extends ImageGeometry {
@@ -81,11 +79,13 @@ public class ImageCrop extends ImageGeometry {
private static final String LOGTAG = "ImageCrop";
private String mAspect = "";
- private int mAspectTextSize = 24;
+ private static int mAspectTextSize = 24;
private boolean mFixedAspect = false;
- public void setAspectTextSize(int textSize) {
+ private EditorCrop mEditorCrop;
+
+ public static void setAspectTextSize(int textSize) {
mAspectTextSize = textSize;
}
@@ -151,9 +151,7 @@ public class ImageCrop extends ImageGeometry {
mAspectHeight = h;
setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
getLocalStraighten()));
- if (mVisibilityGained) {
- cropSetup();
- }
+ cropSetup();
saveAndSetPreset();
invalidate();
}
@@ -168,9 +166,7 @@ public class ImageCrop extends ImageGeometry {
mAspectHeight = h / scale;
setLocalCropBounds(getUntranslatedStraightenCropBounds(photobounds,
getLocalStraighten()));
- if (mVisibilityGained) {
- cropSetup();
- }
+ cropSetup();
saveAndSetPreset();
invalidate();
}
@@ -181,9 +177,7 @@ public class ImageCrop extends ImageGeometry {
mAspectHeight = 1;
setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
getLocalStraighten()));
- if (mVisibilityGained) {
- cropSetup();
- }
+ cropSetup();
saveAndSetPreset();
invalidate();
}
@@ -779,4 +773,8 @@ public class ImageCrop extends ImageGeometry {
}
}
+ public void setEditor(EditorCrop editorCrop) {
+ mEditorCrop = editorCrop;
+ }
+
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
index 479652ce3..6c3417ad3 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
@@ -7,7 +7,6 @@ import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.MotionEvent;
import com.android.gallery3d.filtershow.editors.EditorDraw;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
index 6bfba1b2c..70637a30c 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
@@ -24,6 +24,7 @@ import android.graphics.RectF;
import android.util.AttributeSet;
import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorFlip;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP;
public class ImageFlip extends ImageGeometry {
@@ -32,6 +33,7 @@ public class ImageFlip extends ImageGeometry {
private static final float MIN_FLICK_DIST_FOR_FLIP = 0.1f;
private static final String LOGTAG = "ImageFlip";
private FLIP mNextFlip = FLIP.NONE;
+ private EditorFlip mEditorFlip;
public ImageFlip(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -140,4 +142,8 @@ public class ImageFlip extends ImageGeometry {
drawTransformedCropped(canvas, image, gPaint);
}
+ public void setEditor(EditorFlip editorFlip) {
+ mEditorFlip = editorFlip;
+ }
+
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java
index e18f0d034..68a74dc1a 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java
@@ -29,7 +29,6 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
-import com.android.gallery3d.app.Log;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP;
import com.android.gallery3d.filtershow.presets.ImagePreset;
@@ -138,7 +137,7 @@ public abstract class ImageGeometry extends ImageShow {
}
// Overwrites local with master
- protected void syncLocalToMasterGeometry() {
+ public void syncLocalToMasterGeometry() {
mLocalGeometry = getGeometry();
calculateLocalScalingFactorAndOffset();
}
@@ -423,6 +422,7 @@ public abstract class ImageGeometry extends ImageShow {
return;
}
mHasDrawn = true;
+
drawShape(canvas, image);
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java b/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java
index 625cdbe0d..c58dd5f8e 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java
@@ -22,16 +22,12 @@ import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
-import android.graphics.RectF;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
import com.android.gallery3d.filtershow.editors.EditorRedEye;
import com.android.gallery3d.filtershow.filters.FilterPoint;
import com.android.gallery3d.filtershow.filters.FilterRedEyeRepresentation;
import com.android.gallery3d.filtershow.filters.ImageFilterRedEye;
-import com.android.gallery3d.filtershow.filters.RedEyeCandidate;
public abstract class ImagePoint extends ImageShow {
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
index 30cc9e2f3..c4b9aa27d 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
@@ -23,6 +23,7 @@ import android.graphics.Paint;
import android.util.AttributeSet;
import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorRotate;
public class ImageRotate extends ImageGeometry {
@@ -30,6 +31,7 @@ public class ImageRotate extends ImageGeometry {
private float mAngle = 0;
private final boolean mSnapToNinety = true;
+ private EditorRotate mEditorRotate;
private static final String LOGTAG = "ImageRotate";
public ImageRotate(Context context, AttributeSet attrs) {
@@ -84,4 +86,8 @@ public class ImageRotate extends ImageGeometry {
gPaint.setARGB(255, 255, 255, 255);
drawTransformedCropped(canvas, image, gPaint);
}
+
+ public void setEditor(EditorRotate editorRotate) {
+ mEditorRotate = editorRotate;
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
index 39e0cc82b..1edfd79a1 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
@@ -17,20 +17,28 @@
package com.android.gallery3d.filtershow.imageshow;
import android.content.Context;
-import android.graphics.*;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.net.Uri;
import android.os.Handler;
import android.util.AttributeSet;
-import android.util.Log;
-import android.view.*;
+import android.view.GestureDetector;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
import android.widget.LinearLayout;
import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.PanelController;
import com.android.gallery3d.filtershow.cache.ImageLoader;
-import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
import com.android.gallery3d.filtershow.filters.ImageFilter;
import com.android.gallery3d.filtershow.presets.ImagePreset;
@@ -289,11 +297,6 @@ public class ImageShow extends View implements OnGestureListener,
}
}
- public void defaultDrawImage(Canvas canvas) {
- drawImage(canvas, getFilteredImage());
- drawPartialImage(canvas, getGeometryOnlyImage());
- }
-
@Override
public void onDraw(Canvas canvas) {
MasterImage.getImage().setImageShowSize(getWidth(), getHeight());
@@ -306,7 +309,7 @@ public class ImageShow extends View implements OnGestureListener,
canvas.scale(scaleFactor, scaleFactor, cx, cy);
canvas.translate(translation.x, translation.y);
drawBackground(canvas);
- defaultDrawImage(canvas);
+ drawImage(canvas, getFilteredImage());
canvas.restore();
if (showTitle() && getImagePreset() != null) {
@@ -326,6 +329,13 @@ public class ImageShow extends View implements OnGestureListener,
Rect dest = new Rect(0, 0, getWidth(), getHeight());
canvas.drawBitmap(partialPreview, src, dest, mPaint);
}
+
+ canvas.save();
+ canvas.scale(scaleFactor, scaleFactor, cx, cy);
+ canvas.translate(translation.x, translation.y);
+ drawPartialImage(canvas, getGeometryOnlyImage());
+ canvas.restore();
+
drawToast(canvas);
}
@@ -374,7 +384,7 @@ public class ImageShow extends View implements OnGestureListener,
canvas.save();
if (image != null) {
if (mShowOriginalDirection == 0) {
- if ((mTouch.y - mTouchDown.y) > (mTouch.x - mTouchDown.x)) {
+ if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
mShowOriginalDirection = UNVEIL_VERTICAL;
} else {
mShowOriginalDirection = UNVEIL_HORIZONTAL;
@@ -397,16 +407,18 @@ public class ImageShow extends View implements OnGestureListener,
drawImage(canvas, image);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
+ paint.setStrokeWidth(3);
if (mShowOriginalDirection == UNVEIL_VERTICAL) {
- canvas.drawLine(mImageBounds.left, mTouch.y - 1,
- mImageBounds.right, mTouch.y - 1, paint);
+ canvas.drawLine(mImageBounds.left, mTouch.y,
+ mImageBounds.right, mTouch.y, paint);
} else {
- canvas.drawLine(mTouch.x - 1, mImageBounds.top,
- mTouch.x - 1, mImageBounds.bottom, paint);
+ canvas.drawLine(mTouch.x, mImageBounds.top,
+ mTouch.x, mImageBounds.bottom, paint);
}
Rect bounds = new Rect();
+ paint.setAntiAlias(true);
paint.setTextSize(mOriginalTextSize);
paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
paint.setColor(Color.BLACK);
@@ -560,6 +572,7 @@ public class ImageShow extends View implements OnGestureListener,
translation.y = (int) (originalTranslation.y + translateY);
MasterImage.getImage().setTranslation(translation);
}
+ mTouchShowOriginal = false;
} else if (!mOriginalDisabled && !mActivity.isShowingHistoryPanel()
&& (System.currentTimeMillis() - mTouchShowOriginalDate
> mTouchShowOriginalDelayMin)
@@ -617,14 +630,16 @@ public class ImageShow extends View implements OnGestureListener,
if (mActivity == null) {
return false;
}
+ if (endEvent.getPointerCount() == 2) {
+ return false;
+ }
if ((!mActivity.isShowingHistoryPanel() && startEvent.getX() > endEvent.getX())
|| (mActivity.isShowingHistoryPanel() && endEvent.getX() > startEvent.getX())) {
if (!mTouchShowOriginal
|| (mTouchShowOriginal &&
(System.currentTimeMillis() - mTouchShowOriginalDate
< mTouchShowOriginalDelayMax))) {
- // TODO fix gesture.
- // mActivity.toggleHistoryPanel();
+ mActivity.toggleHistoryPanel();
}
}
return true;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
index dfd950565..866b1b05f 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
@@ -19,18 +19,18 @@ package com.android.gallery3d.filtershow.imageshow;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
public class ImageStraighten extends ImageGeometry {
private float mBaseAngle = 0;
private float mAngle = 0;
+ private EditorStraighten mEditorStraighten;
private static final String LOGTAG = "ImageStraighten";
private static final Paint gPaint = new Paint();
@@ -134,4 +134,8 @@ public class ImageStraighten extends ImageGeometry {
}
}
+ public void setEditor(EditorStraighten editorStraighten) {
+ mEditorStraighten = editorStraighten;
+ }
+
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java b/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java
index 3795d1f21..a49636fae 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java
@@ -18,15 +18,11 @@ package com.android.gallery3d.filtershow.imageshow;
import android.content.Context;
import android.graphics.Canvas;
-import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.android.gallery3d.filtershow.editors.BasicEditor;
-import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
-import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation;
import com.android.gallery3d.filtershow.filters.FilterTinyPlanetRepresentation;
-import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet;
public class ImageTinyPlanet extends ImageShow {
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
index a51d10276..c55e5ae42 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.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.
@@ -88,14 +88,20 @@ public class ImageVignette extends ImageShow {
setRepresentation(mVignetteRep);
break;
}
- resetImageCaches(this);
+ computeEllipses();
invalidate();
- mEditorVignette.commitLocalRepresentation();
return true;
}
public void setRepresentation(FilterVignetteRepresentation vignetteRep) {
mVignetteRep = vignetteRep;
+ computeEllipses();
+ }
+
+ public void computeEllipses() {
+ if (mVignetteRep == null) {
+ return;
+ }
Matrix toImg = getScreenToImageMatrix(false);
Matrix toScr = new Matrix();
toImg.invert(toScr);
@@ -125,6 +131,7 @@ public class ImageVignette extends ImageShow {
mElipse.setRadius(toScr.mapRadius(mVignetteRep.getRadiusX()),
toScr.mapRadius(mVignetteRep.getRadiusY()));
}
+ mEditorVignette.commitLocalRepresentation();
}
public void setEditor(EditorVignette editorVignette) {
@@ -132,11 +139,28 @@ public class ImageVignette extends ImageShow {
}
@Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ computeEllipses();
+ }
+
+ @Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
- setRepresentation(mVignetteRep);
- mElipse.draw(canvas);
+ if (mVignetteRep == null) {
+ return;
+ }
+ Matrix toImg = getScreenToImageMatrix(false);
+ Matrix toScr = new Matrix();
+ toImg.invert(toScr);
+ float[] c = new float[] {
+ mVignetteRep.getCenterX(), mVignetteRep.getCenterY() };
+ toScr.mapPoints(c);
+ mElipse.setCenter(c[0], c[1]);
+ mElipse.setRadius(toScr.mapRadius(mVignetteRep.getRadiusX()),
+ toScr.mapRadius(mVignetteRep.getRadiusY()));
+ mElipse.draw(canvas);
}
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
index 9eafe2236..209c3bdb9 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
@@ -17,7 +17,11 @@
package com.android.gallery3d.filtershow.imageshow;
import android.graphics.*;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.util.Log;
import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.HistoryAdapter;
import com.android.gallery3d.filtershow.ImageStateAdapter;
@@ -31,8 +35,12 @@ import java.util.Vector;
public class MasterImage implements RenderingRequestCaller {
private static final String LOGTAG = "MasterImage";
+ private boolean DEBUG = false;
private static MasterImage sMasterImage = null;
+ private static int sIconSeedSize = 128;
+ private static float sHistoryPreviewSize = 128.0f;
+ private Bitmap mThumbnailBitmap;
private ImageFilter mCurrentFilter = null;
private ImagePreset mPreset = null;
@@ -63,6 +71,20 @@ public class MasterImage implements RenderingRequestCaller {
private Point mImageShowSize = new Point();
+ final private static int NEW_GEOMETRY = 1;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case NEW_GEOMETRY: {
+ hasNewGeometry();
+ break;
+ }
+ }
+ }
+ };
+
private MasterImage() {
}
@@ -73,6 +95,10 @@ public class MasterImage implements RenderingRequestCaller {
return sMasterImage;
}
+ public static void setIconSeedSize(int iconSeedSize) {
+ sIconSeedSize = iconSeedSize;
+ }
+
public void addObserver(ImageShow observer) {
if (mObservers.contains(observer)) {
return;
@@ -107,6 +133,7 @@ public class MasterImage implements RenderingRequestCaller {
mPreset.fillImageStateAdapter(mState);
if (addToHistory) {
mHistory.addHistoryItem(mPreset);
+ renderHistoryPreview();
}
updatePresets(true);
GeometryMetadata geo = mPreset.mGeoData;
@@ -116,6 +143,23 @@ public class MasterImage implements RenderingRequestCaller {
mPreviousGeometry = new GeometryMetadata(geo);
}
+ private void renderHistoryPreview() {
+ ImagePreset historyPreset = mPreset;
+ if (historyPreset != null) {
+ Bitmap preview = mLoader.getOriginalBitmapSmall();
+ if (preview != null) {
+ float s = Math.min(preview.getWidth(), preview.getHeight());
+ float f = sHistoryPreviewSize / s;
+ int w = (int) (preview.getWidth() * f);
+ int h = (int) (preview.getHeight() * f);
+ Bitmap historyPreview = Bitmap.createScaledBitmap(preview, w, h, true);
+ historyPreset.setPreviewImage(historyPreview);
+ RenderingRequest.post(historyPreview,
+ historyPreset, RenderingRequest.ICON_RENDERING, this);
+ }
+ }
+ }
+
private void setGeometry() {
Bitmap image = mLoader.getOriginalBitmapLarge();
if (image == null) {
@@ -182,6 +226,16 @@ public class MasterImage implements RenderingRequestCaller {
return mFilteredPreview;
}
+ public void setOriginalGeometry(Bitmap originalBitmapLarge) {
+ GeometryMetadata geo = getPreset().mGeoData;
+ float w = originalBitmapLarge.getWidth();
+ float h = originalBitmapLarge.getHeight();
+ RectF r = new RectF(0, 0, w, h);
+ geo.setPhotoBounds(r);
+ geo.setCropBounds(r);
+ getPreset().setGeometry(geo);
+ }
+
public Bitmap getFilteredImage() {
return mFilteredPreview.getConsumer();
}
@@ -208,7 +262,8 @@ public class MasterImage implements RenderingRequestCaller {
if (force || mGeometryOnlyPreset == null) {
ImagePreset newPreset = new ImagePreset(mPreset);
newPreset.setDoApplyFilters(false);
- if (mGeometryOnlyPreset == null
+ newPreset.setDoApplyGeometry(true);
+ if (force || mGeometryOnlyPreset == null
|| !newPreset.same(mGeometryOnlyPreset)) {
mGeometryOnlyPreset = newPreset;
RenderingRequest.post(mLoader.getOriginalBitmapLarge(),
@@ -217,8 +272,9 @@ public class MasterImage implements RenderingRequestCaller {
}
if (force || mFiltersOnlyPreset == null) {
ImagePreset newPreset = new ImagePreset(mPreset);
+ newPreset.setDoApplyFilters(true);
newPreset.setDoApplyGeometry(false);
- if (mFiltersOnlyPreset == null
+ if (force || mFiltersOnlyPreset == null
|| !newPreset.same(mFiltersOnlyPreset)) {
mFiltersOnlyPreset = newPreset;
RenderingRequest.post(mLoader.getOriginalBitmapLarge(),
@@ -255,6 +311,7 @@ public class MasterImage implements RenderingRequestCaller {
invalidatePartialPreview();
needsUpdateFullResPreview();
FilteringPipeline.getPipeline().updatePreviewBuffer();
+ renderHistoryPreview();
}
public void setImageShowSize(int w, int h) {
@@ -319,6 +376,12 @@ public class MasterImage implements RenderingRequestCaller {
mPartialBitmap = request.getBitmap();
notifyObservers();
}
+ if (request.getType() == RenderingRequest.ICON_RENDERING) {
+ // History preview images
+ ImagePreset preset = request.getOriginalImagePreset();
+ preset.setPreviewImage(request.getBitmap());
+ mHistory.notifyDataSetChanged();
+ }
}
public static void reset() {
@@ -330,11 +393,67 @@ public class MasterImage implements RenderingRequestCaller {
}
public void notifyGeometryChange() {
+ if (mHandler.hasMessages(NEW_GEOMETRY)) {
+ return;
+ }
+ mHandler.sendEmptyMessage(NEW_GEOMETRY);
+ }
+
+ public void hasNewGeometry() {
+ updatePresets(true);
+ computeThumbnailBitmap();
for (GeometryListener listener : mGeometryListeners) {
listener.geometryChanged();
}
}
+ private Bitmap createSquareImage(Bitmap dst, Bitmap image, Rect destination) {
+ if (image != null) {
+ Canvas canvas = new Canvas(dst);
+ int iw = image.getWidth();
+ int ih = image.getHeight();
+ int x = 0;
+ int y = 0;
+ int size = 0;
+ Rect source = null;
+ if (iw > ih) {
+ size = ih;
+ x = (int) ((iw - size) / 2.0f);
+ y = 0;
+ } else {
+ size = iw;
+ x = 0;
+ y = (int) ((ih - size) / 2.0f);
+ }
+ source = new Rect(x, y, x + size, y + size);
+ canvas.drawBitmap(image, source, destination, new Paint());
+ }
+ return dst;
+ }
+
+ public void computeThumbnailBitmap() {
+ Bitmap bmap = mLoader.getOriginalBitmapSmall();
+ if (bmap == null) {
+ return;
+ }
+ ImagePreset geoPreset = new ImagePreset(MasterImage.getImage().getGeometryPreset());
+ bmap = geoPreset.applyGeometry(bmap);
+ float w = bmap.getWidth();
+ float h = bmap.getHeight();
+ float s = Math.min(w, h);
+ float f = sIconSeedSize / s;
+ w = w * f;
+ h = h * f;
+ s = Math.min(w, h);
+ Bitmap bmap2 = Bitmap.createScaledBitmap(bmap, (int) s, (int) s, true);
+ bmap = createSquareImage(bmap2, bmap, new Rect(0, 0, (int) s, (int) s));
+ if (DEBUG) {
+ Log.v(LOGTAG, "Create thumbnail of size " + bmap.getWidth() + " x " + bmap.getHeight()
+ + " seed size: " + sIconSeedSize);
+ }
+ mThumbnailBitmap = bmap;
+ }
+
public float getScaleFactor() {
return mScaleFactor;
}
@@ -368,4 +487,8 @@ public class MasterImage implements RenderingRequestCaller {
mTranslation.y = 0;
needsUpdateFullResPreview();
}
+
+ public Bitmap getThumbnailBitmap() {
+ return mThumbnailBitmap;
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
index ae5a03414..ca74a8729 100644
--- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
@@ -22,7 +22,6 @@ import android.util.Log;
import com.android.gallery3d.filtershow.ImageStateAdapter;
import com.android.gallery3d.filtershow.cache.ImageLoader;
-import com.android.gallery3d.filtershow.filters.BaseFiltersManager;
import com.android.gallery3d.filtershow.filters.FilterRepresentation;
import com.android.gallery3d.filtershow.filters.FiltersManager;
import com.android.gallery3d.filtershow.filters.ImageFilter;
@@ -55,6 +54,7 @@ public class ImagePreset {
public final GeometryMetadata mGeoData = new GeometryMetadata();
private boolean mPartialRendering = false;
private Rect mPartialRenderingBounds;
+ private Bitmap mPreviewImage;
public ImagePreset() {
setup();
@@ -130,10 +130,17 @@ public class ImagePreset {
}
public void updateFilterRepresentation(FilterRepresentation representation) {
+ if (representation == null) {
+ return;
+ }
synchronized (mFilters) {
- int position = getPositionForRepresentation(representation);
- FilterRepresentation old = mFilters.elementAt(position);
- old.updateTempParametersFrom(representation);
+ if (representation instanceof GeometryMetadata) {
+ setGeometry((GeometryMetadata) representation);
+ } else {
+ int position = getPositionForRepresentation(representation);
+ FilterRepresentation old = mFilters.elementAt(position);
+ old.updateTempParametersFrom(representation);
+ }
}
MasterImage.getImage().invalidatePreview();
}
@@ -192,6 +199,7 @@ public class ImagePreset {
public synchronized void setGeometry(GeometryMetadata m) {
mGeoData.set(m);
+ MasterImage.getImage().notifyGeometryChange();
}
private void setBorder(FilterRepresentation filter) {
@@ -327,8 +335,28 @@ public class ImagePreset {
Log.v(LOGTAG, "/// showFilters -- " + mFilters.size() + " filters");
}
+ public FilterRepresentation getLastRepresentation() {
+ if (mFilters.size() > 0) {
+ return mFilters.lastElement();
+ }
+ return null;
+ }
+
+ public void removeFilter(FilterRepresentation filterRepresentation) {
+ for (int i = 0; i < mFilters.size(); i++) {
+ if (mFilters.elementAt(i).getFilterClass() == filterRepresentation.getFilterClass()) {
+ mFilters.remove(i);
+ setHistoryName("Remove");
+ return;
+ }
+ }
+ }
+
public void addFilter(FilterRepresentation representation) {
- Log.v(LOGTAG, "*** Add Filter *** " + representation);
+ if (representation instanceof GeometryMetadata) {
+ setGeometry((GeometryMetadata) representation);
+ return;
+ }
if (representation.getPriority() == FilterRepresentation.TYPE_BORDER) {
setHistoryName(representation.getName());
setBorder(representation);
@@ -360,6 +388,9 @@ public class ImagePreset {
}
public FilterRepresentation getRepresentation(FilterRepresentation filterRepresentation) {
+ if (filterRepresentation instanceof GeometryMetadata) {
+ return mGeoData;
+ }
for (int i = 0; i < mFilters.size(); i++) {
FilterRepresentation representation = mFilters.elementAt(i);
if (representation.getFilterClass() == filterRepresentation.getFilterClass()) {
@@ -385,7 +416,14 @@ public class ImagePreset {
public Bitmap applyGeometry(Bitmap bitmap) {
// Apply any transform -- 90 rotate, flip, straighten, crop
// Returns a new bitmap.
- return mGeoData.apply(bitmap, mScaleFactor, mQuality);
+ if (mDoApplyGeometry) {
+ ImageFilter filter = FiltersManager.getManager().getFilterForRepresentation(mGeoData);
+ mGeoData.synchronizeRepresentation();
+ filter.useRepresentation(mGeoData);
+ filter.setImagePreset(this);
+ bitmap = filter.apply(bitmap, mScaleFactor, mQuality);
+ }
+ return bitmap;
}
public Bitmap applyBorder(Bitmap bitmap) {
@@ -428,6 +466,12 @@ public class ImagePreset {
if (mGeoData.hasModifications()) {
return false;
}
+ if (mBorder != null && !mBorder.supportsPartialRendering()) {
+ return false;
+ }
+ if (ImageLoader.getZoomOrientation() != ImageLoader.ORI_NORMAL) {
+ return false;
+ }
for (int i = 0; i < mFilters.size(); i++) {
FilterRepresentation representation = null;
synchronized (mFilters) {
@@ -478,4 +522,13 @@ public class ImagePreset {
public Rect getPartialRenderingBounds() {
return mPartialRenderingBounds;
}
+
+ public Bitmap getPreviewImage() {
+ return mPreviewImage;
+ }
+
+ public void setPreviewImage(Bitmap previewImage) {
+ mPreviewImage = previewImage;
+ }
+
}
diff --git a/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java b/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java
index de2e1e5dc..9d50d5ac0 100644
--- a/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java
+++ b/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java
@@ -37,6 +37,7 @@ public class FilterIconButton extends IconButton implements View.OnClickListener
RenderingRequestCaller, GeometryListener {
private static final String LOGTAG = "FilterIconButton";
private Bitmap mOverlayBitmap = null;
+ private boolean mOverlayOnly = false;
private PanelController mController = null;
private FilterRepresentation mFilterRepresentation = null;
private LinearLayout mParentContainer = null;
@@ -68,14 +69,16 @@ public class FilterIconButton extends IconButton implements View.OnClickListener
@Override
protected Bitmap drawImage(Bitmap dst, Bitmap image, Rect destination) {
+ if (mOverlayOnly) {
+ // TODO: merge back IconButton and FilterIconButton
+ return super.drawImage(dst, image, destination);
+ }
if (mIconBitmap == null && mPreset == null) {
- ImageLoader loader = MasterImage.getImage().getLoader();
- if (loader != null) {
- ImagePreset geoPreset = new ImagePreset(MasterImage.getImage().getGeometryPreset());
- image = geoPreset.applyGeometry(image);
- dst = super.drawImage(dst, image, destination);
+ dst = MasterImage.getImage().getThumbnailBitmap();
+ if (dst != null) {
ImagePreset mPreset = new ImagePreset();
mPreset.addFilter(mFilterRepresentation);
+ mPreset.setDoApplyGeometry(false);
mDestination = destination;
RenderingRequest.post(dst.copy(Bitmap.Config.ARGB_8888, true), mPreset, RenderingRequest.ICON_RENDERING, this);
}
@@ -112,6 +115,11 @@ public class FilterIconButton extends IconButton implements View.OnClickListener
mOverlayBitmap = BitmapFactory.decodeResource(getResources(),
mFilterRepresentation.getOverlayId());
}
+ mOverlayOnly = mFilterRepresentation.getOverlayOnly();
+ if (mOverlayOnly) {
+ setIcon(mOverlayBitmap);
+ }
+ stale_icon = true;
invalidate();
}
@@ -124,13 +132,14 @@ public class FilterIconButton extends IconButton implements View.OnClickListener
if (mOverlayBitmap != null) {
mIconBitmap = super.drawImage(mIconBitmap, mOverlayBitmap, mDestination);
}
- invalidate();
stale_icon = true;
+ invalidate();
}
@Override
public void geometryChanged() {
stale_icon = true;
+
mIconBitmap = null;
mPreset = null;
invalidate();
diff --git a/src/com/android/gallery3d/filtershow/ui/IconButton.java b/src/com/android/gallery3d/filtershow/ui/IconButton.java
index 28d01dfd9..ed10be301 100644
--- a/src/com/android/gallery3d/filtershow/ui/IconButton.java
+++ b/src/com/android/gallery3d/filtershow/ui/IconButton.java
@@ -17,19 +17,14 @@
package com.android.gallery3d.filtershow.ui;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.Button;
-import com.android.gallery3d.R;
-
/**
* Class of buttons with both an image icon and text.
*/
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
index e54c83eff..3e52f5ee5 100644
--- a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
+++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
@@ -35,7 +35,6 @@ import android.widget.PopupMenu;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.editors.EditorCurves;
-import com.android.gallery3d.filtershow.filters.BaseFiltersManager;
import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation;
import com.android.gallery3d.filtershow.filters.FiltersManager;
import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
diff --git a/src/com/android/gallery3d/glrenderer/BasicTexture.java b/src/com/android/gallery3d/glrenderer/BasicTexture.java
index 82eb5a7ee..2e77b903f 100644
--- a/src/com/android/gallery3d/glrenderer/BasicTexture.java
+++ b/src/com/android/gallery3d/glrenderer/BasicTexture.java
@@ -78,8 +78,8 @@ public abstract class BasicTexture implements Texture {
public void setSize(int width, int height) {
mWidth = width;
mHeight = height;
- mTextureWidth = Utils.nextPowerOf2(width);
- mTextureHeight = Utils.nextPowerOf2(height);
+ mTextureWidth = width > 0 ? Utils.nextPowerOf2(width) : 0;
+ mTextureHeight = height > 0 ? Utils.nextPowerOf2(height) : 0;
if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
Log.w(TAG, String.format("texture is too large: %d x %d",
mTextureWidth, mTextureHeight), new Exception());
diff --git a/src/com/android/photos/AlbumSetFragment.java b/src/com/android/photos/AlbumSetFragment.java
new file mode 100644
index 000000000..3c51bbac3
--- /dev/null
+++ b/src/com/android/photos/AlbumSetFragment.java
@@ -0,0 +1,157 @@
+/*
+ * 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.photos;
+
+import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.CursorAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+import com.android.photos.data.AlbumSetLoader;
+import com.android.photos.drawables.DrawableFactory;
+import com.android.photos.shims.MediaSetLoader;
+
+import java.util.Date;
+
+
+public class AlbumSetFragment extends Fragment implements OnItemClickListener,
+ LoaderCallbacks<Cursor> {
+
+ private GridView mAlbumSetView;
+ private View mEmptyView;
+ private AlbumSetCursorAdapter mAdapter;
+
+ private static final int LOADER_ALBUMSET = 1;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.album_set, container, false);
+ mAlbumSetView = (GridView) root.findViewById(android.R.id.list);
+ mEmptyView = root.findViewById(android.R.id.empty);
+ mEmptyView.setVisibility(View.GONE);
+ mAdapter = new AlbumSetCursorAdapter(getActivity());
+ mAlbumSetView.setAdapter(mAdapter);
+ mAlbumSetView.setOnItemClickListener(this);
+ getLoaderManager().initLoader(LOADER_ALBUMSET, null, this);
+ updateEmptyStatus();
+ return root;
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // TODO: Switch to AlbumSetLoader
+ MediaSetLoader loader = new MediaSetLoader(getActivity());
+ mAdapter.setDrawableFactory(loader);
+ return loader;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader,
+ Cursor data) {
+ mAdapter.swapCursor(data);
+ updateEmptyStatus();
+ }
+
+ private void updateEmptyStatus() {
+ boolean empty = (mAdapter == null || mAdapter.getCount() == 0);
+ mAlbumSetView.setVisibility(empty ? View.GONE : View.VISIBLE);
+ mEmptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> av, View v, int pos, long id) {
+ Cursor c = (Cursor) av.getItemAtPosition(pos);
+ int albumId = c.getInt(AlbumSetLoader.INDEX_ID);
+ // TODO launch an activity showing the photos in the album
+ Toast.makeText(v.getContext(), "Clicked " + albumId, Toast.LENGTH_SHORT).show();
+ }
+
+ private static class AlbumSetCursorAdapter extends CursorAdapter {
+
+ private DrawableFactory<Cursor> mDrawableFactory;
+
+ public void setDrawableFactory(DrawableFactory<Cursor> factory) {
+ mDrawableFactory = factory;
+ }
+ private Date mDate = new Date(); // Used for converting timestamps for display
+
+ public AlbumSetCursorAdapter(Context context) {
+ super(context, null, false);
+ }
+
+ @Override
+ public void bindView(View v, Context context, Cursor cursor) {
+ TextView titleTextView = (TextView) v.findViewById(
+ R.id.album_set_item_title);
+ titleTextView.setText(cursor.getString(AlbumSetLoader.INDEX_TITLE));
+
+ TextView dateTextView = (TextView) v.findViewById(
+ R.id.album_set_item_date);
+ long timestamp = cursor.getLong(AlbumSetLoader.INDEX_TIMESTAMP);
+ if (timestamp > 0) {
+ mDate.setTime(timestamp);
+ dateTextView.setText(DateFormat.getMediumDateFormat(context).format(mDate));
+ } else {
+ dateTextView.setText(null);
+ }
+
+ ProgressBar uploadProgressBar = (ProgressBar) v.findViewById(
+ R.id.album_set_item_upload_progress);
+ if (cursor.getInt(AlbumSetLoader.INDEX_COUNT_PENDING_UPLOAD) > 0) {
+ uploadProgressBar.setVisibility(View.VISIBLE);
+ uploadProgressBar.setProgress(50);
+ } else {
+ uploadProgressBar.setVisibility(View.INVISIBLE);
+ }
+
+ ImageView thumbImageView = (ImageView) v.findViewById(
+ R.id.album_set_item_image);
+ Drawable recycle = thumbImageView.getDrawable();
+ Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
+ if (recycle != drawable) {
+ thumbImageView.setImageDrawable(drawable);
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ return LayoutInflater.from(context).inflate(
+ R.layout.album_set_item, parent, false);
+ }
+ }
+}
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
new file mode 100644
index 000000000..1c7115191
--- /dev/null
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -0,0 +1,87 @@
+/*
+ * 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.photos;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Rect;
+import android.util.Log;
+import com.android.photos.views.TiledImageRenderer;
+
+import java.io.IOException;
+
+public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
+
+ BitmapRegionDecoder mDecoder;
+
+
+ public BitmapRegionTileSource(String path) {
+ try {
+ mDecoder = BitmapRegionDecoder.newInstance(path, true);
+ } catch (IOException e) {
+ Log.w("BitmapRegionTileSource", "ctor failed", e);
+ }
+ }
+
+ @Override
+ public int getTileSize() {
+ return 256;
+ }
+
+ @Override
+ public int getImageWidth() {
+ return mDecoder.getWidth();
+ }
+
+ @Override
+ public int getImageHeight() {
+ return mDecoder.getHeight();
+ }
+
+ @Override
+ public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+ int tileSize = getTileSize();
+ int t = tileSize << level;
+
+ Rect wantRegion = new Rect(x, y, x + t, y + t);
+
+ if (bitmap == null) {
+ bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
+ }
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ options.inPreferQualityOverSpeed = true;
+ options.inSampleSize = (1 << level);
+ options.inBitmap = bitmap;
+
+ try {
+ // In CropImage, we may call the decodeRegion() concurrently.
+ bitmap = mDecoder.decodeRegion(wantRegion, options);
+ } finally {
+ if (options.inBitmap != bitmap && options.inBitmap != null) {
+ options.inBitmap = null;
+ }
+ }
+
+ if (bitmap == null) {
+ Log.w("BitmapRegionTileSource", "fail in decoding region");
+ }
+ return bitmap;
+ }
+}
diff --git a/src/com/android/photos/FullscreenViewer.java b/src/com/android/photos/FullscreenViewer.java
new file mode 100644
index 000000000..50ea1ba71
--- /dev/null
+++ b/src/com/android/photos/FullscreenViewer.java
@@ -0,0 +1,44 @@
+/*
+ * 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.photos;
+
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.photos.views.TiledImageView;
+
+
+public class FullscreenViewer extends Activity {
+
+ private TiledImageView mTextureView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String path = getIntent().getData().toString();
+ mTextureView = new TiledImageView(this);
+ mTextureView.setTileSource(new BitmapRegionTileSource(path));
+ setContentView(mTextureView);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mTextureView.destroy();
+ }
+
+}
diff --git a/src/com/android/photos/GalleryActivity.java b/src/com/android/photos/GalleryActivity.java
new file mode 100644
index 000000000..46b5140fb
--- /dev/null
+++ b/src/com/android/photos/GalleryActivity.java
@@ -0,0 +1,125 @@
+/*
+ * 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.photos;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.R;
+
+public class GalleryActivity extends Activity {
+
+ private final String FTAG_PHOTOSET = "PhotoSet";
+ private final String FTAG_ALBUMSET = "AlbumSet";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setupActionBar();
+ }
+
+ private void setupActionBar() {
+ ActionBar ab = getActionBar();
+ ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ ab.setDisplayShowHomeEnabled(false);
+ ab.setDisplayShowTitleEnabled(false);
+ Tab tab = ab.newTab();
+ tab.setText(R.string.tab_photos);
+ tab.setTabListener(new TabListener<PhotoSetFragment>(this,
+ FTAG_PHOTOSET, PhotoSetFragment.class));
+ ab.addTab(tab, true);
+ tab = ab.newTab();
+ tab.setText(R.string.tab_albums);
+ tab.setTabListener(new TabListener<AlbumSetFragment>(this,
+ FTAG_ALBUMSET, AlbumSetFragment.class));
+ ab.addTab(tab);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.gallery, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_camera:
+ Intent intent = new Intent(this, CameraActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private static class TabListener<T extends Fragment> implements ActionBar.TabListener {
+ private Fragment mFragment;
+ private final Activity mActivity;
+ private final String mTag;
+ private final Class<T> mClass;
+
+ /** Constructor used each time a new tab is created.
+ * @param activity The host Activity, used to instantiate the fragment
+ * @param tag The identifier tag for the fragment
+ * @param clz The fragment's Class, used to instantiate the fragment
+ */
+ public TabListener(Activity activity, String tag, Class<T> clz) {
+ mActivity = activity;
+ mTag = tag;
+ mClass = clz;
+ }
+
+ /* The following are each of the ActionBar.TabListener callbacks */
+
+ @Override
+ public void onTabSelected(Tab tab, FragmentTransaction ft) {
+ // Check if the fragment is already initialized
+ if (mFragment == null) {
+ // If not, instantiate and add it to the activity
+ mFragment = Fragment.instantiate(mActivity, mClass.getName());
+ ft.add(android.R.id.content, mFragment, mTag);
+ } else {
+ // If it exists, simply attach it in order to show it
+ ft.attach(mFragment);
+ }
+ }
+
+ @Override
+ public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+ if (mFragment != null) {
+ // Detach the fragment, because another one is being attached
+ ft.detach(mFragment);
+ }
+ }
+
+ @Override
+ public void onTabReselected(Tab tab, FragmentTransaction ft) {
+ // User selected the already selected tab. Usually do nothing.
+ }
+ }
+}
diff --git a/src/com/android/photos/PhotoFragment.java b/src/com/android/photos/PhotoFragment.java
new file mode 100644
index 000000000..3be6313f2
--- /dev/null
+++ b/src/com/android/photos/PhotoFragment.java
@@ -0,0 +1,25 @@
+/*
+ * 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.photos;
+
+import android.app.Fragment;
+
+
+public class PhotoFragment extends Fragment {
+
+}
diff --git a/src/com/android/photos/PhotoSetFragment.java b/src/com/android/photos/PhotoSetFragment.java
new file mode 100644
index 000000000..1de8de5a7
--- /dev/null
+++ b/src/com/android/photos/PhotoSetFragment.java
@@ -0,0 +1,136 @@
+/*
+ * 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.photos;
+
+import android.app.Fragment;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.CursorAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+
+import com.android.gallery3d.R;
+import com.android.photos.data.PhotoSetLoader;
+import com.android.photos.drawables.DrawableFactory;
+import com.android.photos.shims.MediaItemsLoader;
+import com.android.photos.views.GalleryThumbnailView.GalleryThumbnailAdapter;
+
+
+public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor> {
+
+ private static final int LOADER_PHOTOSET = 1;
+
+ private GridView mPhotoSetView;
+ private View mEmptyView;
+ private ThumbnailAdapter mAdapter;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.photo_set, container, false);
+ mPhotoSetView = (GridView) root.findViewById(android.R.id.list);
+ // TODO: Remove once UI stabilizes
+ mPhotoSetView.setColumnWidth(MediaItemsLoader.getThumbnailSize());
+ mEmptyView = root.findViewById(android.R.id.empty);
+ mEmptyView.setVisibility(View.GONE);
+ mAdapter = new ThumbnailAdapter(getActivity());
+ mPhotoSetView.setAdapter(mAdapter);
+ getLoaderManager().initLoader(LOADER_PHOTOSET, null, this);
+ updateEmptyStatus();
+ return root;
+ }
+
+ private void updateEmptyStatus() {
+ boolean empty = (mAdapter == null || mAdapter.getCount() == 0);
+ mPhotoSetView.setVisibility(empty ? View.GONE : View.VISIBLE);
+ mEmptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ // TODO: Switch to PhotoSetLoader
+ MediaItemsLoader loader = new MediaItemsLoader(getActivity());
+ mAdapter.setDrawableFactory(loader);
+ return loader;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader,
+ Cursor data) {
+ mAdapter.swapCursor(data);
+ updateEmptyStatus();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ }
+
+ private static class ThumbnailAdapter extends CursorAdapter implements GalleryThumbnailAdapter {
+ private LayoutInflater mInflater;
+ private DrawableFactory<Cursor> mDrawableFactory;
+
+ public ThumbnailAdapter(Context context) {
+ super(context, null, false);
+ mInflater = LayoutInflater.from(context);
+ }
+
+ public void setDrawableFactory(DrawableFactory<Cursor> factory) {
+ mDrawableFactory = factory;
+ }
+
+ @Override
+ public void bindView(View view, Context context, Cursor cursor) {
+ ImageView iv = (ImageView) view;
+ Drawable recycle = iv.getDrawable();
+ Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
+ if (recycle != drawable) {
+ iv.setImageDrawable(drawable);
+ }
+ }
+
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ View view = mInflater.inflate(R.layout.photo_set_item, parent, false);
+ LayoutParams params = view.getLayoutParams();
+ int columnWidth = ((GridView) parent).getColumnWidth();
+ params.height = columnWidth;
+ view.setLayoutParams(params);
+ return view;
+ }
+
+ @Override
+ public float getIntrinsicAspectRatio(int position) {
+ Cursor cursor = getItem(position);
+ float width = cursor.getInt(PhotoSetLoader.INDEX_WIDTH);
+ float height = cursor.getInt(PhotoSetLoader.INDEX_HEIGHT);
+ return width / height;
+ }
+
+ @Override
+ public Cursor getItem(int position) {
+ return (Cursor) super.getItem(position);
+ }
+ }
+}
diff --git a/src/com/android/photos/canvas/CanvasActivity.java b/src/com/android/photos/canvas/CanvasActivity.java
index 17afdb8b4..ad3cb638b 100644
--- a/src/com/android/photos/canvas/CanvasActivity.java
+++ b/src/com/android/photos/canvas/CanvasActivity.java
@@ -20,14 +20,14 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import com.google.android.canvas.provider.CanvasContract;
+import com.google.android.pano.provider.PanoContract;
public class CanvasActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Intent intent = CanvasContract.getBrowseIntent(
+ Intent intent = PanoContract.getBrowseIntent(
CanvasProvider.BROWSER_ROOT_URI, 0);
startActivity(intent);
finish();
diff --git a/src/com/android/photos/canvas/CanvasProvider.java b/src/com/android/photos/canvas/CanvasProvider.java
index 1ed6cd461..1bc55669a 100644
--- a/src/com/android/photos/canvas/CanvasProvider.java
+++ b/src/com/android/photos/canvas/CanvasProvider.java
@@ -42,8 +42,8 @@ import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.ThreadPool.CancelListener;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
-import com.google.android.canvas.data.Cluster;
-import com.google.android.canvas.provider.CanvasContract;
+import com.google.android.pano.data.Cluster;
+import com.google.android.pano.provider.PanoContract;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -142,7 +142,7 @@ public class CanvasProvider extends CanvasProviderBase {
Cluster.Builder bob = new Cluster.Builder();
bob.id(i);
bob.displayName(set.getName());
- Intent intent = CanvasContract.getBrowseIntent(BROWSER_ROOT_URI, i);
+ Intent intent = PanoContract.getBrowseIntent(BROWSER_ROOT_URI, i);
bob.intent(intent);
bob.imageCropAllowed(true);
bob.cacheTimeMs(CACHE_TIME_MS);
diff --git a/src/com/android/photos/canvas/CanvasProviderBase.java b/src/com/android/photos/canvas/CanvasProviderBase.java
index 4438c5398..a38aae5ce 100644
--- a/src/com/android/photos/canvas/CanvasProviderBase.java
+++ b/src/com/android/photos/canvas/CanvasProviderBase.java
@@ -26,8 +26,8 @@ import android.net.Uri;
import android.os.Binder;
import android.provider.BaseColumns;
-import com.google.android.canvas.data.Cluster;
-import com.google.android.canvas.provider.CanvasContract;
+import com.google.android.pano.data.Cluster;
+import com.google.android.pano.provider.PanoContract;
import java.util.ArrayList;
import java.util.HashMap;
@@ -45,10 +45,10 @@ public abstract class CanvasProviderBase extends ContentProvider {
protected static final String PATH_IMAGE = "image";
protected static final String PATH_LAUNCHER = "launcher";
protected static final String PATH_LAUNCHER_ITEM = PATH_LAUNCHER + "/"
- + CanvasContract.PATH_LAUNCHER_ITEM;
+ + PanoContract.PATH_LAUNCHER_ITEM;
protected static final String PATH_BROWSE = "browse";
protected static final String PATH_BROWSE_HEADERS = PATH_BROWSE + "/"
- + CanvasContract.PATH_BROWSE_HEADERS;
+ + PanoContract.PATH_BROWSE_HEADERS;
public static final Uri BROWSER_ROOT_URI = Uri.parse("content://"
+ AUTHORITY + "/" + PATH_BROWSE);
@@ -91,19 +91,19 @@ public abstract class CanvasProviderBase extends ContentProvider {
static {
LAUNCHER_COLUMN_CASES.put(BaseColumns._ID, LAUNCHER_CASE_ID);
LAUNCHER_COLUMN_CASES.put(BaseColumns._COUNT, LAUNCHER_CASE_COUNT);
- LAUNCHER_COLUMN_CASES.put(CanvasContract.Launcher.NAME,
+ LAUNCHER_COLUMN_CASES.put(PanoContract.Launcher.NAME,
LAUNCHER_CASE_NAME);
- LAUNCHER_COLUMN_CASES.put(CanvasContract.Launcher.IMPORTANCE,
+ LAUNCHER_COLUMN_CASES.put(PanoContract.Launcher.IMPORTANCE,
LAUNCHER_CASE_IMPORTANCE);
- LAUNCHER_COLUMN_CASES.put(CanvasContract.Launcher.DISPLAY_NAME,
+ LAUNCHER_COLUMN_CASES.put(PanoContract.Launcher.DISPLAY_NAME,
LAUNCHER_CASE_DISPLAY_NAME);
- LAUNCHER_COLUMN_CASES.put(CanvasContract.Launcher.VISIBLE_COUNT,
+ LAUNCHER_COLUMN_CASES.put(PanoContract.Launcher.VISIBLE_COUNT,
LAUNCHER_CASE_VISIBLE_COUNT);
- LAUNCHER_COLUMN_CASES.put(CanvasContract.Launcher.IMAGE_CROP_ALLOWED,
+ LAUNCHER_COLUMN_CASES.put(PanoContract.Launcher.IMAGE_CROP_ALLOWED,
LAUNCHER_CASE_CROP_ALLOWED);
- LAUNCHER_COLUMN_CASES.put(CanvasContract.Launcher.CACHE_TIME_MS,
+ LAUNCHER_COLUMN_CASES.put(PanoContract.Launcher.CACHE_TIME_MS,
LAUNCHER_CASE_CACHE_TIME);
- LAUNCHER_COLUMN_CASES.put(CanvasContract.Launcher.INTENT_URI,
+ LAUNCHER_COLUMN_CASES.put(PanoContract.Launcher.INTENT_URI,
LAUNCHER_CASE_INTENT_URI);
LAUNCHER_PROJECTION_ALL = LAUNCHER_COLUMN_CASES.keySet().toArray(
@@ -120,9 +120,9 @@ public abstract class CanvasProviderBase extends ContentProvider {
static {
CLUSTER_COLUMN_CASES.put(BaseColumns._ID, CLUSTER_CASE_ID);
CLUSTER_COLUMN_CASES.put(BaseColumns._COUNT, CLUSTER_CASE_COUNT);
- CLUSTER_COLUMN_CASES.put(CanvasContract.LauncherItem.PARENT_ID,
+ CLUSTER_COLUMN_CASES.put(PanoContract.LauncherItem.PARENT_ID,
CLUSTER_CASE_PARENT_ID);
- CLUSTER_COLUMN_CASES.put(CanvasContract.LauncherItem.IMAGE_URI,
+ CLUSTER_COLUMN_CASES.put(PanoContract.LauncherItem.IMAGE_URI,
CLUSTER_CASE_IMAGE_URI);
CLUSTER_PROJECTION_ALL = CLUSTER_COLUMN_CASES.keySet().toArray(
@@ -149,33 +149,33 @@ public abstract class CanvasProviderBase extends ContentProvider {
BROWSE_HEADER_COLUMN_CASES.put(BaseColumns._ID, BROWSE_HEADER_CASE_ID);
BROWSE_HEADER_COLUMN_CASES.put(BaseColumns._COUNT,
BROWSE_HEADER_CASE_COUNT);
- BROWSE_HEADER_COLUMN_CASES.put(CanvasContract.BrowseHeaders.NAME,
+ BROWSE_HEADER_COLUMN_CASES.put(PanoContract.BrowseHeaders.NAME,
BROWSE_HEADER_CASE_NAME);
BROWSE_HEADER_COLUMN_CASES.put(
- CanvasContract.BrowseHeaders.DISPLAY_NAME,
+ PanoContract.BrowseHeaders.DISPLAY_NAME,
BROWSE_HEADER_CASE_DISPLAY_NAME);
- BROWSE_HEADER_COLUMN_CASES.put(CanvasContract.BrowseHeaders.ICON_URI,
+ BROWSE_HEADER_COLUMN_CASES.put(PanoContract.BrowseHeaders.ICON_URI,
BROWSE_HEADER_CASE_ICON_URI);
- BROWSE_HEADER_COLUMN_CASES.put(CanvasContract.BrowseHeaders.BADGE_URI,
+ BROWSE_HEADER_COLUMN_CASES.put(PanoContract.BrowseHeaders.BADGE_URI,
BROWSE_HEADER_CASE_BADGE_URI);
- BROWSE_HEADER_COLUMN_CASES.put(CanvasContract.BrowseHeaders.COLOR_HINT,
+ BROWSE_HEADER_COLUMN_CASES.put(PanoContract.BrowseHeaders.COLOR_HINT,
BROWSE_HEADER_CASE_COLOR_HINT);
BROWSE_HEADER_COLUMN_CASES.put(
- CanvasContract.BrowseHeaders.TEXT_COLOR_HINT,
+ PanoContract.BrowseHeaders.TEXT_COLOR_HINT,
BROWSE_HEADER_CASE_TEXT_COLOR_HINT);
BROWSE_HEADER_COLUMN_CASES.put(
- CanvasContract.BrowseHeaders.BG_IMAGE_URI,
+ PanoContract.BrowseHeaders.BG_IMAGE_URI,
BROWSE_HEADER_CASE_BG_IMAGE_URI);
BROWSE_HEADER_COLUMN_CASES.put(
- CanvasContract.BrowseHeaders.EXPAND_GROUP,
+ PanoContract.BrowseHeaders.EXPAND_GROUP,
BROWSE_HEADER_CASE_EXPAND_GROUP);
- BROWSE_HEADER_COLUMN_CASES.put(CanvasContract.BrowseHeaders.WRAP_ITEMS,
+ BROWSE_HEADER_COLUMN_CASES.put(PanoContract.BrowseHeaders.WRAP_ITEMS,
BROWSE_HEADER_CASE_WRAP);
BROWSE_HEADER_COLUMN_CASES.put(
- CanvasContract.BrowseHeaders.DEFAULT_ITEM_WIDTH,
+ PanoContract.BrowseHeaders.DEFAULT_ITEM_WIDTH,
BROWSE_HEADER_CASE_DEFAULT_ITEM_WIDTH);
BROWSE_HEADER_COLUMN_CASES.put(
- CanvasContract.BrowseHeaders.DEFAULT_ITEM_HEIGHT,
+ PanoContract.BrowseHeaders.DEFAULT_ITEM_HEIGHT,
BROWSE_HEADER_CASE_DEFAULT_ITEM_HEIGHT);
BROWSE_HEADER_PROJECTION_ALL = BROWSE_HEADER_COLUMN_CASES.keySet()
@@ -197,19 +197,19 @@ public abstract class CanvasProviderBase extends ContentProvider {
static {
BROWSE_COLUMN_CASES.put(BaseColumns._ID, BROWSE_CASE_ID);
BROWSE_COLUMN_CASES.put(BaseColumns._COUNT, BROWSE_CASE_COUNT);
- BROWSE_COLUMN_CASES.put(CanvasContract.BrowseItems.PARENT_ID,
+ BROWSE_COLUMN_CASES.put(PanoContract.BrowseItems.PARENT_ID,
BROWSE_CASE_PARENT_ID);
- BROWSE_COLUMN_CASES.put(CanvasContract.BrowseItems.DISPLAY_NAME,
+ BROWSE_COLUMN_CASES.put(PanoContract.BrowseItems.DISPLAY_NAME,
BROWSE_CASE_DISPLAY_NAME);
- BROWSE_COLUMN_CASES.put(CanvasContract.BrowseItems.DISPLAY_DESCRIPTION,
+ BROWSE_COLUMN_CASES.put(PanoContract.BrowseItems.DISPLAY_DESCRIPTION,
BROWSE_CASE_DISPLAY_DESCRIPTION);
- BROWSE_COLUMN_CASES.put(CanvasContract.BrowseItems.IMAGE_URI,
+ BROWSE_COLUMN_CASES.put(PanoContract.BrowseItems.IMAGE_URI,
BROWSE_CASE_IMAGE_URI);
- BROWSE_COLUMN_CASES.put(CanvasContract.BrowseItems.WIDTH,
+ BROWSE_COLUMN_CASES.put(PanoContract.BrowseItems.WIDTH,
BROWSE_CASE_WIDTH);
- BROWSE_COLUMN_CASES.put(CanvasContract.BrowseItems.HEIGHT,
+ BROWSE_COLUMN_CASES.put(PanoContract.BrowseItems.HEIGHT,
BROWSE_CASE_HEIGHT);
- BROWSE_COLUMN_CASES.put(CanvasContract.BrowseItems.INTENT_URI,
+ BROWSE_COLUMN_CASES.put(PanoContract.BrowseItems.INTENT_URI,
BROWSE_CASE_INTENT_URI);
BROWSE_PROJECTION_ALL = BROWSE_COLUMN_CASES.keySet().toArray(
diff --git a/src/com/android/photos/data/AlbumSetLoader.java b/src/com/android/photos/data/AlbumSetLoader.java
new file mode 100644
index 000000000..b2b5204e6
--- /dev/null
+++ b/src/com/android/photos/data/AlbumSetLoader.java
@@ -0,0 +1,51 @@
+package com.android.photos.data;
+
+import android.database.MatrixCursor;
+
+
+public class AlbumSetLoader {
+ public static final int INDEX_ID = 0;
+ public static final int INDEX_TITLE = 1;
+ public static final int INDEX_TIMESTAMP = 2;
+ public static final int INDEX_THUMBNAIL_URI = 3;
+ public static final int INDEX_THUMBNAIL_WIDTH = 4;
+ public static final int INDEX_THUMBNAIL_HEIGHT = 5;
+ public static final int INDEX_COUNT_PENDING_UPLOAD = 6;
+ public static final int INDEX_COUNT = 7;
+
+ public static final String[] PROJECTION = {
+ "_id",
+ "title",
+ "timestamp",
+ "thumb_uri",
+ "thumb_width",
+ "thumb_height",
+ "count_pending_upload",
+ "_count"
+ };
+ public static final MatrixCursor MOCK = createRandomCursor(30);
+
+ private static MatrixCursor createRandomCursor(int count) {
+ MatrixCursor c = new MatrixCursor(PROJECTION, count);
+ for (int i = 0; i < count; i++) {
+ c.addRow(createRandomRow());
+ }
+ return c;
+ }
+
+ private static Object[] createRandomRow() {
+ double random = Math.random();
+ int id = (int) (500 * random);
+ Object[] row = {
+ id,
+ "Fun times " + id,
+ (long) (System.currentTimeMillis() * random),
+ null,
+ 0,
+ 0,
+ (random < .3 ? 1 : 0),
+ 1
+ };
+ return row;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/photos/data/GalleryBitmapPool.java b/src/com/android/photos/data/GalleryBitmapPool.java
index cddc160fd..4c3279f73 100644
--- a/src/com/android/photos/data/GalleryBitmapPool.java
+++ b/src/com/android/photos/data/GalleryBitmapPool.java
@@ -46,11 +46,9 @@ public class GalleryBitmapPool {
mCapacityBytes = capacityBytes;
}
- private static GalleryBitmapPool sInstance;
+ private static GalleryBitmapPool sInstance = new GalleryBitmapPool(CAPACITY_BYTES);
+
public static GalleryBitmapPool getInstance() {
- if (sInstance == null) {
- sInstance = new GalleryBitmapPool(CAPACITY_BYTES);
- }
return sInstance;
}
diff --git a/src/com/android/photos/data/NotificationWatcher.java b/src/com/android/photos/data/NotificationWatcher.java
new file mode 100644
index 000000000..9041c236f
--- /dev/null
+++ b/src/com/android/photos/data/NotificationWatcher.java
@@ -0,0 +1,55 @@
+/*
+ * 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.photos.data;
+
+import android.net.Uri;
+
+import com.android.photos.data.PhotoProvider.ChangeNotification;
+
+import java.util.ArrayList;
+
+/**
+ * Used for capturing notifications from PhotoProvider without relying on
+ * ContentResolver. MockContentResolver does not allow sending notification to
+ * ContentObservers, so PhotoProvider allows this alternative for testing.
+ */
+public class NotificationWatcher implements ChangeNotification {
+ private ArrayList<Uri> mUris = new ArrayList<Uri>();
+ private boolean mSyncToNetwork = false;
+
+ @Override
+ public void notifyChange(Uri uri, boolean syncToNetwork) {
+ mUris.add(uri);
+ mSyncToNetwork = mSyncToNetwork || syncToNetwork;
+ }
+
+ public boolean isNotified(Uri uri) {
+ return mUris.contains(uri);
+ }
+
+ public int notificationCount() {
+ return mUris.size();
+ }
+
+ public boolean syncToNetwork() {
+ return mSyncToNetwork;
+ }
+
+ public void reset() {
+ mUris.clear();
+ mSyncToNetwork = false;
+ }
+}
diff --git a/src/com/android/photos/data/PhotoDatabase.java b/src/com/android/photos/data/PhotoDatabase.java
new file mode 100644
index 000000000..a87f00bfa
--- /dev/null
+++ b/src/com/android/photos/data/PhotoDatabase.java
@@ -0,0 +1,166 @@
+/*
+ * 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.photos.data;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import com.android.photos.data.PhotoProvider.Accounts;
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used in PhotoProvider to create and access the database containing
+ * information about photo and video information stored on the server.
+ */
+public class PhotoDatabase extends SQLiteOpenHelper {
+ @SuppressWarnings("unused")
+ private static final String TAG = PhotoDatabase.class.getSimpleName();
+ static final int DB_VERSION = 1;
+
+ private static final String SQL_CREATE_TABLE = "CREATE TABLE ";
+
+ private static final String[][] CREATE_PHOTO = {
+ { Photos._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+ // Photos.ACCOUNT_ID is a foreign key to Accounts._ID
+ { Photos.ACCOUNT_ID, "INTEGER NOT NULL" },
+ { Photos.WIDTH, "INTEGER NOT NULL" },
+ { Photos.HEIGHT, "INTEGER NOT NULL" },
+ { Photos.DATE_TAKEN, "INTEGER NOT NULL" },
+ // Photos.ALBUM_ID is a foreign key to Albums._ID
+ { Photos.ALBUM_ID, "INTEGER" },
+ { Photos.MIME_TYPE, "TEXT NOT NULL" },
+ { Photos.TITLE, "TEXT" },
+ { Photos.DATE_MODIFIED, "INTEGER" },
+ { Photos.ROTATION, "INTEGER" },
+ };
+
+ private static final String[][] CREATE_ALBUM = {
+ { Albums._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+ // Albums.ACCOUNT_ID is a foreign key to Accounts._ID
+ { Albums.ACCOUNT_ID, "INTEGER NOT NULL" },
+ // Albums.PARENT_ID is a foreign key to Albums._ID
+ { Albums.PARENT_ID, "INTEGER" },
+ { Albums.VISIBILITY, "INTEGER NOT NULL" },
+ { Albums.LOCATION_STRING, "TEXT" },
+ { Albums.TITLE, "TEXT NOT NULL" },
+ { Albums.SUMMARY, "TEXT" },
+ { Albums.DATE_PUBLISHED, "INTEGER" },
+ { Albums.DATE_MODIFIED, "INTEGER" },
+ createUniqueConstraint(Albums.PARENT_ID, Albums.TITLE),
+ };
+
+ private static final String[][] CREATE_METADATA = {
+ { Metadata._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+ // Metadata.PHOTO_ID is a foreign key to Photos._ID
+ { Metadata.PHOTO_ID, "INTEGER NOT NULL" },
+ { Metadata.KEY, "TEXT NOT NULL" },
+ { Metadata.VALUE, "TEXT NOT NULL" },
+ createUniqueConstraint(Metadata.PHOTO_ID, Metadata.KEY),
+ };
+
+ private static final String[][] CREATE_ACCOUNT = {
+ { Accounts._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+ { Accounts.ACCOUNT_NAME, "TEXT NOT NULL" },
+ };
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ createTable(db, Accounts.TABLE, getAccountTableDefinition());
+ createTable(db, Albums.TABLE, getAlbumTableDefinition());
+ createTable(db, Photos.TABLE, getPhotoTableDefinition());
+ createTable(db, Metadata.TABLE, getMetadataTableDefinition());
+ }
+
+ public PhotoDatabase(Context context, String dbName) {
+ super(context, dbName, null, DB_VERSION);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ }
+
+ protected List<String[]> getAlbumTableDefinition() {
+ return tableCreationStrings(CREATE_ALBUM);
+ }
+
+ protected List<String[]> getPhotoTableDefinition() {
+ return tableCreationStrings(CREATE_PHOTO);
+ }
+
+ protected List<String[]> getMetadataTableDefinition() {
+ return tableCreationStrings(CREATE_METADATA);
+ }
+
+ protected List<String[]> getAccountTableDefinition() {
+ return tableCreationStrings(CREATE_ACCOUNT);
+ }
+
+ protected static void createTable(SQLiteDatabase db, String table, List<String[]> columns) {
+ StringBuilder create = new StringBuilder(SQL_CREATE_TABLE);
+ create.append(table).append('(');
+ boolean first = true;
+ for (String[] column : columns) {
+ if (!first) {
+ create.append(',');
+ }
+ first = false;
+ for (String val: column) {
+ create.append(val).append(' ');
+ }
+ }
+ create.append(')');
+ db.beginTransaction();
+ try {
+ db.execSQL(create.toString());
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ protected static String[] createUniqueConstraint(String column1, String column2) {
+ return new String[] {
+ "UNIQUE(", column1, ",", column2, ")"
+ };
+ }
+
+ protected static List<String[]> tableCreationStrings(String[][] createTable) {
+ ArrayList<String[]> create = new ArrayList<String[]>(createTable.length);
+ for (String[] line: createTable) {
+ create.add(line);
+ }
+ return create;
+ }
+
+ protected static void addToTable(List<String[]> createTable, String[][] columns, String[][] constraints) {
+ if (columns != null) {
+ for (String[] column: columns) {
+ createTable.add(0, column);
+ }
+ }
+ if (constraints != null) {
+ for (String[] constraint: constraints) {
+ createTable.add(constraint);
+ }
+ }
+ }
+}
diff --git a/src/com/android/photos/data/PhotoProvider.java b/src/com/android/photos/data/PhotoProvider.java
new file mode 100644
index 000000000..cecfe5ea4
--- /dev/null
+++ b/src/com/android/photos/data/PhotoProvider.java
@@ -0,0 +1,543 @@
+/*
+ * 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.photos.data;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.provider.BaseColumns;
+
+import java.util.List;
+
+/**
+ * A provider that gives access to photo and video information for media stored
+ * on the server. Only media that is or will be put on the server will be
+ * accessed by this provider. Use Photos.CONTENT_URI to query all photos and
+ * videos. Use Albums.CONTENT_URI to query all albums. Use Metadata.CONTENT_URI
+ * to query metadata about a photo or video, based on the ID of the media. Use
+ * ImageCache.THUMBNAIL_CONTENT_URI, ImageCache.PREVIEW_CONTENT_URI, or
+ * ImageCache.ORIGINAL_CONTENT_URI to query the path of the thumbnail, preview,
+ * or original-sized image respectfully. <br/>
+ * To add or update metadata, use the update function rather than insert. All
+ * values for the metadata must be in the ContentValues, even if they are also
+ * in the selection. The selection and selectionArgs are not used when updating
+ * metadata. If the metadata values are null, the row will be deleted.
+ */
+public class PhotoProvider extends SQLiteContentProvider {
+ @SuppressWarnings("unused")
+ private static final String TAG = PhotoProvider.class.getSimpleName();
+
+ protected static final String DB_NAME = "photo.db";
+ public static final String AUTHORITY = PhotoProviderAuthority.AUTHORITY;
+ static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme("content").authority(AUTHORITY)
+ .build();
+
+ // Used to allow mocking out the change notification because
+ // MockContextResolver disallows system-wide notification.
+ public static interface ChangeNotification {
+ void notifyChange(Uri uri, boolean syncToNetwork);
+ }
+
+ /**
+ * Contains columns that can be accessed via Accounts.CONTENT_URI
+ */
+ public static interface Accounts extends BaseColumns {
+ /**
+ * Internal database table used for account information
+ */
+ public static final String TABLE = "accounts";
+ /**
+ * Content URI for account information
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+ /**
+ * User name for this account.
+ */
+ public static final String ACCOUNT_NAME = "name";
+ }
+
+ /**
+ * Contains columns that can be accessed via Photos.CONTENT_URI.
+ */
+ public static interface Photos extends BaseColumns {
+ /** Internal database table used for basic photo information. */
+ public static final String TABLE = "photo";
+ /** Content URI for basic photo and video information. */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+
+ /** Long foreign key to Accounts._ID */
+ public static final String ACCOUNT_ID = "account_id";
+ /** Column name for the width of the original image. Integer value. */
+ public static final String WIDTH = "width";
+ /** Column name for the height of the original image. Integer value. */
+ public static final String HEIGHT = "height";
+ /**
+ * Column name for the date that the original image was taken. Long
+ * value indicating the milliseconds since epoch in the GMT time zone.
+ */
+ public static final String DATE_TAKEN = "date_taken";
+ /**
+ * Column name indicating the long value of the album id that this image
+ * resides in. Will be NULL if it it has not been uploaded to the
+ * server.
+ */
+ public static final String ALBUM_ID = "album_id";
+ /** The column name for the mime-type String. */
+ public static final String MIME_TYPE = "mime_type";
+ /** The title of the photo. String value. */
+ public static final String TITLE = "title";
+ /** The date the photo entry was last updated. Long value. */
+ public static final String DATE_MODIFIED = "date_modified";
+ /**
+ * The rotation of the photo in degrees, if rotation has not already
+ * been applied. Integer value.
+ */
+ public static final String ROTATION = "rotation";
+ }
+
+ /**
+ * Contains columns and Uri for accessing album information.
+ */
+ public static interface Albums extends BaseColumns {
+ /** Internal database table used album information. */
+ public static final String TABLE = "album";
+ /** Content URI for album information. */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+
+ /** Long foreign key to Accounts._ID */
+ public static final String ACCOUNT_ID = "account_id";
+ /** Parent directory or null if this is in the root. */
+ public static final String PARENT_ID = "parent_id";
+ /**
+ * Column name for the visibility level of the album. Can be any of the
+ * VISIBILITY_* values.
+ */
+ public static final String VISIBILITY = "visibility";
+ /** The user-specified location associated with the album. String value. */
+ public static final String LOCATION_STRING = "location_string";
+ /** The title of the album. String value. */
+ public static final String TITLE = "title";
+ /** A short summary of the contents of the album. String value. */
+ public static final String SUMMARY = "summary";
+ /** The date the album was created. Long value */
+ public static final String DATE_PUBLISHED = "date_published";
+ /** The date the album entry was last updated. Long value. */
+ public static final String DATE_MODIFIED = "date_modified";
+
+ // Privacy values for Albums.VISIBILITY
+ public static final int VISIBILITY_PRIVATE = 1;
+ public static final int VISIBILITY_SHARED = 2;
+ public static final int VISIBILITY_PUBLIC = 3;
+ }
+
+ /**
+ * Contains columns and Uri for accessing photo and video metadata
+ */
+ public static interface Metadata extends BaseColumns {
+ /** Internal database table used metadata information. */
+ public static final String TABLE = "metadata";
+ /** Content URI for photo and video metadata. */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+ /** Foreign key to photo_id. Long value. */
+ public static final String PHOTO_ID = "photo_id";
+ /** Metadata key. String value */
+ public static final String KEY = "key";
+ /**
+ * Metadata value. Type is based on key.
+ */
+ public static final String VALUE = "value";
+
+ /** A short summary of the photo. String value. */
+ public static final String KEY_SUMMARY = "summary";
+ /** The date the photo was added. Long value. */
+ public static final String KEY_PUBLISHED = "date_published";
+ /** The date the photo was last updated. Long value. */
+ public static final String KEY_DATE_UPDATED = "date_updated";
+ /** The size of the photo is bytes. Integer value. */
+ public static final String KEY_SIZE_IN_BTYES = "size";
+ /** The latitude associated with the photo. Double value. */
+ public static final String KEY_LATITUDE = "latitude";
+ /** The longitude associated with the photo. Double value. */
+ public static final String KEY_LONGITUDE = "longitude";
+
+ /** The make of the camera used. String value. */
+ public static final String KEY_EXIF_MAKE = ExifInterface.TAG_MAKE;
+ /** The model of the camera used. String value. */
+ public static final String KEY_EXIF_MODEL = ExifInterface.TAG_MODEL;;
+ /** The exposure time used. Float value. */
+ public static final String KEY_EXIF_EXPOSURE = ExifInterface.TAG_EXPOSURE_TIME;
+ /** Whether the flash was used. Boolean value. */
+ public static final String KEY_EXIF_FLASH = ExifInterface.TAG_FLASH;
+ /** The focal length used. Float value. */
+ public static final String KEY_EXIF_FOCAL_LENGTH = ExifInterface.TAG_FOCAL_LENGTH;
+ /** The fstop value used. Float value. */
+ public static final String KEY_EXIF_FSTOP = ExifInterface.TAG_APERTURE;
+ /** The ISO equivalent value used. Integer value. */
+ public static final String KEY_EXIF_ISO = ExifInterface.TAG_ISO;
+ }
+
+ /**
+ * Contains columns and Uri for maintaining the image cache.
+ */
+ public static interface ImageCache extends BaseColumns {
+ /** Internal database table used for the image cache */
+ public static final String TABLE = "image_cache";
+
+ /**
+ * The image_type query parameter required for accessing a specific
+ * image
+ */
+ public static final String IMAGE_TYPE_QUERY_PARAMETER = "image_type";
+
+ // ImageCache.IMAGE_TYPE values
+ public static final int IMAGE_TYPE_ALBUM_COVER = 1;
+ public static final int IMAGE_TYPE_THUMBNAIL = 2;
+ public static final int IMAGE_TYPE_PREVIEW = 3;
+ public static final int IMAGE_TYPE_ORIGINAL = 4;
+
+ /**
+ * Content URI for retrieving image paths. The
+ * IMAGE_TYPE_QUERY_PARAMETER must be used in queries.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+
+ /**
+ * Content URI for retrieving the album cover art. The album ID must be
+ * appended to the URI.
+ */
+ public static final Uri ALBUM_COVER_CONTENT_URI = Uri.withAppendedPath(CONTENT_URI,
+ Albums.TABLE);
+
+ /**
+ * An _ID from Albums or Photos, depending on whether IMAGE_TYPE is
+ * IMAGE_TYPE_ALBUM or not. Long value.
+ */
+ public static final String REMOTE_ID = "remote_id";
+ /** One of IMAGE_TYPE_* values. */
+ public static final String IMAGE_TYPE = "image_type";
+ /** The String path to the image. */
+ public static final String PATH = "path";
+ };
+
+ // SQL used within this class.
+ protected static final String WHERE_ID = BaseColumns._ID + " = ?";
+ protected static final String WHERE_METADATA_ID = Metadata.PHOTO_ID + " = ? AND "
+ + Metadata.KEY + " = ?";
+
+ protected static final String SELECT_ALBUM_ID = "SELECT " + Albums._ID + " FROM "
+ + Albums.TABLE;
+ protected static final String SELECT_PHOTO_ID = "SELECT " + Photos._ID + " FROM "
+ + Photos.TABLE;
+ protected static final String SELECT_PHOTO_COUNT = "SELECT COUNT(*) FROM " + Photos.TABLE;
+ protected static final String DELETE_PHOTOS = "DELETE FROM " + Photos.TABLE;
+ protected static final String DELETE_METADATA = "DELETE FROM " + Metadata.TABLE;
+ protected static final String SELECT_METADATA_COUNT = "SELECT COUNT(*) FROM " + Metadata.TABLE;
+ protected static final String WHERE = " WHERE ";
+ protected static final String IN = " IN ";
+ protected static final String NESTED_SELECT_START = "(";
+ protected static final String NESTED_SELECT_END = ")";
+
+ /**
+ * For selecting the mime-type for an image.
+ */
+ private static final String[] PROJECTION_MIME_TYPE = {
+ Photos.MIME_TYPE,
+ };
+
+ private static final String[] BASE_COLUMNS_ID = {
+ BaseColumns._ID,
+ };
+
+ protected ChangeNotification mNotifier = null;
+ protected static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ protected static final int MATCH_PHOTO = 1;
+ protected static final int MATCH_PHOTO_ID = 2;
+ protected static final int MATCH_ALBUM = 3;
+ protected static final int MATCH_ALBUM_ID = 4;
+ protected static final int MATCH_METADATA = 5;
+ protected static final int MATCH_METADATA_ID = 6;
+ protected static final int MATCH_IMAGE = 7;
+ protected static final int MATCH_ALBUM_COVER = 8;
+
+ static {
+ sUriMatcher.addURI(AUTHORITY, Photos.TABLE, MATCH_PHOTO);
+ // match against Photos._ID
+ sUriMatcher.addURI(AUTHORITY, Photos.TABLE + "/#", MATCH_PHOTO_ID);
+ sUriMatcher.addURI(AUTHORITY, Albums.TABLE, MATCH_ALBUM);
+ // match against Albums._ID
+ sUriMatcher.addURI(AUTHORITY, Albums.TABLE + "/#", MATCH_ALBUM_ID);
+ sUriMatcher.addURI(AUTHORITY, Metadata.TABLE, MATCH_METADATA);
+ // match against metadata/<Metadata._ID>
+ sUriMatcher.addURI(AUTHORITY, Metadata.TABLE + "/#", MATCH_METADATA_ID);
+ // match against image_cache/<ImageCache.PHOTO_ID>
+ sUriMatcher.addURI(AUTHORITY, ImageCache.TABLE + "/#", MATCH_IMAGE);
+ // match against image_cache/album/<Albums._ID>
+ sUriMatcher.addURI(AUTHORITY, ImageCache.TABLE + "/" + Albums.TABLE + "/#",
+ MATCH_ALBUM_COVER);
+ }
+
+ @Override
+ public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+ boolean callerIsSyncAdapter) {
+ int match = matchUri(uri);
+ selection = addIdToSelection(match, selection);
+ selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
+ int deleted = 0;
+ SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+ deleted = deleteCascade(db, match, selection, selectionArgs, uri);
+ return deleted;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ Cursor cursor = query(uri, PROJECTION_MIME_TYPE, null, null, null);
+ String mimeType = null;
+ if (cursor.moveToNext()) {
+ mimeType = cursor.getString(0);
+ }
+ cursor.close();
+ return mimeType;
+ }
+
+ @Override
+ public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
+ int match = matchUri(uri);
+ validateMatchTable(match);
+ String table = getTableFromMatch(match, uri);
+ SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+ Uri insertedUri = null;
+ long id = db.insert(table, null, values);
+ if (id != -1) {
+ // uri already matches the table.
+ insertedUri = ContentUris.withAppendedId(uri, id);
+ postNotifyUri(insertedUri);
+ }
+ return insertedUri;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return query(uri, projection, selection, selectionArgs, sortOrder, null);
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder, CancellationSignal cancellationSignal) {
+ int match = matchUri(uri);
+ selection = addIdToSelection(match, selection);
+ selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
+ String table = getTableFromMatch(match, uri);
+ SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
+ return db.query(false, table, projection, selection, selectionArgs, null, null, sortOrder,
+ null, cancellationSignal);
+ }
+
+ @Override
+ public int updateInTransaction(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter) {
+ int match = matchUri(uri);
+ int rowsUpdated = 0;
+ SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+ if (match == MATCH_METADATA) {
+ rowsUpdated = modifyMetadata(db, values);
+ } else {
+ selection = addIdToSelection(match, selection);
+ selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
+ String table = getTableFromMatch(match, uri);
+ rowsUpdated = db.update(table, values, selection, selectionArgs);
+ }
+ postNotifyUri(uri);
+ return rowsUpdated;
+ }
+
+ public void setMockNotification(ChangeNotification notification) {
+ mNotifier = notification;
+ }
+
+ protected static String addIdToSelection(int match, String selection) {
+ String where;
+ switch (match) {
+ case MATCH_PHOTO_ID:
+ case MATCH_ALBUM_ID:
+ case MATCH_METADATA_ID:
+ where = WHERE_ID;
+ break;
+ default:
+ return selection;
+ }
+ return DatabaseUtils.concatenateWhere(selection, where);
+ }
+
+ protected static String[] addIdToSelectionArgs(int match, Uri uri, String[] selectionArgs) {
+ String[] whereArgs;
+ switch (match) {
+ case MATCH_PHOTO_ID:
+ case MATCH_ALBUM_ID:
+ case MATCH_METADATA_ID:
+ whereArgs = new String[] {
+ uri.getPathSegments().get(1),
+ };
+ break;
+ default:
+ return selectionArgs;
+ }
+ return DatabaseUtils.appendSelectionArgs(selectionArgs, whereArgs);
+ }
+
+ protected static String[] addMetadataKeysToSelectionArgs(String[] selectionArgs, Uri uri) {
+ List<String> segments = uri.getPathSegments();
+ String[] additionalArgs = {
+ segments.get(1),
+ segments.get(2),
+ };
+
+ return DatabaseUtils.appendSelectionArgs(selectionArgs, additionalArgs);
+ }
+
+ protected static String getTableFromMatch(int match, Uri uri) {
+ String table;
+ switch (match) {
+ case MATCH_PHOTO:
+ case MATCH_PHOTO_ID:
+ table = Photos.TABLE;
+ break;
+ case MATCH_ALBUM:
+ case MATCH_ALBUM_ID:
+ table = Albums.TABLE;
+ break;
+ case MATCH_METADATA:
+ case MATCH_METADATA_ID:
+ table = Metadata.TABLE;
+ break;
+ default:
+ throw unknownUri(uri);
+ }
+ return table;
+ }
+
+ @Override
+ public SQLiteOpenHelper getDatabaseHelper(Context context) {
+ return new PhotoDatabase(context, DB_NAME);
+ }
+
+ private int modifyMetadata(SQLiteDatabase db, ContentValues values) {
+ int rowCount;
+ if (values.get(Metadata.VALUE) == null) {
+ String[] selectionArgs = {
+ values.getAsString(Metadata.PHOTO_ID), values.getAsString(Metadata.KEY),
+ };
+ rowCount = db.delete(Metadata.TABLE, WHERE_METADATA_ID, selectionArgs);
+ } else {
+ long rowId = db.replace(Metadata.TABLE, null, values);
+ rowCount = (rowId == -1) ? 0 : 1;
+ }
+ return rowCount;
+ }
+
+ private int matchUri(Uri uri) {
+ int match = sUriMatcher.match(uri);
+ if (match == UriMatcher.NO_MATCH) {
+ throw unknownUri(uri);
+ }
+ if (match == MATCH_IMAGE || match == MATCH_ALBUM_COVER) {
+ throw new IllegalArgumentException("Operation not allowed on image cache database");
+ }
+ return match;
+ }
+
+ @Override
+ protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) {
+ if (mNotifier != null) {
+ mNotifier.notifyChange(uri, syncToNetwork);
+ } else {
+ resolver.notifyChange(uri, null, syncToNetwork);
+ }
+ }
+
+ protected static IllegalArgumentException unknownUri(Uri uri) {
+ return new IllegalArgumentException("Unknown Uri format: " + uri);
+ }
+
+ protected static String nestWhere(String matchColumn, String table, String nestedWhere) {
+ String query = SQLiteQueryBuilder.buildQueryString(false, table, BASE_COLUMNS_ID,
+ nestedWhere, null, null, null, null);
+ return matchColumn + IN + NESTED_SELECT_START + query + NESTED_SELECT_END;
+ }
+
+ protected int deleteCascade(SQLiteDatabase db, int match, String selection,
+ String[] selectionArgs, Uri uri) {
+ switch (match) {
+ case MATCH_PHOTO:
+ case MATCH_PHOTO_ID: {
+ deleteCascadeMetadata(db, selection, selectionArgs);
+ break;
+ }
+ case MATCH_ALBUM:
+ case MATCH_ALBUM_ID: {
+ deleteCascadePhotos(db, selection, selectionArgs);
+ break;
+ }
+ }
+ String table = getTableFromMatch(match, uri);
+ int deleted = db.delete(table, selection, selectionArgs);
+ if (deleted > 0) {
+ postNotifyUri(uri);
+ }
+ return deleted;
+ }
+
+ private void deleteCascadePhotos(SQLiteDatabase db, String albumSelect,
+ String[] selectArgs) {
+ String photoWhere = nestWhere(Photos.ALBUM_ID, Albums.TABLE, albumSelect);
+ deleteCascadeMetadata(db, photoWhere, selectArgs);
+ int deleted = db.delete(Photos.TABLE, photoWhere, selectArgs);
+ if (deleted > 0) {
+ postNotifyUri(Photos.CONTENT_URI);
+ }
+ }
+
+ private void deleteCascadeMetadata(SQLiteDatabase db, String photosSelect,
+ String[] selectArgs) {
+ String metadataWhere = nestWhere(Metadata.PHOTO_ID, Photos.TABLE, photosSelect);
+ int deleted = db.delete(Metadata.TABLE, metadataWhere, selectArgs);
+ if (deleted > 0) {
+ postNotifyUri(Metadata.CONTENT_URI);
+ }
+ }
+
+ private static void validateMatchTable(int match) {
+ switch (match) {
+ case MATCH_PHOTO:
+ case MATCH_ALBUM:
+ case MATCH_METADATA:
+ break;
+ default:
+ throw new IllegalArgumentException("Operation not allowed on an existing row.");
+ }
+ }
+}
diff --git a/src/com/android/photos/data/PhotoSetLoader.java b/src/com/android/photos/data/PhotoSetLoader.java
new file mode 100644
index 000000000..21da90694
--- /dev/null
+++ b/src/com/android/photos/data/PhotoSetLoader.java
@@ -0,0 +1,88 @@
+/*
+ * 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.photos.data;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Files.FileColumns;
+
+import com.android.photos.drawables.DataUriThumbnailDrawable;
+import com.android.photos.drawables.DrawableFactory;
+
+public class PhotoSetLoader extends CursorLoader implements DrawableFactory<Cursor> {
+
+ private static final Uri CONTENT_URI = Files.getContentUri("external");
+ public static final String[] PROJECTION = new String[] {
+ FileColumns._ID,
+ FileColumns.DATA,
+ FileColumns.WIDTH,
+ FileColumns.HEIGHT,
+ FileColumns.DATE_ADDED,
+ FileColumns.MEDIA_TYPE,
+ };
+ private static final String SORT_ORDER = FileColumns.DATE_ADDED + " DESC";
+ private static final String SELECTION =
+ FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_IMAGE
+ + " OR "
+ + FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_VIDEO;
+
+ public static final int INDEX_ID = 0;
+ public static final int INDEX_DATA = 1;
+ public static final int INDEX_WIDTH = 2;
+ public static final int INDEX_HEIGHT = 3;
+ public static final int INDEX_DATE_ADDED = 4;
+ public static final int INDEX_MEDIA_TYPE = 5;
+
+ private static final Uri GLOBAL_CONTENT_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/external/");
+ private final ContentObserver mGlobalObserver = new ForceLoadContentObserver();
+
+ public PhotoSetLoader(Context context) {
+ super(context, CONTENT_URI, PROJECTION, SELECTION, null, SORT_ORDER);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ getContext().getContentResolver().registerContentObserver(GLOBAL_CONTENT_URI,
+ true, mGlobalObserver);
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ getContext().getContentResolver().unregisterContentObserver(mGlobalObserver);
+ }
+
+ @Override
+ public Drawable drawableForItem(Cursor item, Drawable recycle) {
+ DataUriThumbnailDrawable drawable = null;
+ if (recycle == null || !(recycle instanceof DataUriThumbnailDrawable)) {
+ drawable = new DataUriThumbnailDrawable();
+ } else {
+ drawable = (DataUriThumbnailDrawable) recycle;
+ }
+ drawable.setImage(item.getString(INDEX_DATA),
+ item.getInt(INDEX_WIDTH), item.getInt(INDEX_HEIGHT));
+ return drawable;
+ }
+}
diff --git a/src/com/android/photos/data/SQLiteContentProvider.java b/src/com/android/photos/data/SQLiteContentProvider.java
new file mode 100644
index 000000000..ecd868b52
--- /dev/null
+++ b/src/com/android/photos/data/SQLiteContentProvider.java
@@ -0,0 +1,264 @@
+/*
+ * 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.photos.data;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * General purpose {@link ContentProvider} base class that uses SQLiteDatabase
+ * for storage.
+ */
+public abstract class SQLiteContentProvider extends ContentProvider {
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "SQLiteContentProvider";
+
+ private SQLiteOpenHelper mOpenHelper;
+ private Set<Uri> mChangedUris;
+
+ private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
+ private static final int SLEEP_AFTER_YIELD_DELAY = 4000;
+
+ /**
+ * Maximum number of operations allowed in a batch between yield points.
+ */
+ private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500;
+
+ @Override
+ public boolean onCreate() {
+ Context context = getContext();
+ mOpenHelper = getDatabaseHelper(context);
+ mChangedUris = new HashSet<Uri>();
+ return true;
+ }
+
+ @Override
+ public void shutdown() {
+ getDatabaseHelper().close();
+ }
+
+ /**
+ * Returns a {@link SQLiteOpenHelper} that can open the database.
+ */
+ public abstract SQLiteOpenHelper getDatabaseHelper(Context context);
+
+ /**
+ * The equivalent of the {@link #insert} method, but invoked within a
+ * transaction.
+ */
+ public abstract Uri insertInTransaction(Uri uri, ContentValues values,
+ boolean callerIsSyncAdapter);
+
+ /**
+ * The equivalent of the {@link #update} method, but invoked within a
+ * transaction.
+ */
+ public abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs, boolean callerIsSyncAdapter);
+
+ /**
+ * The equivalent of the {@link #delete} method, but invoked within a
+ * transaction.
+ */
+ public abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+ boolean callerIsSyncAdapter);
+
+ /**
+ * Call this to add a URI to the list of URIs to be notified when the
+ * transaction is committed.
+ */
+ protected void postNotifyUri(Uri uri) {
+ synchronized (mChangedUris) {
+ mChangedUris.add(uri);
+ }
+ }
+
+ public boolean isCallerSyncAdapter(Uri uri) {
+ return false;
+ }
+
+ public SQLiteOpenHelper getDatabaseHelper() {
+ return mOpenHelper;
+ }
+
+ private boolean applyingBatch() {
+ return mApplyingBatch.get() != null && mApplyingBatch.get();
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ Uri result = null;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ result = insertInTransaction(uri, values, callerIsSyncAdapter);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ result = insertInTransaction(uri, values, callerIsSyncAdapter);
+ }
+ return result;
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ int numValues = values.length;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ for (int i = 0; i < numValues; i++) {
+ @SuppressWarnings("unused")
+ Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter);
+ db.yieldIfContendedSafely();
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ return numValues;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ int count = 0;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ count = updateInTransaction(uri, values, selection, selectionArgs,
+ callerIsSyncAdapter);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter);
+ }
+
+ return count;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ int count = 0;
+ boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+ boolean applyingBatch = applyingBatch();
+ if (!applyingBatch) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ onEndTransaction(callerIsSyncAdapter);
+ } else {
+ count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+ }
+ return count;
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ throws OperationApplicationException {
+ int ypCount = 0;
+ int opCount = 0;
+ boolean callerIsSyncAdapter = false;
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ mApplyingBatch.set(true);
+ final int numOperations = operations.size();
+ final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+ for (int i = 0; i < numOperations; i++) {
+ if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
+ throw new OperationApplicationException(
+ "Too many content provider operations between yield points. "
+ + "The maximum number of operations per yield point is "
+ + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
+ }
+ final ContentProviderOperation operation = operations.get(i);
+ if (!callerIsSyncAdapter && isCallerSyncAdapter(operation.getUri())) {
+ callerIsSyncAdapter = true;
+ }
+ if (i > 0 && operation.isYieldAllowed()) {
+ opCount = 0;
+ if (db.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) {
+ ypCount++;
+ }
+ }
+ results[i] = operation.apply(this, results, i);
+ }
+ db.setTransactionSuccessful();
+ return results;
+ } finally {
+ mApplyingBatch.set(false);
+ db.endTransaction();
+ onEndTransaction(callerIsSyncAdapter);
+ }
+ }
+
+ protected void onEndTransaction(boolean callerIsSyncAdapter) {
+ Set<Uri> changed;
+ synchronized (mChangedUris) {
+ changed = new HashSet<Uri>(mChangedUris);
+ mChangedUris.clear();
+ }
+ ContentResolver resolver = getContext().getContentResolver();
+ for (Uri uri : changed) {
+ boolean syncToNetwork = !callerIsSyncAdapter && syncToNetwork(uri);
+ notifyChange(resolver, uri, syncToNetwork);
+ }
+ }
+
+ protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) {
+ resolver.notifyChange(uri, null, syncToNetwork);
+ }
+
+ protected boolean syncToNetwork(Uri uri) {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/photos/drawables/AutoThumbnailDrawable.java b/src/com/android/photos/drawables/AutoThumbnailDrawable.java
new file mode 100644
index 000000000..b51b6709f
--- /dev/null
+++ b/src/com/android/photos/drawables/AutoThumbnailDrawable.java
@@ -0,0 +1,309 @@
+/*
+ * 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.photos.drawables;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import com.android.photos.data.GalleryBitmapPool;
+
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public abstract class AutoThumbnailDrawable<T> extends Drawable {
+
+ private static final String TAG = "AutoThumbnailDrawable";
+
+ private static ExecutorService sThreadPool = Executors.newSingleThreadExecutor();
+ private static GalleryBitmapPool sBitmapPool = GalleryBitmapPool.getInstance();
+ private static byte[] sTempStorage = new byte[64 * 1024];
+
+ // UI thread only
+ private Paint mPaint = new Paint();
+ private Matrix mDrawMatrix = new Matrix();
+
+ // Decoder thread only
+ private BitmapFactory.Options mOptions = new BitmapFactory.Options();
+
+ // Shared, guarded by mLock
+ private Object mLock = new Object();
+ private Bitmap mBitmap;
+ protected T mData;
+ private boolean mIsQueued;
+ private int mImageWidth, mImageHeight;
+ private Rect mBounds = new Rect();
+ private int mSampleSize = 1;
+
+ public AutoThumbnailDrawable() {
+ mPaint.setAntiAlias(true);
+ mPaint.setFilterBitmap(true);
+ mDrawMatrix.reset();
+ mOptions.inTempStorage = sTempStorage;
+ }
+
+ protected abstract byte[] getPreferredImageBytes(T data);
+ protected abstract InputStream getFallbackImageStream(T data);
+ protected abstract boolean dataChangedLocked(T data);
+
+ public void setImage(T data, int width, int height) {
+ if (!dataChangedLocked(data)) return;
+ synchronized (mLock) {
+ mImageWidth = width;
+ mImageHeight = height;
+ mData = data;
+ setBitmapLocked(null);
+ refreshSampleSizeLocked();
+ }
+ invalidateSelf();
+ }
+
+ private void setBitmapLocked(Bitmap b) {
+ if (b == mBitmap) {
+ return;
+ }
+ if (mBitmap != null) {
+ sBitmapPool.put(mBitmap);
+ }
+ mBitmap = b;
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ synchronized (mLock) {
+ mBounds.set(bounds);
+ if (mBounds.isEmpty()) {
+ mBitmap = null;
+ } else {
+ refreshSampleSizeLocked();
+ updateDrawMatrixLocked();
+ }
+ }
+ invalidateSelf();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mBitmap != null) {
+ canvas.save();
+ canvas.clipRect(mBounds);
+ canvas.concat(mDrawMatrix);
+ canvas.drawBitmap(mBitmap, 0, 0, mPaint);
+ canvas.restore();
+ } else {
+ // TODO: Draw placeholder...?
+ }
+ }
+
+ private void updateDrawMatrixLocked() {
+ if (mBitmap == null || mBounds.isEmpty()) {
+ mDrawMatrix.reset();
+ return;
+ }
+
+ float scale;
+ float dx = 0, dy = 0;
+
+ int dwidth = mBitmap.getWidth();
+ int dheight = mBitmap.getHeight();
+ int vwidth = mBounds.width();
+ int vheight = mBounds.height();
+
+ // Calculates a matrix similar to ScaleType.CENTER_CROP
+ if (dwidth * vheight > vwidth * dheight) {
+ scale = (float) vheight / (float) dheight;
+ dx = (vwidth - dwidth * scale) * 0.5f;
+ } else {
+ scale = (float) vwidth / (float) dwidth;
+ dy = (vheight - dheight * scale) * 0.5f;
+ }
+ if (scale < .8f) {
+ Log.w(TAG, "sample size was too small! Overdrawing! " + scale + ", " + mSampleSize);
+ } else if (scale > 1.5f) {
+ Log.w(TAG, "Potential quality loss! " + scale + ", " + mSampleSize);
+ }
+
+ mDrawMatrix.setScale(scale, scale);
+ mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
+ }
+
+ private int calculateSampleSizeLocked(int dwidth, int dheight) {
+ float scale;
+
+ int vwidth = mBounds.width();
+ int vheight = mBounds.height();
+
+ // Inverse of updateDrawMatrixLocked
+ if (dwidth * vheight > vwidth * dheight) {
+ scale = (float) dheight / (float) vheight;
+ } else {
+ scale = (float) dwidth / (float) vwidth;
+ }
+ int result = Math.round(scale);
+ return result > 0 ? result : 1;
+ }
+
+ private void refreshSampleSizeLocked() {
+ if (mBounds.isEmpty() || mImageWidth == 0 || mImageHeight == 0) {
+ return;
+ }
+
+ int sampleSize = calculateSampleSizeLocked(mImageWidth, mImageHeight);
+ if (sampleSize != mSampleSize || mBitmap == null) {
+ mSampleSize = sampleSize;
+ loadBitmapLocked();
+ }
+ }
+
+ private void loadBitmapLocked() {
+ if (!mIsQueued && !mBounds.isEmpty()) {
+ unscheduleSelf(mUpdateBitmap);
+ sThreadPool.execute(mLoadBitmap);
+ mIsQueued = true;
+ }
+ }
+
+ public float getAspectRatio() {
+ return (float) mImageWidth / (float) mImageHeight;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return -1;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return -1;
+ }
+
+ @Override
+ public int getOpacity() {
+ Bitmap bm = mBitmap;
+ return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ?
+ PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ int oldAlpha = mPaint.getAlpha();
+ if (alpha != oldAlpha) {
+ mPaint.setAlpha(alpha);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mPaint.setColorFilter(cf);
+ invalidateSelf();
+ }
+
+ private final Runnable mLoadBitmap = new Runnable() {
+ @Override
+ public void run() {
+ T data;
+ synchronized (mLock) {
+ data = mData;
+ }
+ int preferredSampleSize = 1;
+ byte[] preferred = getPreferredImageBytes(data);
+ boolean hasPreferred = (preferred != null && preferred.length > 0);
+ if (hasPreferred) {
+ mOptions.inJustDecodeBounds = true;
+ BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions);
+ mOptions.inJustDecodeBounds = false;
+ }
+ int sampleSize, width, height;
+ synchronized (mLock) {
+ if (dataChangedLocked(data)) {
+ return;
+ }
+ width = mImageWidth;
+ height = mImageHeight;
+ if (hasPreferred) {
+ preferredSampleSize = calculateSampleSizeLocked(
+ mOptions.outWidth, mOptions.outHeight);
+ }
+ sampleSize = calculateSampleSizeLocked(width, height);
+ mIsQueued = false;
+ }
+ Bitmap b = null;
+ InputStream is = null;
+ try {
+ if (hasPreferred) {
+ mOptions.inSampleSize = preferredSampleSize;
+ mOptions.inBitmap = sBitmapPool.get(
+ mOptions.outWidth / preferredSampleSize,
+ mOptions.outHeight / preferredSampleSize);
+ b = BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions);
+ if (mOptions.inBitmap != null && b != mOptions.inBitmap) {
+ sBitmapPool.put(mOptions.inBitmap);
+ mOptions.inBitmap = null;
+ }
+ }
+ if (b == null) {
+ is = getFallbackImageStream(data);
+ mOptions.inSampleSize = sampleSize;
+ mOptions.inBitmap = sBitmapPool.get(width / sampleSize, height / sampleSize);
+ b = BitmapFactory.decodeStream(is, null, mOptions);
+ if (mOptions.inBitmap != null && b != mOptions.inBitmap) {
+ sBitmapPool.put(mOptions.inBitmap);
+ mOptions.inBitmap = null;
+ }
+ }
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to fetch bitmap", e);
+ return;
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (Exception e) {}
+ if (b != null) {
+ synchronized (mLock) {
+ if (!dataChangedLocked(data)) {
+ setBitmapLocked(b);
+ scheduleSelf(mUpdateBitmap, 0);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ private final Runnable mUpdateBitmap = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (AutoThumbnailDrawable.this) {
+ updateDrawMatrixLocked();
+ invalidateSelf();
+ }
+ }
+ };
+
+}
diff --git a/src/com/android/photos/drawables/DataUriThumbnailDrawable.java b/src/com/android/photos/drawables/DataUriThumbnailDrawable.java
new file mode 100644
index 000000000..c83b0c8fa
--- /dev/null
+++ b/src/com/android/photos/drawables/DataUriThumbnailDrawable.java
@@ -0,0 +1,54 @@
+/*
+ * 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.photos.drawables;
+
+import android.media.ExifInterface;
+import android.text.TextUtils;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class DataUriThumbnailDrawable extends AutoThumbnailDrawable<String> {
+
+ @Override
+ protected byte[] getPreferredImageBytes(String data) {
+ byte[] thumbnail = null;
+ try {
+ ExifInterface exif = new ExifInterface(data);
+ if (exif.hasThumbnail()) {
+ thumbnail = exif.getThumbnail();
+ }
+ } catch (IOException e) { }
+ return thumbnail;
+ }
+
+ @Override
+ protected InputStream getFallbackImageStream(String data) {
+ try {
+ return new FileInputStream(data);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean dataChangedLocked(String data) {
+ return !TextUtils.equals(mData, data);
+ }
+}
diff --git a/src/com/android/photos/drawables/DrawableFactory.java b/src/com/android/photos/drawables/DrawableFactory.java
new file mode 100644
index 000000000..ad046c820
--- /dev/null
+++ b/src/com/android/photos/drawables/DrawableFactory.java
@@ -0,0 +1,24 @@
+/*
+ * 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.photos.drawables;
+
+import android.graphics.drawable.Drawable;
+
+
+public interface DrawableFactory<T> {
+ Drawable drawableForItem(T item, Drawable recycle);
+}
diff --git a/src/com/android/photos/drawables/MtpThumbnailDrawable.java b/src/com/android/photos/drawables/MtpThumbnailDrawable.java
new file mode 100644
index 000000000..e35e06943
--- /dev/null
+++ b/src/com/android/photos/drawables/MtpThumbnailDrawable.java
@@ -0,0 +1,61 @@
+/*
+ * 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.photos.drawables;
+
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+
+import com.android.gallery3d.ingest.MtpDeviceIndex;
+
+import java.io.InputStream;
+
+public class MtpThumbnailDrawable extends AutoThumbnailDrawable<MtpObjectInfo> {
+ public void setImage(MtpObjectInfo data) {
+ if (data == null) {
+ setImage(null, 0, 0);
+ } else {
+ setImage(data, data.getImagePixWidth(), data.getImagePixHeight());
+ }
+ }
+
+ @Override
+ protected byte[] getPreferredImageBytes(MtpObjectInfo data) {
+ if (data == null) {
+ return null;
+ }
+ MtpDevice device = MtpDeviceIndex.getInstance().getDevice();
+ if (device != null) {
+ return device.getThumbnail(data.getObjectHandle());
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected InputStream getFallbackImageStream(MtpObjectInfo data) {
+ // No fallback
+ return null;
+ }
+
+ @Override
+ protected boolean dataChangedLocked(MtpObjectInfo data) {
+ // We only fetch the MtpObjectInfo once when creating
+ // the index so checking the reference is enough
+ return mData == data;
+ }
+
+}
diff --git a/src/com/android/photos/shims/BitmapJobDrawable.java b/src/com/android/photos/shims/BitmapJobDrawable.java
new file mode 100644
index 000000000..299becb07
--- /dev/null
+++ b/src/com/android/photos/shims/BitmapJobDrawable.java
@@ -0,0 +1,158 @@
+package com.android.photos.shims;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.ui.BitmapLoader;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.ThreadPool;
+import com.android.photos.data.GalleryBitmapPool;
+import com.android.photos.drawables.AutoThumbnailDrawable;
+
+
+public class BitmapJobDrawable extends Drawable implements Runnable {
+
+ private ThumbnailLoader mLoader;
+ private MediaItem mItem;
+ private Bitmap mBitmap;
+ private Paint mPaint = new Paint();
+ private Matrix mDrawMatrix = new Matrix();
+
+ public BitmapJobDrawable() {
+ }
+
+ public void setMediaItem(MediaItem item) {
+ if (mLoader != null) {
+ mLoader.cancelLoad();
+ }
+ mItem = item;
+ if (mBitmap != null) {
+ GalleryBitmapPool.getInstance().put(mBitmap);
+ mBitmap = null;
+ }
+ // TODO: Figure out why ThumbnailLoader doesn't like to be re-used
+ mLoader = new ThumbnailLoader(this);
+ mLoader.startLoad();
+ invalidateSelf();
+ }
+
+ @Override
+ public void run() {
+ Bitmap bitmap = mLoader.getBitmap();
+ if (bitmap != null) {
+ mBitmap = bitmap;
+ updateDrawMatrix();
+ }
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ updateDrawMatrix();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Rect bounds = getBounds();
+ if (mBitmap != null) {
+ canvas.save();
+ canvas.clipRect(bounds);
+ canvas.concat(mDrawMatrix);
+ canvas.drawBitmap(mBitmap, 0, 0, mPaint);
+ canvas.restore();
+ } else {
+ mPaint.setColor(0xFFCCCCCC);
+ canvas.drawRect(bounds, mPaint);
+ }
+ }
+
+ private void updateDrawMatrix() {
+ Rect bounds = getBounds();
+ if (mBitmap == null || bounds.isEmpty()) {
+ mDrawMatrix.reset();
+ return;
+ }
+
+ float scale;
+ float dx = 0, dy = 0;
+
+ int dwidth = mBitmap.getWidth();
+ int dheight = mBitmap.getHeight();
+ int vwidth = bounds.width();
+ int vheight = bounds.height();
+
+ // Calculates a matrix similar to ScaleType.CENTER_CROP
+ if (dwidth * vheight > vwidth * dheight) {
+ scale = (float) vheight / (float) dheight;
+ dx = (vwidth - dwidth * scale) * 0.5f;
+ } else {
+ scale = (float) vwidth / (float) dwidth;
+ dy = (vheight - dheight * scale) * 0.5f;
+ }
+
+ mDrawMatrix.setScale(scale, scale);
+ mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
+ invalidateSelf();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+ @Override
+ public int getOpacity() {
+ Bitmap bm = mBitmap;
+ return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ?
+ PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ int oldAlpha = mPaint.getAlpha();
+ if (alpha != oldAlpha) {
+ mPaint.setAlpha(alpha);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mPaint.setColorFilter(cf);
+ invalidateSelf();
+ }
+
+ private static class ThumbnailLoader extends BitmapLoader {
+ private static final ThreadPool sThreadPool = new ThreadPool(0, 2);
+ private BitmapJobDrawable mParent;
+
+ public ThumbnailLoader(BitmapJobDrawable parent) {
+ mParent = parent;
+ }
+
+ @Override
+ protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
+ return sThreadPool.submit(
+ mParent.mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
+ }
+
+ @Override
+ protected void onLoadComplete(Bitmap bitmap) {
+ mParent.scheduleSelf(mParent, 0);
+ }
+ }
+
+}
diff --git a/src/com/android/photos/shims/MediaItemsLoader.java b/src/com/android/photos/shims/MediaItemsLoader.java
new file mode 100644
index 000000000..886b3c3a1
--- /dev/null
+++ b/src/com/android/photos/shims/MediaItemsLoader.java
@@ -0,0 +1,148 @@
+/*
+ * 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.photos.shims;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.drawable.Drawable;
+import android.provider.MediaStore.Files.FileColumns;
+
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.MediaSet.ItemConsumer;
+import com.android.gallery3d.data.MediaSet.SyncListener;
+import com.android.gallery3d.util.Future;
+import com.android.photos.data.PhotoSetLoader;
+import com.android.photos.drawables.DrawableFactory;
+
+import java.util.ArrayList;
+
+/**
+ * Returns all MediaItems in a MediaSet, wrapping them in a cursor to appear
+ * like a PhotoSetLoader
+ */
+public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements DrawableFactory<Cursor> {
+
+ private static final SyncListener sNullListener = new SyncListener() {
+ @Override
+ public void onSyncDone(MediaSet mediaSet, int resultCode) {
+ }
+ };
+
+ private MediaSet mMediaSet;
+ private Future<Integer> mSyncTask = null;
+ private ContentListener mObserver = new ContentListener() {
+ @Override
+ public void onContentDirty() {
+ onContentChanged();
+ }
+ };
+ private ArrayList<MediaItem> mMediaItems = new ArrayList<MediaItem>();
+
+ public MediaItemsLoader(Context context) {
+ super(context);
+ DataManager dm = DataManager.from(context);
+ String path = dm.getTopSetPath(DataManager.INCLUDE_ALL);
+ mMediaSet = dm.getMediaSet(path);
+ }
+
+ public MediaItemsLoader(Context context, String parentPath) {
+ super(context);
+ mMediaSet = DataManager.from(getContext()).getMediaSet(parentPath);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ mMediaSet.addContentListener(mObserver);
+ mSyncTask = mMediaSet.requestSync(sNullListener);
+ forceLoad();
+ }
+
+ @Override
+ protected boolean onCancelLoad() {
+ if (mSyncTask != null) {
+ mSyncTask.cancel();
+ mSyncTask = null;
+ }
+ return super.onCancelLoad();
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ mMediaSet.removeContentListener(mObserver);
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ onStopLoading();
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ mMediaSet.loadIfDirty();
+ final MatrixCursor cursor = new MatrixCursor(PhotoSetLoader.PROJECTION);
+ final Object[] row = new Object[PhotoSetLoader.PROJECTION.length];
+ mMediaSet.enumerateTotalMediaItems(new ItemConsumer() {
+ @Override
+ public void consume(int index, MediaItem item) {
+ row[PhotoSetLoader.INDEX_ID] = index;
+ row[PhotoSetLoader.INDEX_DATA] = item.getContentUri().toString();
+ row[PhotoSetLoader.INDEX_DATE_ADDED] = item.getDateInMs();
+ row[PhotoSetLoader.INDEX_HEIGHT] = item.getHeight();
+ row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth();
+ row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth();
+ int rawMediaType = item.getMediaType();
+ int mappedMediaType = FileColumns.MEDIA_TYPE_NONE;
+ if (rawMediaType == MediaItem.MEDIA_TYPE_IMAGE) {
+ mappedMediaType = FileColumns.MEDIA_TYPE_IMAGE;
+ } else if (rawMediaType == MediaItem.MEDIA_TYPE_VIDEO) {
+ mappedMediaType = FileColumns.MEDIA_TYPE_VIDEO;
+ }
+ row[PhotoSetLoader.INDEX_MEDIA_TYPE] = mappedMediaType;
+ cursor.addRow(row);
+ mMediaItems.add(item);
+ }
+ });
+ return cursor;
+ }
+
+ @Override
+ public Drawable drawableForItem(Cursor item, Drawable recycle) {
+ BitmapJobDrawable drawable = null;
+ if (recycle == null || !(recycle instanceof BitmapJobDrawable)) {
+ drawable = new BitmapJobDrawable();
+ } else {
+ drawable = (BitmapJobDrawable) recycle;
+ }
+ int index = item.getInt(PhotoSetLoader.INDEX_ID);
+ drawable.setMediaItem(mMediaItems.get(index));
+ return drawable;
+ }
+
+ public static int getThumbnailSize() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+}
diff --git a/src/com/android/photos/shims/MediaSetLoader.java b/src/com/android/photos/shims/MediaSetLoader.java
new file mode 100644
index 000000000..7a6fcb865
--- /dev/null
+++ b/src/com/android/photos/shims/MediaSetLoader.java
@@ -0,0 +1,143 @@
+/*
+ * 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.photos.shims;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.drawable.Drawable;
+import android.provider.MediaStore.Files.FileColumns;
+
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaDetails;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.MediaSet.ItemConsumer;
+import com.android.gallery3d.data.MediaSet.SyncListener;
+import com.android.gallery3d.util.Future;
+import com.android.photos.data.AlbumSetLoader;
+import com.android.photos.data.PhotoSetLoader;
+import com.android.photos.drawables.DrawableFactory;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+
+/**
+ * Returns all MediaSets in a MediaSet, wrapping them in a cursor to appear
+ * like a AlbumSetLoader.
+ */
+public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements DrawableFactory<Cursor>{
+
+ private static final SyncListener sNullListener = new SyncListener() {
+ @Override
+ public void onSyncDone(MediaSet mediaSet, int resultCode) {
+ }
+ };
+
+ private MediaSet mMediaSet;
+ private Future<Integer> mSyncTask = null;
+ private ContentListener mObserver = new ContentListener() {
+ @Override
+ public void onContentDirty() {
+ onContentChanged();
+ }
+ };
+
+ private ArrayList<MediaItem> mCoverItems = new ArrayList<MediaItem>();
+
+ public MediaSetLoader(Context context) {
+ super(context);
+ DataManager dm = DataManager.from(context);
+ String path = dm.getTopSetPath(DataManager.INCLUDE_ALL);
+ mMediaSet = dm.getMediaSet(path);
+ }
+
+ public MediaSetLoader(Context context, String path) {
+ super(context);
+ mMediaSet = DataManager.from(getContext()).getMediaSet(path);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ mMediaSet.addContentListener(mObserver);
+ mSyncTask = mMediaSet.requestSync(sNullListener);
+ forceLoad();
+ }
+
+ @Override
+ protected boolean onCancelLoad() {
+ if (mSyncTask != null) {
+ mSyncTask.cancel();
+ mSyncTask = null;
+ }
+ return super.onCancelLoad();
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ mMediaSet.removeContentListener(mObserver);
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ onStopLoading();
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ mMediaSet.loadIfDirty();
+ final MatrixCursor cursor = new MatrixCursor(AlbumSetLoader.PROJECTION);
+ final Object[] row = new Object[AlbumSetLoader.PROJECTION.length];
+ int count = mMediaSet.getSubMediaSetCount();
+ for (int i = 0; i < count; i++) {
+ MediaSet m = mMediaSet.getSubMediaSet(i);
+ m.loadIfDirty();
+ row[AlbumSetLoader.INDEX_ID] = i;
+ row[AlbumSetLoader.INDEX_TITLE] = m.getName();
+ row[AlbumSetLoader.INDEX_COUNT] = m.getMediaItemCount();
+ MediaItem coverItem = m.getCoverMediaItem();
+ row[AlbumSetLoader.INDEX_TIMESTAMP] = coverItem.getDateInMs();
+ mCoverItems.add(coverItem);
+ cursor.addRow(row);
+ }
+ return cursor;
+ }
+
+ @Override
+ public Drawable drawableForItem(Cursor item, Drawable recycle) {
+ BitmapJobDrawable drawable = null;
+ if (recycle == null || !(recycle instanceof BitmapJobDrawable)) {
+ drawable = new BitmapJobDrawable();
+ } else {
+ drawable = (BitmapJobDrawable) recycle;
+ }
+ int index = item.getInt(AlbumSetLoader.INDEX_ID);
+ drawable.setMediaItem(mCoverItems.get(index));
+ return drawable;
+ }
+
+ public static int getThumbnailSize() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+}
diff --git a/src/com/android/photos/views/BlockingGLTextureView.java b/src/com/android/photos/views/BlockingGLTextureView.java
new file mode 100644
index 000000000..c38f8f7a4
--- /dev/null
+++ b/src/com/android/photos/views/BlockingGLTextureView.java
@@ -0,0 +1,427 @@
+/*
+ * 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.photos.views;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLSurfaceView.Renderer;
+import android.opengl.GLUtils;
+import android.util.Log;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+
+public class BlockingGLTextureView extends TextureView
+ implements SurfaceTextureListener {
+
+ private RenderThread mRenderThread;
+
+ public BlockingGLTextureView(Context context) {
+ super(context);
+ setSurfaceTextureListener(this);
+ }
+
+ public void setRenderer(Renderer renderer) {
+ if (mRenderThread != null) {
+ throw new IllegalArgumentException("Renderer already set");
+ }
+ mRenderThread = new RenderThread(renderer);
+ }
+
+ public void render() {
+ mRenderThread.render();
+ }
+
+ public void destroy() {
+ if (mRenderThread != null) {
+ mRenderThread.finish();
+ mRenderThread = null;
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
+ int height) {
+ mRenderThread.setSurface(surface);
+ mRenderThread.setSize(width, height);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
+ int height) {
+ mRenderThread.setSize(width, height);
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ if (mRenderThread != null) {
+ mRenderThread.setSurface(null);
+ }
+ return false;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ destroy();
+ } catch (Throwable t) {}
+ super.finalize();
+ }
+
+ /**
+ * An EGL helper class.
+ */
+
+ private static class EglHelper {
+ private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+ private static final int EGL_OPENGL_ES2_BIT = 4;
+
+ EGL10 mEgl;
+ EGLDisplay mEglDisplay;
+ EGLSurface mEglSurface;
+ EGLConfig mEglConfig;
+ EGLContext mEglContext;
+
+ private EGLConfig chooseEglConfig() {
+ int[] configsCount = new int[1];
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] configSpec = getConfig();
+ if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
+ throw new IllegalArgumentException("eglChooseConfig failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ } else if (configsCount[0] > 0) {
+ return configs[0];
+ }
+ return null;
+ }
+
+ private static int[] getConfig() {
+ return new int[] {
+ EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL10.EGL_RED_SIZE, 8,
+ EGL10.EGL_GREEN_SIZE, 8,
+ EGL10.EGL_BLUE_SIZE, 8,
+ EGL10.EGL_ALPHA_SIZE, 8,
+ EGL10.EGL_DEPTH_SIZE, 0,
+ EGL10.EGL_STENCIL_SIZE, 0,
+ EGL10.EGL_NONE
+ };
+ }
+
+ EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+ int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+ return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+ }
+
+ /**
+ * Initialize EGL for a given configuration spec.
+ */
+ public void start() {
+ /*
+ * Get an EGL instance
+ */
+ mEgl = (EGL10) EGLContext.getEGL();
+
+ /*
+ * Get to the default display.
+ */
+ mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+ if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+ throw new RuntimeException("eglGetDisplay failed");
+ }
+
+ /*
+ * We can now initialize EGL for that display
+ */
+ int[] version = new int[2];
+ if(!mEgl.eglInitialize(mEglDisplay, version)) {
+ throw new RuntimeException("eglInitialize failed");
+ }
+ mEglConfig = chooseEglConfig();
+
+ /*
+ * Create an EGL context. We want to do this as rarely as we can, because an
+ * EGL context is a somewhat heavy object.
+ */
+ mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+
+ if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
+ mEglContext = null;
+ throwEglException("createContext");
+ }
+
+ mEglSurface = null;
+ }
+
+ /**
+ * Create an egl surface for the current SurfaceTexture surface. If a surface
+ * already exists, destroy it before creating the new surface.
+ *
+ * @return true if the surface was created successfully.
+ */
+ public boolean createSurface(SurfaceTexture surface) {
+ /*
+ * Check preconditions.
+ */
+ if (mEgl == null) {
+ throw new RuntimeException("egl not initialized");
+ }
+ if (mEglDisplay == null) {
+ throw new RuntimeException("eglDisplay not initialized");
+ }
+ if (mEglConfig == null) {
+ throw new RuntimeException("mEglConfig not initialized");
+ }
+
+ /*
+ * The window size has changed, so we need to create a new
+ * surface.
+ */
+ destroySurfaceImp();
+
+ /*
+ * Create an EGL surface we can render into.
+ */
+ if (surface != null) {
+ mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null);
+ } else {
+ mEglSurface = null;
+ }
+
+ if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+ int error = mEgl.eglGetError();
+ if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+ Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+ }
+ return false;
+ }
+
+ /*
+ * Before we can issue GL commands, we need to make sure
+ * the context is current and bound to a surface.
+ */
+ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ /*
+ * Could not make the context current, probably because the underlying
+ * SurfaceView surface has been destroyed.
+ */
+ logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a GL object for the current EGL context.
+ */
+ public GL10 createGL() {
+ return (GL10) mEglContext.getGL();
+ }
+
+ /**
+ * Display the current render surface.
+ * @return the EGL error code from eglSwapBuffers.
+ */
+ public int swap() {
+ if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+ return mEgl.eglGetError();
+ }
+ return EGL10.EGL_SUCCESS;
+ }
+
+ public void destroySurface() {
+ destroySurfaceImp();
+ }
+
+ private void destroySurfaceImp() {
+ if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
+ mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_CONTEXT);
+ mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+ mEglSurface = null;
+ }
+ }
+
+ public void finish() {
+ if (mEglContext != null) {
+ mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+ mEglContext = null;
+ }
+ if (mEglDisplay != null) {
+ mEgl.eglTerminate(mEglDisplay);
+ mEglDisplay = null;
+ }
+ }
+
+ private void throwEglException(String function) {
+ throwEglException(function, mEgl.eglGetError());
+ }
+
+ public static void throwEglException(String function, int error) {
+ String message = formatEglError(function, error);
+ throw new RuntimeException(message);
+ }
+
+ public static void logEglErrorAsWarning(String tag, String function, int error) {
+ Log.w(tag, formatEglError(function, error));
+ }
+
+ public static String formatEglError(String function, int error) {
+ return function + " failed: " + error;
+ }
+
+ }
+
+ private static class RenderThread extends Thread {
+ private static final int INVALID = -1;
+ private static final int RENDER = 1;
+ private static final int CHANGE_SURFACE = 2;
+ private static final int RESIZE_SURFACE = 3;
+ private static final int FINISH = 4;
+
+ private EglHelper mEglHelper = new EglHelper();
+
+ private Object mLock = new Object();
+ private int mExecMsgId = INVALID;
+ private SurfaceTexture mSurface;
+ private Renderer mRenderer;
+ private int mWidth, mHeight;
+
+ private boolean mFinished = false;
+ private GL10 mGL;
+
+ public RenderThread(Renderer renderer) {
+ super("RenderThread");
+ mRenderer = renderer;
+ start();
+ }
+
+ private void checkRenderer() {
+ if (mRenderer == null) {
+ throw new IllegalArgumentException("Renderer is null!");
+ }
+ }
+
+ private void checkSurface() {
+ if (mSurface == null) {
+ throw new IllegalArgumentException("surface is null!");
+ }
+ }
+
+ public void setSurface(SurfaceTexture surface) {
+ // If the surface is null we're being torn down, don't need a
+ // renderer then
+ if (surface != null) {
+ checkRenderer();
+ }
+ mSurface = surface;
+ exec(CHANGE_SURFACE);
+ }
+
+ public void setSize(int width, int height) {
+ checkRenderer();
+ checkSurface();
+ mWidth = width;
+ mHeight = height;
+ exec(RESIZE_SURFACE);
+ }
+
+ public void render() {
+ checkRenderer();
+ if (mSurface != null) {
+ exec(RENDER);
+ mSurface.updateTexImage();
+ }
+ }
+
+ public void finish() {
+ mSurface = null;
+ exec(FINISH);
+ try {
+ join();
+ } catch (InterruptedException e) {}
+ }
+
+ private void exec(int msgid) {
+ synchronized (mLock) {
+ if (mExecMsgId != INVALID) {
+ throw new IllegalArgumentException("Message already set - multithreaded access?");
+ }
+ mExecMsgId = msgid;
+ mLock.notify();
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ private void handleMessageLocked(int what) {
+ switch (what) {
+ case CHANGE_SURFACE:
+ if (mEglHelper.createSurface(mSurface)) {
+ mGL = mEglHelper.createGL();
+ mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig);
+ }
+ break;
+ case RESIZE_SURFACE:
+ mRenderer.onSurfaceChanged(mGL, mWidth, mHeight);
+ break;
+ case RENDER:
+ mRenderer.onDrawFrame(mGL);
+ mEglHelper.swap();
+ break;
+ case FINISH:
+ mEglHelper.destroySurface();
+ mEglHelper.finish();
+ mFinished = true;
+ break;
+ }
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ mEglHelper.start();
+ while (!mFinished) {
+ while (mExecMsgId == INVALID) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {}
+ }
+ handleMessageLocked(mExecMsgId);
+ mExecMsgId = INVALID;
+ mLock.notify();
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/photos/views/GalleryThumbnailView.java b/src/com/android/photos/views/GalleryThumbnailView.java
new file mode 100644
index 000000000..e5dd6f2ff
--- /dev/null
+++ b/src/com/android/photos/views/GalleryThumbnailView.java
@@ -0,0 +1,883 @@
+/*
+ * 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.photos.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.VelocityTrackerCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.EdgeEffectCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.ListAdapter;
+import android.widget.OverScroller;
+
+import java.util.ArrayList;
+
+public class GalleryThumbnailView extends ViewGroup {
+
+ public interface GalleryThumbnailAdapter extends ListAdapter {
+ /**
+ * @param position Position to get the intrinsic aspect ratio for
+ * @return width / height
+ */
+ float getIntrinsicAspectRatio(int position);
+ }
+
+ private static final String TAG = "GalleryThumbnailView";
+ private static final float ASPECT_RATIO = (float) Math.sqrt(1.5f);
+ private static final int LAND_UNITS = 2;
+ private static final int PORT_UNITS = 3;
+
+ private GalleryThumbnailAdapter mAdapter;
+
+ private final RecycleBin mRecycler = new RecycleBin();
+
+ private final AdapterDataSetObserver mObserver = new AdapterDataSetObserver();
+
+ private boolean mDataChanged;
+ private int mOldItemCount;
+ private int mItemCount;
+ private boolean mHasStableIds;
+
+ private int mFirstPosition;
+
+ private boolean mPopulating;
+ private boolean mInLayout;
+
+ private int mTouchSlop;
+ private int mMaximumVelocity;
+ private int mFlingVelocity;
+ private float mLastTouchX;
+ private float mTouchRemainderX;
+ private int mActivePointerId;
+
+ private static final int TOUCH_MODE_IDLE = 0;
+ private static final int TOUCH_MODE_DRAGGING = 1;
+ private static final int TOUCH_MODE_FLINGING = 2;
+
+ private int mTouchMode;
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+ private final OverScroller mScroller;
+
+ private final EdgeEffectCompat mLeftEdge;
+ private final EdgeEffectCompat mRightEdge;
+
+ private int mLargeColumnWidth;
+ private int mSmallColumnWidth;
+ private int mLargeColumnUnitCount = 8;
+ private int mSmallColumnUnitCount = 10;
+
+ public GalleryThumbnailView(Context context) {
+ this(context, null);
+ }
+
+ public GalleryThumbnailView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public GalleryThumbnailView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final ViewConfiguration vc = ViewConfiguration.get(context);
+ mTouchSlop = vc.getScaledTouchSlop();
+ mMaximumVelocity = vc.getScaledMaximumFlingVelocity();
+ mFlingVelocity = vc.getScaledMinimumFlingVelocity();
+ mScroller = new OverScroller(context);
+
+ mLeftEdge = new EdgeEffectCompat(context);
+ mRightEdge = new EdgeEffectCompat(context);
+ setWillNotDraw(false);
+ setClipToPadding(false);
+ }
+
+ @Override
+ public void requestLayout() {
+ if (!mPopulating) {
+ super.requestLayout();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthMode != MeasureSpec.EXACTLY) {
+ Log.e(TAG, "onMeasure: must have an exact width or match_parent! " +
+ "Using fallback spec of EXACTLY " + widthSize);
+ }
+ if (heightMode != MeasureSpec.EXACTLY) {
+ Log.e(TAG, "onMeasure: must have an exact height or match_parent! " +
+ "Using fallback spec of EXACTLY " + heightSize);
+ }
+
+ setMeasuredDimension(widthSize, heightSize);
+
+ float portSpaces = mLargeColumnUnitCount / PORT_UNITS;
+ float height = getMeasuredHeight() / portSpaces;
+ mLargeColumnWidth = (int) (height / ASPECT_RATIO);
+ portSpaces++;
+ height = getMeasuredHeight() / portSpaces;
+ mSmallColumnWidth = (int) (height / ASPECT_RATIO);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mInLayout = true;
+ populate();
+ mInLayout = false;
+
+ final int width = r - l;
+ final int height = b - t;
+ mLeftEdge.setSize(width, height);
+ mRightEdge.setSize(width, height);
+ }
+
+ private void populate() {
+ if (getWidth() == 0 || getHeight() == 0) {
+ return;
+ }
+
+ // TODO: Handle size changing
+// final int colCount = mColCount;
+// if (mItemTops == null || mItemTops.length != colCount) {
+// mItemTops = new int[colCount];
+// mItemBottoms = new int[colCount];
+// final int top = getPaddingTop();
+// final int offset = top + Math.min(mRestoreOffset, 0);
+// Arrays.fill(mItemTops, offset);
+// Arrays.fill(mItemBottoms, offset);
+// mLayoutRecords.clear();
+// if (mInLayout) {
+// removeAllViewsInLayout();
+// } else {
+// removeAllViews();
+// }
+// mRestoreOffset = 0;
+// }
+
+ mPopulating = true;
+ layoutChildren(mDataChanged);
+ fillRight(mFirstPosition + getChildCount(), 0);
+ fillLeft(mFirstPosition - 1, 0);
+ mPopulating = false;
+ mDataChanged = false;
+ }
+
+ final void layoutChildren(boolean queryAdapter) {
+// TODO
+// final int childCount = getChildCount();
+// for (int i = 0; i < childCount; i++) {
+// View child = getChildAt(i);
+//
+// if (child.isLayoutRequested()) {
+// final int widthSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY);
+// final int heightSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY);
+// child.measure(widthSpec, heightSpec);
+// child.layout(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
+// }
+//
+// int childTop = mItemBottoms[col] > Integer.MIN_VALUE ?
+// mItemBottoms[col] + mItemMargin : child.getTop();
+// if (span > 1) {
+// int lowest = childTop;
+// for (int j = col + 1; j < col + span; j++) {
+// final int bottom = mItemBottoms[j] + mItemMargin;
+// if (bottom > lowest) {
+// lowest = bottom;
+// }
+// }
+// childTop = lowest;
+// }
+// final int childHeight = child.getMeasuredHeight();
+// final int childBottom = childTop + childHeight;
+// final int childLeft = paddingLeft + col * (colWidth + itemMargin);
+// final int childRight = childLeft + child.getMeasuredWidth();
+// child.layout(childLeft, childTop, childRight, childBottom);
+// }
+ }
+
+ /**
+ * Obtain the view and add it to our list of children. The view can be made
+ * fresh, converted from an unused view, or used as is if it was in the
+ * recycle bin.
+ *
+ * @param startPosition Logical position in the list to start from
+ * @param x Left or right edge of the view to add
+ * @param forward If true, align left edge to x and increase position.
+ * If false, align right edge to x and decrease position.
+ * @return Number of views added
+ */
+ private int makeAndAddColumn(int startPosition, int x, boolean forward) {
+ int columnWidth = mLargeColumnWidth;
+ int addViews = 0;
+ for (int remaining = mLargeColumnUnitCount, i = 0;
+ remaining > 0 && startPosition + i >= 0 && startPosition + i < mItemCount;
+ i += forward ? 1 : -1, addViews++) {
+ if (mAdapter.getIntrinsicAspectRatio(startPosition + i) >= 1f) {
+ // landscape
+ remaining -= LAND_UNITS;
+ } else {
+ // portrait
+ remaining -= PORT_UNITS;
+ if (remaining < 0) {
+ remaining += (mSmallColumnUnitCount - mLargeColumnUnitCount);
+ columnWidth = mSmallColumnWidth;
+ }
+ }
+ }
+ int nextTop = 0;
+ for (int i = 0; i < addViews; i++) {
+ int position = startPosition + (forward ? i : -i);
+ View child = obtainView(position, null);
+ if (child.getParent() != this) {
+ if (mInLayout) {
+ addViewInLayout(child, forward ? -1 : 0, child.getLayoutParams());
+ } else {
+ addView(child, forward ? -1 : 0);
+ }
+ }
+ int heightSize = (int) (.5f + (mAdapter.getIntrinsicAspectRatio(position) >= 1f
+ ? columnWidth / ASPECT_RATIO
+ : columnWidth * ASPECT_RATIO));
+ int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
+ int widthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY);
+ child.measure(widthSpec, heightSpec);
+ int childLeft = forward ? x : x - columnWidth;
+ child.layout(childLeft, nextTop, childLeft + columnWidth, nextTop + heightSize);
+ nextTop += heightSize;
+ }
+ return addViews;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ mVelocityTracker.addMovement(ev);
+ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mVelocityTracker.clear();
+ mScroller.abortAnimation();
+ mLastTouchX = ev.getX();
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+ mTouchRemainderX = 0;
+ if (mTouchMode == TOUCH_MODE_FLINGING) {
+ // Catch!
+ mTouchMode = TOUCH_MODE_DRAGGING;
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE: {
+ final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+ if (index < 0) {
+ Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
+ mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
+ "event stream?");
+ return false;
+ }
+ final float x = MotionEventCompat.getX(ev, index);
+ final float dx = x - mLastTouchX + mTouchRemainderX;
+ final int deltaY = (int) dx;
+ mTouchRemainderX = dx - deltaY;
+
+ if (Math.abs(dx) > mTouchSlop) {
+ mTouchMode = TOUCH_MODE_DRAGGING;
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ mVelocityTracker.addMovement(ev);
+ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mVelocityTracker.clear();
+ mScroller.abortAnimation();
+ mLastTouchX = ev.getX();
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+ mTouchRemainderX = 0;
+ break;
+
+ case MotionEvent.ACTION_MOVE: {
+ final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+ if (index < 0) {
+ Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
+ mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
+ "event stream?");
+ return false;
+ }
+ final float x = MotionEventCompat.getX(ev, index);
+ final float dx = x - mLastTouchX + mTouchRemainderX;
+ final int deltaX = (int) dx;
+ mTouchRemainderX = dx - deltaX;
+
+ if (Math.abs(dx) > mTouchSlop) {
+ mTouchMode = TOUCH_MODE_DRAGGING;
+ }
+
+ if (mTouchMode == TOUCH_MODE_DRAGGING) {
+ mLastTouchX = x;
+
+ if (!trackMotionScroll(deltaX, true)) {
+ // Break fling velocity if we impacted an edge.
+ mVelocityTracker.clear();
+ }
+ }
+ } break;
+
+ case MotionEvent.ACTION_CANCEL:
+ mTouchMode = TOUCH_MODE_IDLE;
+ break;
+
+ case MotionEvent.ACTION_UP: {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ final float velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker,
+ mActivePointerId);
+ if (Math.abs(velocity) > mFlingVelocity) { // TODO
+ mTouchMode = TOUCH_MODE_FLINGING;
+ mScroller.fling(0, 0, (int) velocity, 0,
+ Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
+ mLastTouchX = 0;
+ ViewCompat.postInvalidateOnAnimation(this);
+ } else {
+ mTouchMode = TOUCH_MODE_IDLE;
+ }
+
+ } break;
+ }
+ return true;
+ }
+
+ /**
+ *
+ * @param deltaX Pixels that content should move by
+ * @return true if the movement completed, false if it was stopped prematurely.
+ */
+ private boolean trackMotionScroll(int deltaX, boolean allowOverScroll) {
+ final boolean contentFits = contentFits();
+ final int allowOverhang = Math.abs(deltaX);
+
+ final int overScrolledBy;
+ final int movedBy;
+ if (!contentFits) {
+ final int overhang;
+ final boolean up;
+ mPopulating = true;
+ if (deltaX > 0) {
+ overhang = fillLeft(mFirstPosition - 1, allowOverhang);
+ up = true;
+ } else {
+ overhang = fillRight(mFirstPosition + getChildCount(), allowOverhang);
+ up = false;
+ }
+ movedBy = Math.min(overhang, allowOverhang);
+ offsetChildren(up ? movedBy : -movedBy);
+ recycleOffscreenViews();
+ mPopulating = false;
+ overScrolledBy = allowOverhang - overhang;
+ } else {
+ overScrolledBy = allowOverhang;
+ movedBy = 0;
+ }
+
+ if (allowOverScroll) {
+ final int overScrollMode = ViewCompat.getOverScrollMode(this);
+
+ if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
+ (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits)) {
+
+ if (overScrolledBy > 0) {
+ EdgeEffectCompat edge = deltaX > 0 ? mLeftEdge : mRightEdge;
+ edge.onPull((float) Math.abs(deltaX) / getWidth());
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ }
+ }
+
+ return deltaX == 0 || movedBy != 0;
+ }
+
+ /**
+ * Important: this method will leave offscreen views attached if they
+ * are required to maintain the invariant that child view with index i
+ * is always the view corresponding to position mFirstPosition + i.
+ */
+ private void recycleOffscreenViews() {
+ final int height = getHeight();
+ final int clearAbove = 0;
+ final int clearBelow = height;
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+ if (child.getTop() <= clearBelow) {
+ // There may be other offscreen views, but we need to maintain
+ // the invariant documented above.
+ break;
+ }
+
+ if (mInLayout) {
+ removeViewsInLayout(i, 1);
+ } else {
+ removeViewAt(i);
+ }
+
+ mRecycler.addScrap(child);
+ }
+
+ while (getChildCount() > 0) {
+ final View child = getChildAt(0);
+ if (child.getBottom() >= clearAbove) {
+ // There may be other offscreen views, but we need to maintain
+ // the invariant documented above.
+ break;
+ }
+
+ if (mInLayout) {
+ removeViewsInLayout(0, 1);
+ } else {
+ removeViewAt(0);
+ }
+
+ mRecycler.addScrap(child);
+ mFirstPosition++;
+ }
+ }
+
+ final void offsetChildren(int offset) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ child.layout(child.getLeft() + offset, child.getTop(),
+ child.getRight() + offset, child.getBottom());
+ }
+ }
+
+ private boolean contentFits() {
+ final int childCount = getChildCount();
+ if (childCount == 0) return true;
+ if (childCount != mItemCount) return false;
+
+ return getChildAt(0).getLeft() >= getPaddingLeft() &&
+ getChildAt(childCount - 1).getRight() <= getWidth() - getPaddingRight();
+ }
+
+ private void recycleAllViews() {
+ for (int i = 0; i < getChildCount(); i++) {
+ mRecycler.addScrap(getChildAt(i));
+ }
+
+ if (mInLayout) {
+ removeAllViewsInLayout();
+ } else {
+ removeAllViews();
+ }
+ }
+
+ private int fillRight(int pos, int overhang) {
+ int end = (getRight() - getLeft()) + overhang;
+
+ int nextLeft = getChildCount() == 0 ? 0 : getChildAt(getChildCount() - 1).getRight();
+ while (nextLeft < end && pos < mItemCount) {
+ pos += makeAndAddColumn(pos, nextLeft, true);
+ nextLeft = getChildAt(getChildCount() - 1).getRight();
+ }
+ final int gridRight = getWidth() - getPaddingRight();
+ return getChildAt(getChildCount() - 1).getRight() - gridRight;
+ }
+
+ private int fillLeft(int pos, int overhang) {
+ int end = getPaddingLeft() - overhang;
+
+ int nextRight = getChildAt(0).getLeft();
+ while (nextRight > end && pos >= 0) {
+ pos -= makeAndAddColumn(pos, nextRight, false);
+ nextRight = getChildAt(0).getLeft();
+ }
+
+ mFirstPosition = pos + 1;
+ return getPaddingLeft() - getChildAt(0).getLeft();
+ }
+
+ @Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ final int x = mScroller.getCurrX();
+ final int dx = (int) (x - mLastTouchX);
+ mLastTouchX = x;
+ final boolean stopped = !trackMotionScroll(dx, false);
+
+ if (!stopped && !mScroller.isFinished()) {
+ ViewCompat.postInvalidateOnAnimation(this);
+ } else {
+ if (stopped) {
+ final int overScrollMode = ViewCompat.getOverScrollMode(this);
+ if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) {
+ final EdgeEffectCompat edge;
+ if (dx > 0) {
+ edge = mLeftEdge;
+ } else {
+ edge = mRightEdge;
+ }
+ edge.onAbsorb(Math.abs((int) mScroller.getCurrVelocity()));
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
+ mScroller.abortAnimation();
+ }
+ mTouchMode = TOUCH_MODE_IDLE;
+ }
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (!mLeftEdge.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+ canvas.rotate(270);
+ canvas.translate(-height + getPaddingTop(), 0);
+ mLeftEdge.setSize(height, getWidth());
+ if (mLeftEdge.draw(canvas)) {
+ postInvalidateOnAnimation();
+ }
+ canvas.restoreToCount(restoreCount);
+ }
+ if (!mRightEdge.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+ final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+ canvas.rotate(90);
+ canvas.translate(-getPaddingTop(), width);
+ mRightEdge.setSize(height, width);
+ if (mRightEdge.draw(canvas)) {
+ postInvalidateOnAnimation();
+ }
+ canvas.restoreToCount(restoreCount);
+ }
+ }
+
+ /**
+ * Obtain a populated view from the adapter. If optScrap is non-null and is not
+ * reused it will be placed in the recycle bin.
+ *
+ * @param position position to get view for
+ * @param optScrap Optional scrap view; will be reused if possible
+ * @return A new view, a recycled view from mRecycler, or optScrap
+ */
+ private final View obtainView(int position, View optScrap) {
+ View view = mRecycler.getTransientStateView(position);
+ if (view != null) {
+ return view;
+ }
+
+ // Reuse optScrap if it's of the right type (and not null)
+ final int optType = optScrap != null ?
+ ((LayoutParams) optScrap.getLayoutParams()).viewType : -1;
+ final int positionViewType = mAdapter.getItemViewType(position);
+ final View scrap = optType == positionViewType ?
+ optScrap : mRecycler.getScrapView(positionViewType);
+
+ view = mAdapter.getView(position, scrap, this);
+
+ if (view != scrap && scrap != null) {
+ // The adapter didn't use it; put it back.
+ mRecycler.addScrap(scrap);
+ }
+
+ ViewGroup.LayoutParams lp = view.getLayoutParams();
+
+ if (view.getParent() != this) {
+ if (lp == null) {
+ lp = generateDefaultLayoutParams();
+ } else if (!checkLayoutParams(lp)) {
+ lp = generateLayoutParams(lp);
+ }
+ view.setLayoutParams(lp);
+ }
+
+ final LayoutParams sglp = (LayoutParams) lp;
+ sglp.position = position;
+ sglp.viewType = positionViewType;
+
+ return view;
+ }
+
+ public GalleryThumbnailAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ public void setAdapter(GalleryThumbnailAdapter adapter) {
+ if (mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(mObserver);
+ }
+ // TODO: If the new adapter says that there are stable IDs, remove certain layout records
+ // and onscreen views if they have changed instead of removing all of the state here.
+ clearAllState();
+ mAdapter = adapter;
+ mDataChanged = true;
+ mOldItemCount = mItemCount = adapter != null ? adapter.getCount() : 0;
+ if (adapter != null) {
+ adapter.registerDataSetObserver(mObserver);
+ mRecycler.setViewTypeCount(adapter.getViewTypeCount());
+ mHasStableIds = adapter.hasStableIds();
+ } else {
+ mHasStableIds = false;
+ }
+ populate();
+ }
+
+ /**
+ * Clear all state because the grid will be used for a completely different set of data.
+ */
+ private void clearAllState() {
+ // Clear all layout records and views
+ removeAllViews();
+
+ // Reset to the top of the grid
+ mFirstPosition = 0;
+
+ // Clear recycler because there could be different view types now
+ mRecycler.clear();
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.WRAP_CONTENT);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+ return new LayoutParams(lp);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams lp) {
+ return lp instanceof LayoutParams;
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ public static class LayoutParams extends ViewGroup.LayoutParams {
+ private static final int[] LAYOUT_ATTRS = new int[] {
+ android.R.attr.layout_span
+ };
+
+ private static final int SPAN_INDEX = 0;
+
+ /**
+ * The number of columns this item should span
+ */
+ public int span = 1;
+
+ /**
+ * Item position this view represents
+ */
+ int position;
+
+ /**
+ * Type of this view as reported by the adapter
+ */
+ int viewType;
+
+ /**
+ * The column this view is occupying
+ */
+ int column;
+
+ /**
+ * The stable ID of the item this view displays
+ */
+ long id = -1;
+
+ public LayoutParams(int height) {
+ super(MATCH_PARENT, height);
+
+ if (this.height == MATCH_PARENT) {
+ Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " +
+ "impossible! Falling back to WRAP_CONTENT");
+ this.height = WRAP_CONTENT;
+ }
+ }
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ if (this.width != MATCH_PARENT) {
+ Log.w(TAG, "Inflation setting LayoutParams width to " + this.width +
+ " - must be MATCH_PARENT");
+ this.width = MATCH_PARENT;
+ }
+ if (this.height == MATCH_PARENT) {
+ Log.w(TAG, "Inflation setting LayoutParams height to MATCH_PARENT - " +
+ "impossible! Falling back to WRAP_CONTENT");
+ this.height = WRAP_CONTENT;
+ }
+
+ TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
+ span = a.getInteger(SPAN_INDEX, 1);
+ a.recycle();
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams other) {
+ super(other);
+
+ if (this.width != MATCH_PARENT) {
+ Log.w(TAG, "Constructing LayoutParams with width " + this.width +
+ " - must be MATCH_PARENT");
+ this.width = MATCH_PARENT;
+ }
+ if (this.height == MATCH_PARENT) {
+ Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " +
+ "impossible! Falling back to WRAP_CONTENT");
+ this.height = WRAP_CONTENT;
+ }
+ }
+ }
+
+ private class RecycleBin {
+ private ArrayList<View>[] mScrapViews;
+ private int mViewTypeCount;
+ private int mMaxScrap;
+
+ private SparseArray<View> mTransientStateViews;
+
+ public void setViewTypeCount(int viewTypeCount) {
+ if (viewTypeCount < 1) {
+ throw new IllegalArgumentException("Must have at least one view type (" +
+ viewTypeCount + " types reported)");
+ }
+ if (viewTypeCount == mViewTypeCount) {
+ return;
+ }
+
+ ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
+ for (int i = 0; i < viewTypeCount; i++) {
+ scrapViews[i] = new ArrayList<View>();
+ }
+ mViewTypeCount = viewTypeCount;
+ mScrapViews = scrapViews;
+ }
+
+ public void clear() {
+ final int typeCount = mViewTypeCount;
+ for (int i = 0; i < typeCount; i++) {
+ mScrapViews[i].clear();
+ }
+ if (mTransientStateViews != null) {
+ mTransientStateViews.clear();
+ }
+ }
+
+ public void clearTransientViews() {
+ if (mTransientStateViews != null) {
+ mTransientStateViews.clear();
+ }
+ }
+
+ public void addScrap(View v) {
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (ViewCompat.hasTransientState(v)) {
+ if (mTransientStateViews == null) {
+ mTransientStateViews = new SparseArray<View>();
+ }
+ mTransientStateViews.put(lp.position, v);
+ return;
+ }
+
+ final int childCount = getChildCount();
+ if (childCount > mMaxScrap) {
+ mMaxScrap = childCount;
+ }
+
+ ArrayList<View> scrap = mScrapViews[lp.viewType];
+ if (scrap.size() < mMaxScrap) {
+ scrap.add(v);
+ }
+ }
+
+ public View getTransientStateView(int position) {
+ if (mTransientStateViews == null) {
+ return null;
+ }
+
+ final View result = mTransientStateViews.get(position);
+ if (result != null) {
+ mTransientStateViews.remove(position);
+ }
+ return result;
+ }
+
+ public View getScrapView(int type) {
+ ArrayList<View> scrap = mScrapViews[type];
+ if (scrap.isEmpty()) {
+ return null;
+ }
+
+ final int index = scrap.size() - 1;
+ final View result = scrap.get(index);
+ scrap.remove(index);
+ return result;
+ }
+ }
+
+ private class AdapterDataSetObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ mDataChanged = true;
+ mOldItemCount = mItemCount;
+ mItemCount = mAdapter.getCount();
+
+ // TODO: Consider matching these back up if we have stable IDs.
+ mRecycler.clearTransientViews();
+
+ if (!mHasStableIds) {
+ recycleAllViews();
+ }
+
+ // TODO: consider repopulating in a deferred runnable instead
+ // (so that successive changes may still be batched)
+ requestLayout();
+ }
+
+ @Override
+ public void onInvalidated() {
+ }
+ }
+}
diff --git a/src/com/android/photos/views/TiledImageRenderer.java b/src/com/android/photos/views/TiledImageRenderer.java
new file mode 100644
index 000000000..a1f7107f2
--- /dev/null
+++ b/src/com/android/photos/views/TiledImageRenderer.java
@@ -0,0 +1,751 @@
+/*
+ * 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.photos.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.support.v4.util.LongSparseArray;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.UploadedTexture;
+import com.android.photos.data.GalleryBitmapPool;
+
+public class TiledImageRenderer {
+ public static final int SIZE_UNKNOWN = -1;
+
+ private static final String TAG = "TiledImageRenderer";
+ private static final int UPLOAD_LIMIT = 1;
+
+ /*
+ * This is the tile state in the CPU side.
+ * Life of a Tile:
+ * ACTIVATED (initial state)
+ * --> IN_QUEUE - by queueForDecode()
+ * --> RECYCLED - by recycleTile()
+ * IN_QUEUE --> DECODING - by decodeTile()
+ * --> RECYCLED - by recycleTile)
+ * DECODING --> RECYCLING - by recycleTile()
+ * --> DECODED - by decodeTile()
+ * --> DECODE_FAIL - by decodeTile()
+ * RECYCLING --> RECYCLED - by decodeTile()
+ * DECODED --> ACTIVATED - (after the decoded bitmap is uploaded)
+ * DECODED --> RECYCLED - by recycleTile()
+ * DECODE_FAIL -> RECYCLED - by recycleTile()
+ * RECYCLED --> ACTIVATED - by obtainTile()
+ */
+ private static final int STATE_ACTIVATED = 0x01;
+ private static final int STATE_IN_QUEUE = 0x02;
+ private static final int STATE_DECODING = 0x04;
+ private static final int STATE_DECODED = 0x08;
+ private static final int STATE_DECODE_FAIL = 0x10;
+ private static final int STATE_RECYCLING = 0x20;
+ private static final int STATE_RECYCLED = 0x40;
+
+ private static GalleryBitmapPool sTilePool = GalleryBitmapPool.getInstance();
+
+ // TILE_SIZE must be 2^N
+ private int mTileSize;
+
+ private TileSource mModel;
+ protected int mLevelCount; // cache the value of mScaledBitmaps.length
+
+ // The mLevel variable indicates which level of bitmap we should use.
+ // Level 0 means the original full-sized bitmap, and a larger value means
+ // a smaller scaled bitmap (The width and height of each scaled bitmap is
+ // half size of the previous one). If the value is in [0, mLevelCount), we
+ // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value
+ // is mLevelCount
+ private int mLevel = 0;
+
+ private int mOffsetX;
+ private int mOffsetY;
+
+ private int mUploadQuota;
+ private boolean mRenderComplete;
+
+ private final RectF mSourceRect = new RectF();
+ private final RectF mTargetRect = new RectF();
+
+ private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>();
+
+ // The following three queue are guarded by mQueueLock
+ private final Object mQueueLock = new Object();
+ private final TileQueue mRecycledQueue = new TileQueue();
+ private final TileQueue mUploadQueue = new TileQueue();
+ private final TileQueue mDecodeQueue = new TileQueue();
+
+ // The width and height of the full-sized bitmap
+ protected int mImageWidth = SIZE_UNKNOWN;
+ protected int mImageHeight = SIZE_UNKNOWN;
+
+ protected int mCenterX;
+ protected int mCenterY;
+ protected float mScale;
+ protected int mRotation;
+
+ private boolean mLayoutTiles;
+
+ // Temp variables to avoid memory allocation
+ private final Rect mTileRange = new Rect();
+ private final Rect mActiveRange[] = {new Rect(), new Rect()};
+
+ private TileDecoder mTileDecoder;
+ private boolean mBackgroundTileUploaded;
+
+ private int mViewWidth, mViewHeight;
+ private View mParent;
+
+ public static interface TileSource {
+ public int getTileSize();
+ public int getImageWidth();
+ public int getImageHeight();
+
+ // The tile returned by this method can be specified this way: Assuming
+ // the image size is (width, height), first take the intersection of (0,
+ // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If
+ // in extending the region, we found some part of the region is outside
+ // the image, those pixels are filled with black.
+ //
+ // If level > 0, it does the same operation on a down-scaled version of
+ // the original image (down-scaled by a factor of 2^level), but (x, y)
+ // still refers to the coordinate on the original image.
+ //
+ // The method would be called by the decoder thread.
+ public Bitmap getTile(int level, int x, int y, Bitmap reuse);
+ }
+
+ public static int suggestedTileSize(Context context) {
+ return isHighResolution(context) ? 512 : 256;
+ }
+
+ private static boolean isHighResolution(Context context) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager wm = (WindowManager)
+ context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(metrics);
+ return metrics.heightPixels > 2048 || metrics.widthPixels > 2048;
+ }
+
+ public TiledImageRenderer(View parent) {
+ mParent = parent;
+ mTileDecoder = new TileDecoder();
+ mTileDecoder.start();
+ }
+
+ public int getViewWidth() {
+ return mViewWidth;
+ }
+
+ public int getViewHeight() {
+ return mViewHeight;
+ }
+
+ private void invalidate() {
+ mParent.postInvalidate();
+ }
+
+ public void setModel(TileSource model, int rotation) {
+ if (mModel != model) {
+ mModel = model;
+ notifyModelInvalidated();
+ }
+ if (mRotation != rotation) {
+ mRotation = rotation;
+ mLayoutTiles = true;
+ invalidate();
+ }
+ }
+
+ private static int calulateLevelCount(TileSource source) {
+ int levels = 1;
+ int maxDim = Math.max(source.getImageWidth(), source.getImageHeight());
+ int t = source.getTileSize();
+ while (t < maxDim) {
+ t <<= 1;
+ levels++;
+ }
+ return levels;
+ }
+
+ public void notifyModelInvalidated() {
+ invalidateTiles();
+ if (mModel == null) {
+ mImageWidth = 0;
+ mImageHeight = 0;
+ mLevelCount = 0;
+ } else {
+ mImageWidth = mModel.getImageWidth();
+ mImageHeight = mModel.getImageHeight();
+ mLevelCount = calulateLevelCount(mModel);
+ mTileSize = mModel.getTileSize();
+ }
+ mLayoutTiles = true;
+ invalidate();
+ }
+
+ public void setViewSize(int width, int height) {
+ mViewWidth = width;
+ mViewHeight = height;
+ }
+
+ public void setPosition(int centerX, int centerY, float scale) {
+ if (mCenterX == centerX && mCenterY == centerY
+ && mScale == scale) return;
+ mCenterX = centerX;
+ mCenterY = centerY;
+ mScale = scale;
+ mLayoutTiles = true;
+ invalidate();
+ }
+
+ // Prepare the tiles we want to use for display.
+ //
+ // 1. Decide the tile level we want to use for display.
+ // 2. Decide the tile levels we want to keep as texture (in addition to
+ // the one we use for display).
+ // 3. Recycle unused tiles.
+ // 4. Activate the tiles we want.
+ private void layoutTiles() {
+ if (mViewWidth == 0 || mViewHeight == 0 || !mLayoutTiles) {
+ return;
+ }
+ mLayoutTiles = false;
+
+ // The tile levels we want to keep as texture is in the range
+ // [fromLevel, endLevel).
+ int fromLevel;
+ int endLevel;
+
+ // We want to use a texture larger than or equal to the display size.
+ mLevel = Utils.clamp(Utils.floorLog2(1f / mScale), 0, mLevelCount);
+
+ // We want to keep one more tile level as texture in addition to what
+ // we use for display. So it can be faster when the scale moves to the
+ // next level. We choose the level closest to the current scale.
+ if (mLevel != mLevelCount) {
+ Rect range = mTileRange;
+ getRange(range, mCenterX, mCenterY, mLevel, mScale, mRotation);
+ mOffsetX = Math.round(mViewWidth / 2f + (range.left - mCenterX) * mScale);
+ mOffsetY = Math.round(mViewHeight / 2f + (range.top - mCenterY) * mScale);
+ fromLevel = mScale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel;
+ } else {
+ // Activate the tiles of the smallest two levels.
+ fromLevel = mLevel - 2;
+ mOffsetX = Math.round(mViewWidth / 2f - mCenterX * mScale);
+ mOffsetY = Math.round(mViewHeight / 2f - mCenterY * mScale);
+ }
+
+ fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2));
+ endLevel = Math.min(fromLevel + 2, mLevelCount);
+
+ Rect range[] = mActiveRange;
+ for (int i = fromLevel; i < endLevel; ++i) {
+ getRange(range[i - fromLevel], mCenterX, mCenterY, i, mRotation);
+ }
+
+ // If rotation is transient, don't update the tile.
+ if (mRotation % 90 != 0) return;
+
+ synchronized (mQueueLock) {
+ mDecodeQueue.clean();
+ mUploadQueue.clean();
+ mBackgroundTileUploaded = false;
+
+ // Recycle unused tiles: if the level of the active tile is outside the
+ // range [fromLevel, endLevel) or not in the visible range.
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
+ int level = tile.mTileLevel;
+ if (level < fromLevel || level >= endLevel
+ || !range[level - fromLevel].contains(tile.mX, tile.mY)) {
+ mActiveTiles.removeAt(i);
+ i--;
+ n--;
+ recycleTile(tile);
+ }
+ }
+ }
+
+ for (int i = fromLevel; i < endLevel; ++i) {
+ int size = mTileSize << i;
+ Rect r = range[i - fromLevel];
+ for (int y = r.top, bottom = r.bottom; y < bottom; y += size) {
+ for (int x = r.left, right = r.right; x < right; x += size) {
+ activateTile(x, y, i);
+ }
+ }
+ }
+ invalidate();
+ }
+
+ private void invalidateTiles() {
+ synchronized (mQueueLock) {
+ mDecodeQueue.clean();
+ mUploadQueue.clean();
+
+ // TODO disable decoder
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
+ recycleTile(tile);
+ }
+ mActiveTiles.clear();
+ }
+ }
+
+ private void getRange(Rect out, int cX, int cY, int level, int rotation) {
+ getRange(out, cX, cY, level, 1f / (1 << (level + 1)), rotation);
+ }
+
+ // If the bitmap is scaled by the given factor "scale", return the
+ // rectangle containing visible range. The left-top coordinate returned is
+ // aligned to the tile boundary.
+ //
+ // (cX, cY) is the point on the original bitmap which will be put in the
+ // center of the ImageViewer.
+ private void getRange(Rect out,
+ int cX, int cY, int level, float scale, int rotation) {
+
+ double radians = Math.toRadians(-rotation);
+ double w = mViewWidth;
+ double h = mViewHeight;
+
+ double cos = Math.cos(radians);
+ double sin = Math.sin(radians);
+ int width = (int) Math.ceil(Math.max(
+ Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h)));
+ int height = (int) Math.ceil(Math.max(
+ Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h)));
+
+ int left = (int) Math.floor(cX - width / (2f * scale));
+ int top = (int) Math.floor(cY - height / (2f * scale));
+ int right = (int) Math.ceil(left + width / scale);
+ int bottom = (int) Math.ceil(top + height / scale);
+
+ // align the rectangle to tile boundary
+ int size = mTileSize << level;
+ left = Math.max(0, size * (left / size));
+ top = Math.max(0, size * (top / size));
+ right = Math.min(mImageWidth, right);
+ bottom = Math.min(mImageHeight, bottom);
+
+ out.set(left, top, right, bottom);
+ }
+
+ public void freeTextures() {
+ mLayoutTiles = true;
+
+ synchronized (mQueueLock) {
+ mUploadQueue.clean();
+ mDecodeQueue.clean();
+ Tile tile = mRecycledQueue.pop();
+ while (tile != null) {
+ tile.recycle();
+ tile = mRecycledQueue.pop();
+ }
+ }
+
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile texture = mActiveTiles.valueAt(i);
+ texture.recycle();
+ }
+ mActiveTiles.clear();
+ mTileRange.set(0, 0, 0, 0);
+
+ if (sTilePool != null) sTilePool.clear();
+ }
+
+ public void draw(GLCanvas canvas) {
+ layoutTiles();
+ uploadTiles(canvas);
+
+ mUploadQuota = UPLOAD_LIMIT;
+ mRenderComplete = true;
+
+ int level = mLevel;
+ int rotation = mRotation;
+ int flags = 0;
+ if (rotation != 0) flags |= GLCanvas.SAVE_FLAG_MATRIX;
+
+ if (flags != 0) {
+ canvas.save(flags);
+ if (rotation != 0) {
+ int centerX = mViewWidth / 2, centerY = mViewHeight / 2;
+ canvas.translate(centerX, centerY);
+ canvas.rotate(rotation, 0, 0, 1);
+ canvas.translate(-centerX, -centerY);
+ }
+ }
+ try {
+ if (level != mLevelCount) {
+ int size = (mTileSize << level);
+ float length = size * mScale;
+ Rect r = mTileRange;
+
+ for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) {
+ float y = mOffsetY + i * length;
+ for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) {
+ float x = mOffsetX + j * length;
+ drawTile(canvas, tx, ty, level, x, y, length);
+ }
+ }
+ }
+ } finally {
+ if (flags != 0) canvas.restore();
+ }
+
+ if (mRenderComplete) {
+ if (!mBackgroundTileUploaded) {
+ uploadBackgroundTiles(canvas);
+ }
+ } else {
+ invalidate();
+ }
+ }
+
+ private void uploadBackgroundTiles(GLCanvas canvas) {
+ mBackgroundTileUploaded = true;
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
+ if (!tile.isContentValid()) {
+ queueForDecode(tile);
+ }
+ }
+ }
+
+ private void queueForUpload(Tile tile) {
+ synchronized (mQueueLock) {
+ mUploadQueue.push(tile);
+ }
+ invalidate();
+ // TODO
+// if (mTileUploader.mActive.compareAndSet(false, true)) {
+// getGLRoot().addOnGLIdleListener(mTileUploader);
+// }
+ }
+
+ private void queueForDecode(Tile tile) {
+ synchronized (mQueueLock) {
+ if (tile.mTileState == STATE_ACTIVATED) {
+ tile.mTileState = STATE_IN_QUEUE;
+ if (mDecodeQueue.push(tile)) {
+ mQueueLock.notifyAll();
+ }
+ }
+ }
+ }
+
+ private boolean decodeTile(Tile tile) {
+ synchronized (mQueueLock) {
+ if (tile.mTileState != STATE_IN_QUEUE) return false;
+ tile.mTileState = STATE_DECODING;
+ }
+ boolean decodeComplete = tile.decode();
+ synchronized (mQueueLock) {
+ if (tile.mTileState == STATE_RECYCLING) {
+ tile.mTileState = STATE_RECYCLED;
+ if (tile.mDecodedTile != null) {
+ if (sTilePool != null) sTilePool.put(tile.mDecodedTile);
+ tile.mDecodedTile = null;
+ }
+ mRecycledQueue.push(tile);
+ return false;
+ }
+ tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL;
+ return decodeComplete;
+ }
+ }
+
+ private Tile obtainTile(int x, int y, int level) {
+ synchronized (mQueueLock) {
+ Tile tile = mRecycledQueue.pop();
+ if (tile != null) {
+ tile.mTileState = STATE_ACTIVATED;
+ tile.update(x, y, level);
+ return tile;
+ }
+ return new Tile(x, y, level);
+ }
+ }
+
+ private void recycleTile(Tile tile) {
+ synchronized (mQueueLock) {
+ if (tile.mTileState == STATE_DECODING) {
+ tile.mTileState = STATE_RECYCLING;
+ return;
+ }
+ tile.mTileState = STATE_RECYCLED;
+ if (tile.mDecodedTile != null) {
+ if (sTilePool != null) sTilePool.put(tile.mDecodedTile);
+ tile.mDecodedTile = null;
+ }
+ mRecycledQueue.push(tile);
+ }
+ }
+
+ private void activateTile(int x, int y, int level) {
+ long key = makeTileKey(x, y, level);
+ Tile tile = mActiveTiles.get(key);
+ if (tile != null) {
+ if (tile.mTileState == STATE_IN_QUEUE) {
+ tile.mTileState = STATE_ACTIVATED;
+ }
+ return;
+ }
+ tile = obtainTile(x, y, level);
+ mActiveTiles.put(key, tile);
+ }
+
+ private Tile getTile(int x, int y, int level) {
+ return mActiveTiles.get(makeTileKey(x, y, level));
+ }
+
+ private static long makeTileKey(int x, int y, int level) {
+ long result = x;
+ result = (result << 16) | y;
+ result = (result << 16) | level;
+ return result;
+ }
+
+ private void uploadTiles(GLCanvas canvas) {
+ int quota = UPLOAD_LIMIT;
+ Tile tile = null;
+ while (quota > 0) {
+ synchronized (mQueueLock) {
+ tile = mUploadQueue.pop();
+ }
+ if (tile == null) break;
+ if (!tile.isContentValid()) {
+ Utils.assertTrue(tile.mTileState == STATE_DECODED);
+ tile.updateContent(canvas);
+ --quota;
+ }
+ }
+ if (tile != null) {
+ invalidate();
+ }
+ }
+
+ // Draw the tile to a square at canvas that locates at (x, y) and
+ // has a side length of length.
+ private void drawTile(GLCanvas canvas,
+ int tx, int ty, int level, float x, float y, float length) {
+ RectF source = mSourceRect;
+ RectF target = mTargetRect;
+ target.set(x, y, x + length, y + length);
+ source.set(0, 0, mTileSize, mTileSize);
+
+ Tile tile = getTile(tx, ty, level);
+ if (tile != null) {
+ if (!tile.isContentValid()) {
+ if (tile.mTileState == STATE_DECODED) {
+ if (mUploadQuota > 0) {
+ --mUploadQuota;
+ tile.updateContent(canvas);
+ } else {
+ mRenderComplete = false;
+ }
+ } else if (tile.mTileState != STATE_DECODE_FAIL){
+ mRenderComplete = false;
+ queueForDecode(tile);
+ }
+ }
+ drawTile(tile, canvas, source, target);
+ }
+ }
+
+ private boolean drawTile(
+ Tile tile, GLCanvas canvas, RectF source, RectF target) {
+ while (true) {
+ if (tile.isContentValid()) {
+ canvas.drawTexture(tile, source, target);
+ return true;
+ }
+
+ // Parent can be divided to four quads and tile is one of the four.
+ Tile parent = tile.getParentTile();
+ if (parent == null) return false;
+ if (tile.mX == parent.mX) {
+ source.left /= 2f;
+ source.right /= 2f;
+ } else {
+ source.left = (mTileSize + source.left) / 2f;
+ source.right = (mTileSize + source.right) / 2f;
+ }
+ if (tile.mY == parent.mY) {
+ source.top /= 2f;
+ source.bottom /= 2f;
+ } else {
+ source.top = (mTileSize + source.top) / 2f;
+ source.bottom = (mTileSize + source.bottom) / 2f;
+ }
+ tile = parent;
+ }
+ }
+
+ private class Tile extends UploadedTexture {
+ public int mX;
+ public int mY;
+ public int mTileLevel;
+ public Tile mNext;
+ public Bitmap mDecodedTile;
+ public volatile int mTileState = STATE_ACTIVATED;
+
+ public Tile(int x, int y, int level) {
+ mX = x;
+ mY = y;
+ mTileLevel = level;
+ }
+
+ @Override
+ protected void onFreeBitmap(Bitmap bitmap) {
+ if (sTilePool != null) sTilePool.put(bitmap);
+ }
+
+ boolean decode() {
+ // Get a tile from the original image. The tile is down-scaled
+ // by (1 << mTilelevel) from a region in the original image.
+ try {
+ Bitmap reuse = sTilePool.get(mTileSize, mTileSize);
+ mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse);
+ } catch (Throwable t) {
+ Log.w(TAG, "fail to decode tile", t);
+ }
+ return mDecodedTile != null;
+ }
+
+ @Override
+ protected Bitmap onGetBitmap() {
+ Utils.assertTrue(mTileState == STATE_DECODED);
+
+ // We need to override the width and height, so that we won't
+ // draw beyond the boundaries.
+ int rightEdge = ((mImageWidth - mX) >> mTileLevel);
+ int bottomEdge = ((mImageHeight - mY) >> mTileLevel);
+ setSize(Math.min(mTileSize, rightEdge), Math.min(mTileSize, bottomEdge));
+
+ Bitmap bitmap = mDecodedTile;
+ mDecodedTile = null;
+ mTileState = STATE_ACTIVATED;
+ return bitmap;
+ }
+
+ // We override getTextureWidth() and getTextureHeight() here, so the
+ // texture can be re-used for different tiles regardless of the actual
+ // size of the tile (which may be small because it is a tile at the
+ // boundary).
+ @Override
+ public int getTextureWidth() {
+ return mTileSize;
+ }
+
+ @Override
+ public int getTextureHeight() {
+ return mTileSize;
+ }
+
+ public void update(int x, int y, int level) {
+ mX = x;
+ mY = y;
+ mTileLevel = level;
+ invalidateContent();
+ }
+
+ public Tile getParentTile() {
+ if (mTileLevel + 1 == mLevelCount) return null;
+ int size = mTileSize << (mTileLevel + 1);
+ int x = size * (mX / size);
+ int y = size * (mY / size);
+ return getTile(x, y, mTileLevel + 1);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("tile(%s, %s, %s / %s)",
+ mX / mTileSize, mY / mTileSize, mLevel, mLevelCount);
+ }
+ }
+
+ private static class TileQueue {
+ private Tile mHead;
+
+ public Tile pop() {
+ Tile tile = mHead;
+ if (tile != null) mHead = tile.mNext;
+ return tile;
+ }
+
+ public boolean push(Tile tile) {
+ boolean wasEmpty = mHead == null;
+ tile.mNext = mHead;
+ mHead = tile;
+ return wasEmpty;
+ }
+
+ public void clean() {
+ mHead = null;
+ }
+ }
+
+ private class TileDecoder extends Thread {
+
+ public void finishAndWait() {
+ interrupt();
+ try {
+ join();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted while waiting for TileDecoder thread to finish!");
+ }
+ }
+
+ private Tile waitForTile() throws InterruptedException {
+ synchronized(mQueueLock) {
+ while (true) {
+ Tile tile = mDecodeQueue.pop();
+ if (tile != null) {
+ return tile;
+ }
+ mQueueLock.wait();
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (!isInterrupted()) {
+ Tile tile = waitForTile();
+ if (decodeTile(tile)) {
+ queueForUpload(tile);
+ }
+ }
+ } catch (InterruptedException ex) {
+ }
+ }
+
+ }
+}
diff --git a/src/com/android/photos/views/TiledImageView.java b/src/com/android/photos/views/TiledImageView.java
new file mode 100644
index 000000000..6fe030dae
--- /dev/null
+++ b/src/com/android/photos/views/TiledImageView.java
@@ -0,0 +1,269 @@
+/*
+ * 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.photos.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.opengl.GLSurfaceView.Renderer;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.widget.FrameLayout;
+import com.android.gallery3d.glrenderer.GLES20Canvas;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+
+public class TiledImageView extends FrameLayout implements OnScaleGestureListener {
+
+ private BlockingGLTextureView mTextureView;
+ private float mLastX, mLastY;
+
+ private static class ImageRendererWrapper {
+ // Guarded by locks
+ float scale;
+ int centerX, centerY;
+ int rotation;
+ TileSource source;
+
+ // GL thread only
+ TiledImageRenderer image;
+ }
+
+ // TODO: left/right paging
+ private ImageRendererWrapper mRenderers[] = new ImageRendererWrapper[1];
+ private ImageRendererWrapper mFocusedRenderer;
+
+ // -------------------------
+ // Guarded by mLock
+ // -------------------------
+ private Object mLock = new Object();
+ private ScaleGestureDetector mScaleGestureDetector;
+
+ public TiledImageView(Context context) {
+ this(context, null);
+ }
+
+ public TiledImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mTextureView = new BlockingGLTextureView(context);
+ addView(mTextureView, new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ mTextureView.setRenderer(new TileRenderer());
+ setTileSource(new ColoredTiles());
+ mScaleGestureDetector = new ScaleGestureDetector(context, this);
+ }
+
+ public void destroy() {
+ mTextureView.destroy();
+ }
+
+ public void setTileSource(TileSource source) {
+ synchronized (mLock) {
+ for (int i = 0; i < mRenderers.length; i++) {
+ ImageRendererWrapper renderer = mRenderers[i];
+ if (renderer == null) {
+ renderer = mRenderers[i] = new ImageRendererWrapper();
+ }
+ renderer.source = source;
+ renderer.centerX = renderer.source.getImageWidth() / 2;
+ renderer.centerY = renderer.source.getImageHeight() / 2;
+ renderer.rotation = 0;
+ renderer.scale = 0;
+ renderer.image = new TiledImageRenderer(this);
+ updateScaleIfNecessaryLocked(renderer);
+ }
+ }
+ mFocusedRenderer = mRenderers[0];
+ invalidate();
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return true;
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ // Don't need the lock because this will only fire inside of onTouchEvent
+ mFocusedRenderer.scale *= detector.getScaleFactor();
+ invalidate();
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int action = event.getActionMasked();
+ final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+ final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+ // Determine focal point
+ float sumX = 0, sumY = 0;
+ final int count = event.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ if (skipIndex == i) continue;
+ sumX += event.getX(i);
+ sumY += event.getY(i);
+ }
+ final int div = pointerUp ? count - 1 : count;
+ float x = sumX / div;
+ float y = sumY / div;
+
+ synchronized (mLock) {
+ mScaleGestureDetector.onTouchEvent(event);
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ mFocusedRenderer.centerX += (mLastX - x) / mFocusedRenderer.scale;
+ mFocusedRenderer.centerY += (mLastY - y) / mFocusedRenderer.scale;
+ invalidate();
+ break;
+ }
+ }
+
+ mLastX = x;
+ mLastY = y;
+ return true;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right,
+ int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ synchronized (mLock) {
+ for (ImageRendererWrapper renderer : mRenderers) {
+ updateScaleIfNecessaryLocked(renderer);
+ }
+ }
+ }
+
+ private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
+ if (renderer.scale > 0 || getWidth() == 0) return;
+ renderer.scale = Math.min(
+ (float) getWidth() / (float) renderer.source.getImageWidth(),
+ (float) getHeight() / (float) renderer.source.getImageHeight());
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ mTextureView.render();
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ mTextureView.invalidate();
+ }
+
+ private class TileRenderer implements Renderer {
+
+ private GLES20Canvas mCanvas;
+
+ @Override
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ mCanvas = new GLES20Canvas();
+ for (ImageRendererWrapper renderer : mRenderers) {
+ renderer.image.setModel(renderer.source, renderer.rotation);
+ }
+ }
+
+ @Override
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+ mCanvas.setSize(width, height);
+ for (ImageRendererWrapper renderer : mRenderers) {
+ renderer.image.setViewSize(width, height);
+ }
+ }
+
+ @Override
+ public void onDrawFrame(GL10 gl) {
+ mCanvas.clearBuffer();
+ synchronized (mLock) {
+ for (ImageRendererWrapper renderer : mRenderers) {
+ renderer.image.setModel(renderer.source, renderer.rotation);
+ renderer.image.setPosition(renderer.centerX, renderer.centerY, renderer.scale);
+ }
+ }
+ for (ImageRendererWrapper renderer : mRenderers) {
+ renderer.image.draw(mCanvas);
+ }
+ }
+
+ }
+
+ private static class ColoredTiles implements TileSource {
+ private static int[] COLORS = new int[] {
+ Color.RED,
+ Color.BLUE,
+ Color.YELLOW,
+ Color.GREEN,
+ Color.CYAN,
+ Color.MAGENTA,
+ Color.WHITE,
+ };
+
+ private Paint mPaint = new Paint();
+ private Canvas mCanvas = new Canvas();
+
+ @Override
+ public int getTileSize() {
+ return 256;
+ }
+
+ @Override
+ public int getImageWidth() {
+ return 16384;
+ }
+
+ @Override
+ public int getImageHeight() {
+ return 8192;
+ }
+
+ @Override
+ public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+ int tileSize = getTileSize();
+ if (bitmap == null) {
+ bitmap = Bitmap.createBitmap(tileSize, tileSize,
+ Bitmap.Config.ARGB_8888);
+ }
+ mCanvas.setBitmap(bitmap);
+ mCanvas.drawColor(COLORS[level]);
+ mPaint.setColor(Color.BLACK);
+ mPaint.setTextSize(20);
+ mPaint.setTextAlign(Align.CENTER);
+ mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
+ tileSize <<= level;
+ x /= tileSize;
+ y /= tileSize;
+ mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
+ mCanvas.setBitmap(null);
+ return bitmap;
+ }
+ }
+}
diff --git a/src/com/google/android/canvas/data/Cluster.java b/src/com/google/android/pano/data/Cluster.java
index ab6aaedcc..d4a2c1c08 100644
--- a/src/com/google/android/canvas/data/Cluster.java
+++ b/src/com/google/android/pano/data/Cluster.java
@@ -1,6 +1,6 @@
// Copyright 2012 Google Inc. All Rights Reserved.
-package com.google.android.canvas.data;
+package com.google.android.pano.data;
import android.content.Intent;
import android.net.Uri;
@@ -21,6 +21,7 @@ public class Cluster {
private boolean mImageCropAllowed;
private long mCacheTimeMs;
private Intent mIntent;
+ private Uri mBrowseItemsUri;
private List<ClusterItem> mClusterItems;
@@ -30,7 +31,7 @@ public class Cluster {
public static class ClusterItem {
private Uri mImageUri;
- ClusterItem(Uri imageUri) {
+ public ClusterItem(Uri imageUri) {
mImageUri = imageUri;
}
@@ -44,6 +45,31 @@ public class Cluster {
builder.append("imageUri: ").append(mImageUri);
return builder.toString();
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mImageUri == null) ? 0 : mImageUri.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ClusterItem other = (ClusterItem) obj;
+ if (mImageUri == null) {
+ if (other.mImageUri != null)
+ return false;
+ } else if (!mImageUri.equals(other.mImageUri))
+ return false;
+ return true;
+ }
}
public Cluster() {
@@ -83,6 +109,10 @@ public class Cluster {
return mIntent;
}
+ public Uri getBrowseItemsUri() {
+ return mBrowseItemsUri;
+ }
+
public int getItemCount() {
return mClusterItems.size();
}
@@ -94,10 +124,85 @@ public class Cluster {
return null;
}
- void addClusterItem(ClusterItem item) {
+ public void addClusterItem(ClusterItem item) {
mClusterItems.add(item);
}
+ public void clearItems() {
+ mClusterItems.clear();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (mCacheTimeMs ^ (mCacheTimeMs >>> 32));
+ if (mClusterItems == null) {
+ result = prime * result;
+ } else {
+ for (ClusterItem ci : mClusterItems) {
+ result = prime * result + ci.hashCode();
+ }
+ }
+ result = prime * result + ((mDisplayName == null) ? 0 : mDisplayName.toString().hashCode());
+ result = prime * result + (int) (mId ^ (mId >>> 32));
+ result = prime * result + (mImageCropAllowed ? 1231 : 1237);
+ result = prime * result + mImportance;
+ result = prime * result + ((mIntent == null) ? 0 : mIntent.hashCode());
+ result = prime * result + ((mName == null) ? 0 : mName.hashCode());
+ result = prime * result + mVisibleCount;
+ result = prime * result + ((mBrowseItemsUri == null) ? 0 : mBrowseItemsUri.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Cluster other = (Cluster) obj;
+ if (mCacheTimeMs != other.mCacheTimeMs)
+ return false;
+ if (mClusterItems == null) {
+ if (other.mClusterItems != null)
+ return false;
+ } else if (!mClusterItems.equals(other.mClusterItems))
+ return false;
+ if (mDisplayName == null) {
+ if (other.mDisplayName != null)
+ return false;
+ } else if (!mDisplayName.equals(other.mDisplayName))
+ return false;
+ if (mId != other.mId)
+ return false;
+ if (mImageCropAllowed != other.mImageCropAllowed)
+ return false;
+ if (mImportance != other.mImportance)
+ return false;
+ if (mIntent == null) {
+ if (other.mIntent != null)
+ return false;
+ } else if (!mIntent.equals(other.mIntent))
+ return false;
+ if (mName == null) {
+ if (other.mName != null)
+ return false;
+ } else if (!mName.equals(other.mName))
+ return false;
+ if (mVisibleCount != other.mVisibleCount)
+ return false;
+ if (mBrowseItemsUri == null) {
+ if (other.mBrowseItemsUri != null)
+ return false;
+ } else if (!mBrowseItemsUri.equals(other.mBrowseItemsUri)) {
+ return false;
+ }
+ return true;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -108,7 +213,9 @@ public class Cluster {
.append(", visibleCount: ").append(mVisibleCount)
.append(", imageCropAllowed: ").append(mImageCropAllowed)
.append(", cacheTimeMs: ").append(mCacheTimeMs)
- .append(", intent: ").append(mIntent.toUri(0));
+ .append(", clusterItems: ").append(mClusterItems)
+ .append(", intent: ").append(mIntent != null ? mIntent.toUri(0) : "")
+ .append(", browseItems: ").append(mBrowseItemsUri != null ? mBrowseItemsUri : "");
return builder.toString();
}
@@ -124,6 +231,7 @@ public class Cluster {
private boolean mImageCropAllowed;
private long mCacheTimeMs;
private Intent mIntent;
+ private Uri mBrowseItemsUri;
private List<ClusterItem> mClusterItems;
@@ -138,6 +246,7 @@ public class Cluster {
cluster.mIntent = mIntent;
cluster.mCacheTimeMs = mCacheTimeMs;
cluster.mClusterItems.addAll(mClusterItems);
+ cluster.mBrowseItemsUri = mBrowseItemsUri;
return cluster;
}
@@ -186,6 +295,11 @@ public class Cluster {
return this;
}
+ public Builder browseItemsUri(Uri uri) {
+ mBrowseItemsUri = uri;
+ return this;
+ }
+
public Builder addItem(Uri imageUri) {
ClusterItem item = new ClusterItem(imageUri);
mClusterItems.add(item);
diff --git a/src/com/google/android/canvas/data/util/UriUtils.java b/src/com/google/android/pano/data/util/UriUtils.java
index 7b7b73cb5..5d973c16c 100644
--- a/src/com/google/android/canvas/data/util/UriUtils.java
+++ b/src/com/google/android/pano/data/util/UriUtils.java
@@ -1,6 +1,6 @@
// Copyright 2012 Google Inc. All Rights Reserved.
-package com.google.android.canvas.data.util;
+package com.google.android.pano.data.util;
import android.content.ContentResolver;
import android.content.Context;
@@ -83,6 +83,15 @@ public final class UriUtils {
}
/**
+ * Returns {@code true} if the URI refers to a content URI which can be opened via
+ * {@link ContentResolver#openInputStream(Uri)}.
+ */
+ public static boolean isContentUri(Uri uri) {
+ return ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) ||
+ ContentResolver.SCHEME_FILE.equals(uri.getScheme());
+ }
+
+ /**
* Checks if the URI refers to an shortcut icon resource.
*/
public static boolean isShortcutIconResourceUri(Uri uri) {
@@ -118,7 +127,8 @@ public final class UriUtils {
* Returns {@code true} if this is a web URI.
*/
public static boolean isWebUri(Uri resourceUri) {
- String scheme = resourceUri.getScheme().toLowerCase();
+ String scheme = resourceUri.getScheme() == null ? null
+ : resourceUri.getScheme().toLowerCase();
return HTTP_PREFIX.equals(scheme) || HTTPS_PREFIX.equals(scheme);
}
}
diff --git a/src/com/google/android/canvas/provider/CanvasContract.java b/src/com/google/android/pano/provider/PanoContract.java
index f6a1741c4..cfce17a17 100644
--- a/src/com/google/android/canvas/provider/CanvasContract.java
+++ b/src/com/google/android/pano/provider/PanoContract.java
@@ -1,4 +1,4 @@
-package com.google.android.canvas.provider;
+package com.google.android.pano.provider;
import android.content.ContentUris;
import android.content.Intent;
@@ -6,13 +6,13 @@ import android.net.Uri;
import android.provider.BaseColumns;
/**
- * The contract between Canvas and ContentProviders that allow access to Canvas
- * browsing data. All apps that wish to interact with Canvas should use these
+ * The contract between Pano and ContentProviders that allow access to Pano
+ * browsing data. All apps that wish to interact with Pano should use these
* definitions.
*
* TODO add more details
*/
-public final class CanvasContract {
+public final class PanoContract {
// Base for content uris
public static final String CONTENT = "content://";
@@ -30,37 +30,50 @@ public final class CanvasContract {
public static final String PATH_BROWSE_HEADERS = "headers";
/**
- * This tag is used to identify the authority to be used for a canvas
- * launcher app.
- *
- * TODO: this is obsolete: remove.
+ * Pano will search for activities with the action {@link Intent#ACTION_MAIN} and this
+ * category in order to find the activities for the home screen.
*/
- public static final String METADATA_TAG = "com.google.android.canvas.data.launcher";
+ public static final String CATEGORY_BROWSE_LAUNCHER =
+ "com.google.android.pano.category.BROWSE_LAUNCHER";
/**
- * This tag is used to identify the launcher info data file to be used for a canvas
+ * This tag is used to identify the launcher info data file to be used for a Pano
* launcher app.
*/
public static final String METADATA_LAUNCHER_INFO_TAG =
- "com.google.android.canvas.data.launcher_info";
+ "com.google.android.pano.data.launcher_info";
+
+ /**
+ * An intent action for browsing app content in Pano. Apps receiving this
+ * intent should call {@link Intent#getData()} to retrieve the base Uri and
+ * {@link #EXTRA_START_INDEX} or {@link #EXTRA_START_ID} to find which header
+ * to start at (default 0).
+ */
+ public static final String ACTION_BROWSE = "com.google.android.pano.action.BROWSE";
/**
- * This tag is used to denote a background color hint for the activity.
+ * An intent action for picking app content in Pano.
* <p>
- * This can either be a reference to an @color or else a string (e.g. #ff001100).
- *
- * TODO: this is obsolete: remove.
+ * Any intents launched from here will be returned to the calling activity instead of being
+ * directly launched.
+ * <p>
+ * This can be used to select an item.
*/
- public static final String METADATA_COLOR_HINT =
- "com.google.android.canvas.ui.launcher_color_hint";
+ public static final String ACTION_BROWSE_PICKER =
+ "com.google.android.pano.action.BROWSE_PICKER";
/**
- * An intent action for browsing app content in Canvas. Apps receiving this
- * intent should call {@link Intent#getData()} to retrieve the base Uri and
- * {@link #EXTRA_START_INDEX} or {@link #EXTRA_START_ID} to find which header
- * to start at (default 0).
+ * An intent action for picking app content while keeping the launching app running.
+ * <p>
+ * Pano Browse will appear over the app.
+ * <p>
+ * Any intents launched from here will be returned to the calling activity instead of being
+ * directly launched.
+ * <p>
+ * This can be used to select an item.
*/
- public static final String ACTION_BROWSE = "com.google.android.canvas.action.BROWSE";
+ public static final String ACTION_BROWSE_PICKER_TRANSLUCENT =
+ "com.google.android.pano.action.BROWSE_PICKER_TRANSLUCENT";
/**
* The index of the header to focus on initially when the browse is launched.
@@ -76,10 +89,25 @@ public final class CanvasContract {
public static final String EXTRA_START_ID = "start_id";
/**
- * An intent action for viewing detail content in Canvas. Apps receiving this
+ * An intent action for viewing detail content in Pano. Apps receiving this
* intent should call {@link Intent#getData()} to retrieve the base Uri.
*/
- public static final String ACTION_DETAIL = "com.google.android.canvas.action.DETAIL";
+ public static final String ACTION_DETAIL = "com.google.android.pano.action.DETAIL";
+
+ /**
+ * The name of the section to focus on initially when {@link #ACTION_DETAIL} is launched.
+ * <p>
+ * Using name allows targeting a sub section.
+ */
+ public static final String EXTRA_START_NAME = "start_name";
+
+ /**
+ * Index of a child to focus on initially when {@link #ACTION_DETAIL} is launched.
+ * <p>
+ * Requires a start section to be specified using {@link #EXTRA_START_INDEX} or
+ * {@link #EXTRA_START_NAME}.
+ */
+ public static final String EXTRA_START_CHILD_INDEX = "start_child_index";
/**
* Path for querying details for an item.
@@ -99,11 +127,33 @@ public final class CanvasContract {
public static final String PATH_DETAIL_ACTIONS = "actions";
/**
- * Action for searching a Canvas provider. Apps receiving this
+ * Action for searching a Pano provider. Apps receiving this
* intent should call {@link Intent#getData()} to retrieve the base Uri and
* {@link #EXTRA_QUERY} to find query.
*/
- public static final String ACTION_SEARCH = "com.google.android.canvas.action.SEARCH";
+ public static final String ACTION_SEARCH = "com.google.android.pano.action.SEARCH";
+
+ /**
+ * Action for searching a Pano provider.
+ * <p>
+ * Any intent selected off this activity will be returned to the calling activity instead of
+ * being launched directly.
+ */
+ public static final String ACTION_SEARCH_PICKER =
+ "com.google.android.pano.action.SEARCH_PICKER";
+
+ /**
+ * An intent action for searching app content while keeping the launching app running.
+ * <p>
+ * Pano Search will appear over the app.
+ * <p>
+ * Any intents launched from here will be returned to the calling activity instead of being
+ * directly launched.
+ * <p>
+ * This can be used to select an item.
+ */
+ public static final String ACTION_SEARCH_PICKER_TRANSLUCENT =
+ "com.google.android.pano.action.SEARCH_PICKER_TRANSLUCENT";
/**
* The query to be executed when search activity is launched
@@ -112,6 +162,13 @@ public final class CanvasContract {
public static final String EXTRA_QUERY = "query";
/**
+ * Optional String extra for meta information. This must be supplied as a string, but must be a valid URI.
+ * <p>
+ * Used with {@link #ACTION_SEARCH}.
+ */
+ public static final String EXTRA_META_URI = "meta_uri";
+
+ /**
* Optional int extra for setting the display mode of the search activity.
*
* @see #DISPLAY_MODE_ROW
@@ -121,9 +178,10 @@ public final class CanvasContract {
public static final int DISPLAY_MODE_ROW = 0;
public static final int DISPLAY_MODE_GRID = 1;
+ public static final int DISPLAY_MODE_BROWSE = 2;
/**
- * Value for the root Canvas URI when this activity should be excluded from the Canvas top level
+ * Value for the root Pano URI when this activity should be excluded from the Pano top level
* and the legacy apps area.
*
* TODO: this is obsolete: remove.
@@ -187,10 +245,27 @@ public final class CanvasContract {
public static final String CACHE_TIME_MS = "cache_time_ms";
/**
+ * Content URI pointing to a list of items in the {@link PanoContract.BrowseItemsColumns}
+ * schema.
+ * <p>
+ * This is optional but highly recommended if the cluster represents a browse row.
+ * <p>
+ * In this case, the cluster items will be read from this URI instead of from the cluster
+ * items.
+ * <p>
+ * If this is filled in, the intent_uri must have the action
+ * {@link PanoContract#ACTION_BROWSE}. The row with this URI will automatically be
+ * selected when the activity starts up.
+ *
+ * <P>Type: String (Uri)</P>
+ */
+ public static final String BROWSE_ITEMS_URI = "browse_items_uri";
+
+ /**
* A standard Intent Uri to be launched when this cluster is selected.
- * This may be a {@link CanvasContract#ACTION_BROWSE} intent or an
+ * This may be a {@link PanoContract#ACTION_BROWSE} intent or an
* intent to launch directly into an app. You can also use
- * {@link CanvasContract#getBrowseIntent(Uri, int)} to generate a
+ * {@link PanoContract#getBrowseIntent(Uri, int)} to generate a
* browse intent for a given root Uri. Use {@link Intent#toUri(int)}
* with a flag of {@link Intent#URI_INTENT_SCHEME}.
*
@@ -208,7 +283,7 @@ public final class CanvasContract {
public static final String NOTIFICATION_TEXT = "notification_text";
/**
- * An optional Uri for querying progresss for any ongoing actions, such
+ * An optional Uri for querying progress for any ongoing actions, such
* as an active download.
*
* <P>Type: String (Uri)</P>
@@ -232,6 +307,16 @@ public final class CanvasContract {
* <P>Type: INTEGER</P>
*/
public static final String PROGRESS = "progress";
+
+ /**
+ * The smallest value that is a valid {@link #PROGRESS}.
+ */
+ public static final int PROGRESS_MIN = 0;
+
+ /**
+ * The largest value that is a valid {@link #PROGRESS}.
+ */
+ public static final int PROGRESS_MAX = 100;
}
public static final class Progress implements BaseColumns, ProgressColumns {
@@ -305,6 +390,16 @@ public final class CanvasContract {
public static final String DISPLAY_NAME = "display_name";
/**
+ * Optional Uri pointing to the data for the items for this header.
+ * <p>
+ * If this is not provided, a Uri will be constructed using
+ * {@link BrowseItems#getBrowseItemsUri(Uri, long)}.
+ *
+ * <P>Type: String</P>
+ */
+ public static final String ITEMS_URI = "items_uri";
+
+ /**
* Uri pointing to an icon to be used as part of the header. This
* String should be generated using {@link Uri#toString()}
*
@@ -346,6 +441,20 @@ public final class CanvasContract {
*
* <P>Type: String (Uri)</P>
*/
+ public static final String BACKGROUND_IMAGE_URI = "background_image_uri";
+
+ /**
+ * Uri pointing to an image to display in the background when on this
+ * tab. Be sure the image contrasts enough with the text color hint and
+ * is of high enough quality to be displayed at 1080p. This String
+ * should be generated using {@link Uri#toString()}. The URI will be either
+ * a resource uri in format of android:resource:// or an external URL
+ * like file://, http://, https://.
+ *
+ * <P>Type: String (Uri)</P>
+ * <P>This is the obsolete version of {@link #BACKGROUND_IMAGE_URI}</P>
+ * TODO: remove obsolete version when all clients have upgraded.
+ */
public static final String BG_IMAGE_URI = "bg_image_uri";
/**
@@ -481,22 +590,30 @@ public final class CanvasContract {
protected interface UserRatingColumns {
/**
- * The average rating for this item. (Optional)
- *
- * <P>Type: Double</P>
+ * A custom rating String for this item, such as "78 points" or
+ * "20/100". (Optional)
+ * <p>
+ * A null or the absence of this column indicates there is no custom
+ * rating available.
+ * <P>Type: String</P>
*/
- public static final String USER_RATING_AVERAGE = "user_rating_average";
+ public static final String USER_RATING_CUSTOM = "user_rating_custom";
/**
- * A simple rating for this item as an integer in the range
- * [0-10] inclusive. (Optional)
- *
- * <P>Type: INTEGER</P>
+ * A scaled rating for this item as a float in the range [0-10]
+ * inclusive. Pano will be responsible for visualizing this
+ * value.(Optional)
+ * <p>
+ * A -1 or the absence of this column indicates there is no rating
+ * available.
+ * <P>Type: FLOAT</P>
*/
- public static final String USER_RATING_SIMPLE = "user_rating_simple";
+ public static final String USER_RATING = "user_rating";
/**
* The number of reviews included in the average rating. (Optional)
+ * <p>
+ * A value of 0 indicates the count is not available.
*
* <P>Type: INTEGER</P>
*/
@@ -568,6 +685,13 @@ public final class CanvasContract {
protected interface DetailSectionsColumns {
/**
+ * Text ID for a section. Can be used to target the section
+ *
+ * <P>Type: String</P>
+ */
+ public static final String NAME = "name";
+
+ /**
* Text that will be shown to the user for navigating between sections.
*
* <P>Type: String</P>
@@ -776,6 +900,23 @@ public final class CanvasContract {
public static final String DISPLAY_NUMBER = "display_number";
/**
+ * Hint for how to display the item. This is either {@link #ITEM_DISPLAY_TYPE_NORMAL} or
+ * {@link #ITEM_DISPLAY_TYPE_SINGLE_LINE}.
+ */
+ public static final String ITEM_DISPLAY_TYPE = "item_display_type";
+
+ /**
+ * Value for {@link #ITEM_DISPLAY_TYPE} which allows for multiple lines.
+ */
+ public static final int ITEM_DISPLAY_TYPE_NORMAL = 0;
+
+ /**
+ * Value for {@link #ITEM_DISPLAY_TYPE} which hints that the content should be displayed
+ * on a single line.
+ */
+ public static final int ITEM_DISPLAY_TYPE_SINGLE_LINE = 1;
+
+ /**
* The uri for retrieving the image to show for this item. This string
* should be created using {@link Uri#toString()}. (Optional)
*
@@ -795,6 +936,23 @@ public final class CanvasContract {
public static final String ACTION_URI = "action_uri";
}
+ protected interface SearchBrowseResult {
+ public static final String RESULTS_URI = "results_uri";
+ /**
+ * The default width of the expanded image
+ *
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DEFAULT_ITEM_WIDTH = "default_item_width";
+
+ /**
+ * The default height of the expanded image
+ *
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DEFAULT_ITEM_HEIGHT = "default_item_height";
+ }
+
public static final class DetailChildren
implements BaseColumns, ItemChildrenColumns, UserRatingColumns {
@@ -805,7 +963,7 @@ public final class CanvasContract {
}
public static final class SearchResults
- implements BaseColumns, ItemChildrenColumns, UserRatingColumns {
+ implements BaseColumns, ItemChildrenColumns, UserRatingColumns, SearchBrowseResult {
/**
* Non instantiable.
@@ -813,6 +971,40 @@ public final class CanvasContract {
private SearchResults() {}
}
+ protected interface MetaColumns {
+
+ /**
+ * The uri for retrieving the background image to show for this item. This string
+ * should be created using {@link Uri#toString()}.
+ *
+ * <P>Type: String (Uri)</P>
+ */
+ public static final String BACKGROUND_IMAGE_URI = "background_image_uri";
+ /**
+ * Uri pointing to an icon to be used for app branding on this tab.
+ * This String should be generated using {@link Uri#toString()}
+ *
+ * <P>Type: String (Uri)</P>
+ */
+ public static final String BADGE_URI = "badge_uri";
+
+ /**
+ * A 0xAARRGGBB color that should be applied to the background when on
+ * this tab.
+ *
+ * <P>Type: INTEGER</P>
+ */
+ public static final String COLOR_HINT = "color_hint";
+ }
+
+ public static final class MetaSchema implements BaseColumns, MetaColumns {
+
+ /**
+ * Non instantiable.
+ */
+ private MetaSchema() {}
+ }
+
protected interface DetailActionsColumns {
/**
@@ -892,4 +1084,4 @@ public final class CanvasContract {
intent.setData(root);
return intent;
}
-} \ No newline at end of file
+}
diff --git a/src_pd/com/android/gallery3d/filtershow/editors/EditorManager.java b/src_pd/com/android/gallery3d/filtershow/editors/EditorManager.java
index 92962cbb1..2a39b688f 100644
--- a/src_pd/com/android/gallery3d/filtershow/editors/EditorManager.java
+++ b/src_pd/com/android/gallery3d/filtershow/editors/EditorManager.java
@@ -29,6 +29,10 @@ public class EditorManager {
editorPlaceHolder.addEditor(new EditorTinyPlanet());
editorPlaceHolder.addEditor(new EditorDraw());
editorPlaceHolder.addEditor(new EditorVignette());
+ editorPlaceHolder.addEditor(new EditorFlip());
+ editorPlaceHolder.addEditor(new EditorRotate());
+ editorPlaceHolder.addEditor(new EditorStraighten());
+ editorPlaceHolder.addEditor(new EditorCrop());
}
}
diff --git a/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java b/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java
index 988cf2d81..d6b871899 100644
--- a/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java
+++ b/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java
@@ -16,13 +16,26 @@
package com.android.gallery3d.filtershow.filters;
+import java.util.HashMap;
+import java.util.Vector;
+
public class FiltersManager extends BaseFiltersManager {
- private static FiltersManager gInstance = null;
+ private static FiltersManager sInstance = null;
+
+ protected FiltersManager() {
+ mFilters = new HashMap<Class, ImageFilter>();
+ addFilters(mFilters);
+ }
public static FiltersManager getManager() {
- if (gInstance == null) {
- gInstance = new FiltersManager();
+ if (sInstance == null) {
+ sInstance = new FiltersManager();
}
- return gInstance;
+ return sInstance;
}
+
+ public static void reset() {
+ sInstance = null;
+ }
+
}
diff --git a/src_pd/com/android/photos/data/PhotoProviderAuthority.java b/src_pd/com/android/photos/data/PhotoProviderAuthority.java
new file mode 100644
index 000000000..0ac76cb0e
--- /dev/null
+++ b/src_pd/com/android/photos/data/PhotoProviderAuthority.java
@@ -0,0 +1,21 @@
+/*
+ * 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.photos.data;
+
+interface PhotoProviderAuthority {
+ public static final String AUTHORITY = "com.android.gallery3d.photoprovider";
+}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index ef63cc414..b98b5e0b8 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -36,4 +36,8 @@
<instrumentation android:name="com.android.gallery3d.stress.CameraStressTestRunner"
android:targetPackage="com.android.gallery3d"
android:label="Camera stress test runner"/>
+
+ <instrumentation android:name="com.android.photos.data.DataTestRunner"
+ android:targetPackage="com.android.gallery3d"
+ android:label="Tests for android photo DataProviders."/>
</manifest>
diff --git a/tests/src/com/android/photos/data/DataTestRunner.java b/tests/src/com/android/photos/data/DataTestRunner.java
new file mode 100644
index 000000000..432258561
--- /dev/null
+++ b/tests/src/com/android/photos/data/DataTestRunner.java
@@ -0,0 +1,36 @@
+/*
+ * 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.photos.data;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+import junit.framework.TestSuite;
+
+public class DataTestRunner extends InstrumentationTestRunner {
+ @Override
+ public TestSuite getAllTests() {
+ TestSuite suite = new InstrumentationTestSuite(this);
+ suite.addTestSuite(PhotoDatabaseTest.class);
+ suite.addTestSuite(PhotoProviderTest.class);
+ return suite;
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ return DataTestRunner.class.getClassLoader();
+ }
+}
diff --git a/tests/src/com/android/photos/data/PhotoDatabaseTest.java b/tests/src/com/android/photos/data/PhotoDatabaseTest.java
new file mode 100644
index 000000000..70edee212
--- /dev/null
+++ b/tests/src/com/android/photos/data/PhotoDatabaseTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.photos.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.test.InstrumentationTestCase;
+
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.io.File;
+import java.io.IOException;
+
+public class PhotoDatabaseTest extends InstrumentationTestCase {
+
+ private PhotoDatabase mDBHelper;
+ private static final String DB_NAME = "dummy.db";
+ private static final long PARENT_ID1 = 100;
+ private static final long PARENT_ID2 = 101;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Context context = getInstrumentation().getTargetContext();
+ context.deleteDatabase(DB_NAME);
+ mDBHelper = new PhotoDatabase(context, DB_NAME);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDBHelper.close();
+ mDBHelper = null;
+ Context context = getInstrumentation().getTargetContext();
+ context.deleteDatabase(DB_NAME);
+ super.tearDown();
+ }
+
+ public void testCreateDatabase() throws IOException {
+ Context context = getInstrumentation().getTargetContext();
+ File dbFile = context.getDatabasePath(DB_NAME);
+ SQLiteDatabase db = getReadableDB();
+ db.beginTransaction();
+ db.endTransaction();
+ assertTrue(dbFile.exists());
+ }
+
+ public void testTables() {
+ validateTable(Metadata.TABLE, PhotoDatabaseUtils.PROJECTION_METADATA);
+ validateTable(Albums.TABLE, PhotoDatabaseUtils.PROJECTION_ALBUMS);
+ validateTable(Photos.TABLE, PhotoDatabaseUtils.PROJECTION_PHOTOS);
+ }
+
+ public void testAlbumsConstraints() {
+ SQLiteDatabase db = getWriteableDB();
+ db.beginTransaction();
+ try {
+ long accountId = 100;
+ // Test NOT NULL constraint on name
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, null, Albums.VISIBILITY_PRIVATE,
+ accountId));
+
+ // test NOT NULL constraint on privacy
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello", null, accountId));
+
+ // test NOT NULL constraint on account_id
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello",
+ Albums.VISIBILITY_PRIVATE, null));
+
+ // Normal insert
+ assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID1, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
+
+ long albumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, PARENT_ID1);
+
+ // Assign a valid child
+ assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID2, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
+
+ long otherAlbumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, PARENT_ID2);
+ assertNotSame(albumId, otherAlbumId);
+
+ // This is a valid child of another album.
+ assertTrue(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
+
+ // This isn't allowed due to uniqueness constraint (parent_id/name)
+ assertFalse(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello",
+ Albums.VISIBILITY_PRIVATE, accountId));
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public void testPhotosConstraints() {
+ SQLiteDatabase db = getWriteableDB();
+ db.beginTransaction();
+ try {
+ int width = 100;
+ int height = 100;
+ long dateTaken = System.currentTimeMillis();
+ String mimeType = "test/test";
+ long accountId = 100;
+
+ // Test NOT NULL mime-type
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null, null,
+ accountId));
+
+ // Test NOT NULL width
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, height, dateTaken, null, mimeType,
+ accountId));
+
+ // Test NOT NULL height
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, null, dateTaken, null, mimeType,
+ accountId));
+
+ // Test NOT NULL dateTaken
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, null, null, mimeType,
+ accountId));
+
+ // Test NOT NULL accountId
+ assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null,
+ mimeType, null));
+
+ // Test normal insert
+ assertTrue(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null, mimeType,
+ accountId));
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public void testMetadataConstraints() {
+ SQLiteDatabase db = getWriteableDB();
+ db.beginTransaction();
+ try {
+ final String mimeType = "test/test";
+ PhotoDatabaseUtils.insertPhoto(db, 100, 100, 100L, PARENT_ID1, mimeType, 100L);
+ long photoId = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, PARENT_ID1);
+
+ // Test NOT NULL PHOTO_ID constraint.
+ assertFalse(PhotoDatabaseUtils.insertMetadata(db, null, "foo", "bar"));
+
+ // Normal insert.
+ assertTrue(PhotoDatabaseUtils.insertMetadata(db, photoId, "foo", "bar"));
+
+ // Test uniqueness constraint.
+ assertFalse(PhotoDatabaseUtils.insertMetadata(db, photoId, "foo", "baz"));
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ public void testAccountsConstraints() {
+ SQLiteDatabase db = getWriteableDB();
+ db.beginTransaction();
+ try {
+ assertFalse(PhotoDatabaseUtils.insertAccount(db, null));
+ assertTrue(PhotoDatabaseUtils.insertAccount(db, "hello"));
+ assertTrue(PhotoDatabaseUtils.insertAccount(db, "hello"));
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ private SQLiteDatabase getReadableDB() {
+ return mDBHelper.getReadableDatabase();
+ }
+
+ private SQLiteDatabase getWriteableDB() {
+ return mDBHelper.getWritableDatabase();
+ }
+
+ private void validateTable(String table, String[] projection) {
+ SQLiteDatabase db = getReadableDB();
+ Cursor cursor = db.query(table, projection, null, null, null, null, null);
+ assertNotNull(cursor);
+ assertEquals(cursor.getCount(), 0);
+ assertEquals(cursor.getColumnCount(), projection.length);
+ for (int i = 0; i < projection.length; i++) {
+ assertEquals(cursor.getColumnName(i), projection[i]);
+ }
+ }
+
+
+}
diff --git a/tests/src/com/android/photos/data/PhotoDatabaseUtils.java b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java
new file mode 100644
index 000000000..97db8bf7d
--- /dev/null
+++ b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java
@@ -0,0 +1,130 @@
+/*
+ * 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.photos.data;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.photos.data.PhotoProvider.Accounts;
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import junit.framework.AssertionFailedError;
+
+public class PhotoDatabaseUtils {
+ public static String[] PROJECTION_ALBUMS = {
+ Albums._ID,
+ Albums.ACCOUNT_ID,
+ Albums.PARENT_ID,
+ Albums.VISIBILITY,
+ Albums.LOCATION_STRING,
+ Albums.TITLE,
+ Albums.SUMMARY,
+ Albums.DATE_PUBLISHED,
+ Albums.DATE_MODIFIED,
+ };
+
+ public static String[] PROJECTION_METADATA = {
+ Metadata.PHOTO_ID,
+ Metadata.KEY,
+ Metadata.VALUE,
+ };
+
+ public static String[] PROJECTION_PHOTOS = {
+ Photos._ID,
+ Photos.ACCOUNT_ID,
+ Photos.WIDTH,
+ Photos.HEIGHT,
+ Photos.DATE_TAKEN,
+ Photos.ALBUM_ID,
+ Photos.MIME_TYPE,
+ Photos.TITLE,
+ Photos.DATE_MODIFIED,
+ Photos.ROTATION,
+ };
+
+ public static String[] PROJECTION_ACCOUNTS = {
+ Accounts._ID,
+ Accounts.ACCOUNT_NAME,
+ };
+
+ private static String SELECTION_ALBUM_PARENT_ID = Albums.PARENT_ID + " = ?";
+ private static String SELECTION_PHOTO_ALBUM_ID = Photos.ALBUM_ID + " = ?";
+
+ public static long queryAlbumIdFromParentId(SQLiteDatabase db, long parentId) {
+ return queryId(db, Albums.TABLE, PROJECTION_ALBUMS, SELECTION_ALBUM_PARENT_ID, parentId);
+ }
+
+ public static long queryPhotoIdFromAlbumId(SQLiteDatabase db, long albumId) {
+ return queryId(db, Photos.TABLE, PROJECTION_PHOTOS, SELECTION_PHOTO_ALBUM_ID, albumId);
+ }
+
+ public static long queryId(SQLiteDatabase db, String table, String[] projection,
+ String selection, Object parameter) {
+ String paramString = parameter == null ? null : parameter.toString();
+ String[] selectionArgs = {
+ paramString,
+ };
+ Cursor cursor = db.query(table, projection, selection, selectionArgs, null, null, null);
+ try {
+ if (cursor.getCount() != 1 || !cursor.moveToNext()) {
+ throw new AssertionFailedError("Couldn't find item in table");
+ }
+ long id = cursor.getLong(0);
+ return id;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public static boolean insertPhoto(SQLiteDatabase db, Integer width, Integer height,
+ Long dateTaken, Long albumId, String mimeType, Long accountId) {
+ ContentValues values = new ContentValues();
+ values.put(Photos.WIDTH, width);
+ values.put(Photos.HEIGHT, height);
+ values.put(Photos.DATE_TAKEN, dateTaken);
+ values.put(Photos.ALBUM_ID, albumId);
+ values.put(Photos.MIME_TYPE, mimeType);
+ values.put(Photos.ACCOUNT_ID, accountId);
+ return db.insert(Photos.TABLE, null, values) != -1;
+ }
+
+ public static boolean insertAlbum(SQLiteDatabase db, Long parentId, String title,
+ Integer privacy, Long accountId) {
+ ContentValues values = new ContentValues();
+ values.put(Albums.PARENT_ID, parentId);
+ values.put(Albums.TITLE, title);
+ values.put(Albums.VISIBILITY, privacy);
+ values.put(Albums.ACCOUNT_ID, accountId);
+ return db.insert(Albums.TABLE, null, values) != -1;
+ }
+
+ public static boolean insertMetadata(SQLiteDatabase db, Long photosId, String key, String value) {
+ ContentValues values = new ContentValues();
+ values.put(Metadata.PHOTO_ID, photosId);
+ values.put(Metadata.KEY, key);
+ values.put(Metadata.VALUE, value);
+ return db.insert(Metadata.TABLE, null, values) != -1;
+ }
+
+ public static boolean insertAccount(SQLiteDatabase db, String name) {
+ ContentValues values = new ContentValues();
+ values.put(Accounts.ACCOUNT_NAME, name);
+ return db.insert(Accounts.TABLE, null, values) != -1;
+ }
+}
diff --git a/tests/src/com/android/photos/data/PhotoProviderTest.java b/tests/src/com/android/photos/data/PhotoProviderTest.java
new file mode 100644
index 000000000..39abff441
--- /dev/null
+++ b/tests/src/com/android/photos/data/PhotoProviderTest.java
@@ -0,0 +1,359 @@
+/*
+ * 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.photos.data;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.BaseColumns;
+import android.test.ProviderTestCase2;
+
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.util.ArrayList;
+
+public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> {
+ @SuppressWarnings("unused")
+ private static final String TAG = PhotoProviderTest.class.getSimpleName();
+
+ private static final String MIME_TYPE = "test/test";
+ private static final String ALBUM_TITLE = "My Album";
+ private static final long ALBUM_PARENT_ID = 100;
+ private static final String META_KEY = "mykey";
+ private static final String META_VALUE = "myvalue";
+
+ private static final Uri NO_TABLE_URI = PhotoProvider.BASE_CONTENT_URI;
+ private static final Uri BAD_TABLE_URI = Uri.withAppendedPath(PhotoProvider.BASE_CONTENT_URI,
+ "bad_table");
+
+ private static final String WHERE_METADATA_PHOTOS_ID = Metadata.PHOTO_ID + " = ?";
+ private static final String WHERE_METADATA = Metadata.PHOTO_ID + " = ? AND " + Metadata.KEY
+ + " = ?";
+
+ private long mAlbumId;
+ private long mPhotoId;
+ private long mMetadataId;
+
+ private SQLiteOpenHelper mDBHelper;
+ private ContentResolver mResolver;
+ private NotificationWatcher mNotifications = new NotificationWatcher();
+
+ public PhotoProviderTest() {
+ super(PhotoProvider.class, PhotoProvider.AUTHORITY);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mResolver = getMockContentResolver();
+ PhotoProvider provider = (PhotoProvider) getProvider();
+ provider.setMockNotification(mNotifications);
+ mDBHelper = provider.getDatabaseHelper();
+ SQLiteDatabase db = mDBHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ PhotoDatabaseUtils.insertAlbum(db, ALBUM_PARENT_ID, ALBUM_TITLE,
+ Albums.VISIBILITY_PRIVATE, 100L);
+ mAlbumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, ALBUM_PARENT_ID);
+ PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), mAlbumId,
+ MIME_TYPE, 100L);
+ mPhotoId = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, mAlbumId);
+ PhotoDatabaseUtils.insertMetadata(db, mPhotoId, META_KEY, META_VALUE);
+ String[] projection = {
+ BaseColumns._ID,
+ };
+ Cursor cursor = db.query(Metadata.TABLE, projection, null, null, null, null, null);
+ cursor.moveToNext();
+ mMetadataId = cursor.getLong(0);
+ cursor.close();
+ db.setTransactionSuccessful();
+ mNotifications.reset();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDBHelper.close();
+ mDBHelper = null;
+ super.tearDown();
+ getMockContext().deleteDatabase(PhotoProvider.DB_NAME);
+ }
+
+ public void testDelete() {
+ try {
+ mResolver.delete(NO_TABLE_URI, null, null);
+ fail("Exeption should be thrown when no table given");
+ } catch (Exception e) {
+ // expected exception
+ }
+ try {
+ mResolver.delete(BAD_TABLE_URI, null, null);
+ fail("Exeption should be thrown when deleting from a table that doesn't exist");
+ } catch (Exception e) {
+ // expected exception
+ }
+
+ String[] selectionArgs = {
+ String.valueOf(mPhotoId)
+ };
+ // Delete some metadata
+ assertEquals(1,
+ mResolver.delete(Metadata.CONTENT_URI, WHERE_METADATA_PHOTOS_ID, selectionArgs));
+ Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+ assertEquals(1, mResolver.delete(photoUri, null, null));
+ Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
+ assertEquals(1, mResolver.delete(albumUri, null, null));
+ // now delete something that isn't there
+ assertEquals(0, mResolver.delete(photoUri, null, null));
+ }
+
+ public void testDeleteMetadataId() {
+ Uri metadataUri = ContentUris.withAppendedId(Metadata.CONTENT_URI, mMetadataId);
+ assertEquals(1, mResolver.delete(metadataUri, null, null));
+ Cursor cursor = mResolver.query(Metadata.CONTENT_URI, null, null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ // Delete the album and ensure that the photos referring to the album are
+ // deleted.
+ public void testDeleteAlbumCascade() {
+ Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
+ mResolver.delete(albumUri, null, null);
+ assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(albumUri));
+ assertEquals(3, mNotifications.notificationCount());
+ Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+ null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ // Delete all albums and ensure that photos in any album are deleted.
+ public void testDeleteAlbumCascade2() {
+ mResolver.delete(Albums.CONTENT_URI, null, null);
+ assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(Albums.CONTENT_URI));
+ assertEquals(3, mNotifications.notificationCount());
+ Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+ null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ // Delete a photo and ensure that the metadata for that photo are deleted.
+ public void testDeletePhotoCascade() {
+ Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+ mResolver.delete(photoUri, null, null);
+ assertTrue(mNotifications.isNotified(photoUri));
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ assertEquals(2, mNotifications.notificationCount());
+ Cursor cursor = mResolver.query(Metadata.CONTENT_URI,
+ PhotoDatabaseUtils.PROJECTION_METADATA, null, null, null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ public void testGetType() {
+ // We don't return types for albums
+ assertNull(mResolver.getType(Albums.CONTENT_URI));
+
+ Uri noImage = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1);
+ assertNull(mResolver.getType(noImage));
+
+ Uri image = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+ assertEquals(MIME_TYPE, mResolver.getType(image));
+ }
+
+ public void testInsert() {
+ ContentValues values = new ContentValues();
+ values.put(Albums.TITLE, "add me");
+ values.put(Albums.VISIBILITY, Albums.VISIBILITY_PRIVATE);
+ values.put(Albums.ACCOUNT_ID, 100L);
+ values.put(Albums.DATE_MODIFIED, 100L);
+ values.put(Albums.DATE_PUBLISHED, 100L);
+ values.put(Albums.LOCATION_STRING, "Home");
+ values.put(Albums.TITLE, "hello world");
+ values.putNull(Albums.PARENT_ID);
+ values.put(Albums.SUMMARY, "Nothing much to say about this");
+ Uri insertedUri = mResolver.insert(Albums.CONTENT_URI, values);
+ assertNotNull(insertedUri);
+ Cursor cursor = mResolver.query(insertedUri, PhotoDatabaseUtils.PROJECTION_ALBUMS, null,
+ null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ cursor.close();
+ }
+
+ public void testUpdate() {
+ ContentValues values = new ContentValues();
+ // Normal update -- use an album.
+ values.put(Albums.TITLE, "foo");
+ Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
+ assertEquals(1, mResolver.update(albumUri, values, null, null));
+ String[] projection = {
+ Albums.TITLE,
+ };
+ Cursor cursor = mResolver.query(albumUri, projection, null, null, null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals("foo", cursor.getString(0));
+ cursor.close();
+
+ // Update a row that doesn't exist.
+ Uri noAlbumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId + 1);
+ values.put(Albums.TITLE, "bar");
+ assertEquals(0, mResolver.update(noAlbumUri, values, null, null));
+
+ // Update a metadata value that exists.
+ ContentValues metadata = new ContentValues();
+ metadata.put(Metadata.PHOTO_ID, mPhotoId);
+ metadata.put(Metadata.KEY, META_KEY);
+ metadata.put(Metadata.VALUE, "new value");
+ assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null));
+
+ projection = new String[] {
+ Metadata.VALUE,
+ };
+
+ String[] selectionArgs = {
+ String.valueOf(mPhotoId), META_KEY,
+ };
+
+ cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs,
+ null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals("new value", cursor.getString(0));
+ cursor.close();
+
+ // Update a metadata value that doesn't exist.
+ metadata.put(Metadata.KEY, "other stuff");
+ assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null));
+
+ selectionArgs[1] = "other stuff";
+ cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs,
+ null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals("new value", cursor.getString(0));
+ cursor.close();
+
+ // Remove a metadata value using update.
+ metadata.putNull(Metadata.VALUE);
+ assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null));
+ cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs,
+ null);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+ }
+
+ public void testQuery() {
+ // Query a photo that exists.
+ Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+ null, null, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals(mPhotoId, cursor.getLong(0));
+ cursor.close();
+
+ // Query a photo that doesn't exist.
+ Uri noPhotoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1);
+ cursor = mResolver.query(noPhotoUri, PhotoDatabaseUtils.PROJECTION_PHOTOS, null, null,
+ null);
+ assertNotNull(cursor);
+ assertEquals(0, cursor.getCount());
+ cursor.close();
+
+ // Query a photo that exists using selection arguments.
+ String[] selectionArgs = {
+ String.valueOf(mPhotoId),
+ };
+
+ cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+ Photos._ID + " = ?", selectionArgs, null);
+ assertNotNull(cursor);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToNext());
+ assertEquals(mPhotoId, cursor.getLong(0));
+ cursor.close();
+ }
+
+ public void testUpdatePhotoNotification() {
+ Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+ ContentValues values = new ContentValues();
+ values.put(Photos.MIME_TYPE, "not-a/mime-type");
+ mResolver.update(photoUri, values, null, null);
+ assertTrue(mNotifications.isNotified(photoUri));
+ }
+
+ public void testUpdateMetadataNotification() {
+ ContentValues values = new ContentValues();
+ values.put(Metadata.PHOTO_ID, mPhotoId);
+ values.put(Metadata.KEY, META_KEY);
+ values.put(Metadata.VALUE, "hello world");
+ mResolver.update(Metadata.CONTENT_URI, values, null, null);
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ }
+
+ public void testBatchTransaction() throws RemoteException, OperationApplicationException {
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+ ContentProviderOperation.Builder insert = ContentProviderOperation
+ .newInsert(Photos.CONTENT_URI);
+ insert.withValue(Photos.WIDTH, 200L);
+ insert.withValue(Photos.HEIGHT, 100L);
+ insert.withValue(Photos.DATE_TAKEN, System.currentTimeMillis());
+ insert.withValue(Photos.ALBUM_ID, 1000L);
+ insert.withValue(Photos.MIME_TYPE, "image/jpg");
+ insert.withValue(Photos.ACCOUNT_ID, 1L);
+ operations.add(insert.build());
+ ContentProviderOperation.Builder update = ContentProviderOperation.newUpdate(Photos.CONTENT_URI);
+ update.withValue(Photos.DATE_MODIFIED, System.currentTimeMillis());
+ String[] whereArgs = {
+ "100",
+ };
+ String where = Photos.WIDTH + " = ?";
+ update.withSelection(where, whereArgs);
+ operations.add(update.build());
+ ContentProviderOperation.Builder delete = ContentProviderOperation
+ .newDelete(Photos.CONTENT_URI);
+ delete.withSelection(where, whereArgs);
+ operations.add(delete.build());
+ mResolver.applyBatch(PhotoProvider.AUTHORITY, operations);
+ assertEquals(3, mNotifications.notificationCount());
+ SQLiteDatabase db = mDBHelper.getReadableDatabase();
+ long id = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, 1000L);
+ Uri uri = ContentUris.withAppendedId(Photos.CONTENT_URI, id);
+ assertTrue(mNotifications.isNotified(uri));
+ assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+ assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+ }
+
+}
diff --git a/tests/src/com/android/photos/data/TestHelper.java b/tests/src/com/android/photos/data/TestHelper.java
new file mode 100644
index 000000000..338e160cf
--- /dev/null
+++ b/tests/src/com/android/photos/data/TestHelper.java
@@ -0,0 +1,53 @@
+/*
+ * 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.photos.data;
+
+import android.util.Log;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.lang.reflect.Method;
+
+public class TestHelper {
+ private static String TAG = TestHelper.class.getSimpleName();
+
+ public interface TestInitialization {
+ void initialize(TestCase testCase);
+ }
+
+ public static void addTests(Class<? extends TestCase> testClass, TestSuite suite,
+ TestInitialization initialization) {
+ for (Method method : testClass.getDeclaredMethods()) {
+ if (method.getName().startsWith("test") && method.getParameterTypes().length == 0) {
+ TestCase test;
+ try {
+ test = testClass.newInstance();
+ test.setName(method.getName());
+ initialization.initialize(test);
+ suite.addTest(test);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to create test case", e);
+ } catch (InstantiationException e) {
+ Log.e(TAG, "Failed to create test case", e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Failed to create test case", e);
+ }
+ }
+ }
+ }
+
+}