am 082ea423: am 49826d43: Reconcile with ics-mr1-release
* commit '082ea4230f5892379cb0eebfd87d1594e51987ec':
Patch 2 for MR1.
Patch for MR1.
diff --git a/Android.mk b/Android.mk
index 0f5170f..6584c4a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -8,17 +8,29 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += $(call all-java-files-under, src_pd)
+LOCAL_SRC_FILES += $(call all-java-files-under, ../Camera/src)
+
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res packages/apps/Camera/res
+LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.camera
LOCAL_PACKAGE_NAME := Gallery2
LOCAL_OVERRIDES_PACKAGES := Gallery Gallery3D GalleryNew3D
-# We mark this out until Mtp and MediaMetadataRetriever is unhidden.
-LOCAL_SDK_VERSION := current
+#LOCAL_SDK_VERSION := current
+
+LOCAL_JNI_SHARED_LIBRARIES := libjni_mosaic
+
+LOCAL_REQUIRED_MODULES := libjni_mosaic
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
include $(BUILD_PACKAGE)
-# Use the following include to make our test apk.
+ifeq ($(strip $(LOCAL_PACKAGE_OVERRIDES)),)
+# Use the following include to make gallery test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))
+
+# Use the following include to make camera test apk.
+include $(call all-makefiles-under, ../Camera)
+endif
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 187bac9..c907da1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,26 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest android:versionCode="30682"
- android:versionName="1.1.30682"
+<manifest android:versionCode="40000"
+ android:versionName="1.1.40000"
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.gallery3d">
<original-package android:name="com.android.gallery3d" />
+ <uses-sdk android:minSdkVersion="14" />
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.NFC" />
+ <uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<supports-screens android:smallScreens="false"
android:normalScreens="true" android:largeScreens="true"
@@ -28,9 +35,12 @@
<application android:icon="@mipmap/ic_launcher_gallery" android:label="@string/app_name"
android:name="com.android.gallery3d.app.GalleryAppImpl"
- android:theme="@style/Theme.Gallery">
+ android:theme="@style/Theme.Gallery"
+ android:hardwareAccelerated="true">
+ <uses-library android:name="com.google.android.media.effects" android:required="false" />
<activity android:name="com.android.gallery3d.app.MovieActivity"
android:label="@string/movie_view_label"
+ android:theme="@style/Theme.MovieActivity"
android:configChanges="orientation|keyboardHidden|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -61,6 +71,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
+ <data android:scheme="https" />
<data android:mimeType="audio/x-mpegurl" />
<data android:mimeType="audio/mpegurl" />
<data android:mimeType="application/vnd.apple.mpegurl" />
@@ -238,8 +249,58 @@
android:theme="@style/DialogPickerTheme"/>
<activity android:name="com.android.gallery3d.gadget.WidgetTypeChooser"
android:configChanges="keyboardHidden|orientation|screenSize"
- android:theme="@style/DialogPickerTheme"/>
+ android:theme="@android:style/Theme.Holo.Dialog"/>
+ <activity android:name="com.android.camera.Camera"
+ android:taskAffinity="com.android.camera"
+ android:label="@string/camera_label"
+ android:theme="@style/ThemeCamera"
+ android:icon="@mipmap/ic_launcher_camera"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:clearTaskOnLaunch="true"
+ android:screenOrientation="behind"
+ android:windowSoftInputMode="stateAlwaysHidden|adjustPan">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.media.action.IMAGE_CAPTURE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.media.action.STILL_IMAGE_CAMERA" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name="com.android.camera.VideoCamera"
+ android:taskAffinity="com.android.camera"
+ android:label="@string/video_camera_label"
+ android:theme="@style/ThemeCamera"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:icon="@mipmap/ic_launcher_video_camera"
+ android:clearTaskOnLaunch="true"
+ android:screenOrientation="behind"
+ android:windowSoftInputMode="stateAlwaysHidden|adjustPan">
+ <intent-filter>
+ <action android:name="android.media.action.VIDEO_CAMERA" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.media.action.VIDEO_CAPTURE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <activity android:name="com.android.camera.PanoramaActivity"
+ android:taskAffinity="com.android.camera"
+ android:label="@string/pano_dialog_title"
+ android:theme="@style/ThemeCamera"
+ android:configChanges="orientation|screenSize|keyboardHidden"
+ android:clearTaskOnLaunch="true"
+ android:screenOrientation="behind"
+ android:windowSoftInputMode="stateAlwaysHidden|adjustPan">
+ </activity>
<receiver android:name="com.android.gallery3d.gadget.PhotoAppWidgetProvider"
android:label="@string/appwidget_title">
<intent-filter>
@@ -256,6 +317,16 @@
<data android:scheme="package"/>
</intent-filter>
</receiver>
+ <receiver android:name="com.android.camera.CameraButtonIntentReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.CAMERA_BUTTON"/>
+ </intent-filter>
+ </receiver>
+ <receiver android:name="com.android.camera.DisableCameraReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
<service android:name="com.android.gallery3d.gadget.WidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"/>
<activity android:name="com.android.gallery3d.gadget.WidgetConfigure"
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..cc930a1
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,53 @@
+# Copyright (C) 2007 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.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/APPS/Camera*)
+$(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*)
+
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
diff --git a/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java b/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
index 2192cf8..a671ed2 100644
--- a/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
+++ b/gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
@@ -23,6 +23,7 @@
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Build;
+import android.util.FloatMath;
import android.util.Log;
import java.io.ByteArrayOutputStream;
@@ -31,7 +32,7 @@
public class BitmapUtils {
private static final String TAG = "BitmapUtils";
- private static final int COMPRESS_JPEG_QUALITY = 90;
+ private static final int DEFAULT_JPEG_QUALITY = 90;
public static final int UNCONSTRAINED = -1;
private BitmapUtils(){}
@@ -71,7 +72,7 @@
&& minSideLength == UNCONSTRAINED) return 1;
int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
- (int) Math.ceil(Math.sqrt((double) (w * h) / maxNumOfPixels));
+ (int) FloatMath.ceil(FloatMath.sqrt((float) (w * h) / maxNumOfPixels));
if (minSideLength == UNCONSTRAINED) {
return lowerBound;
@@ -93,9 +94,9 @@
: initialSize / 8 * 8;
}
- // Fin the min x that 1 / x <= scale
+ // Find the min x that 1 / x >= scale
public static int computeSampleSizeLarger(float scale) {
- int initialSize = (int) Math.floor(1f / scale);
+ int initialSize = (int) FloatMath.floor(1f / scale);
if (initialSize <= 1) return 1;
return initialSize <= 8
@@ -103,25 +104,15 @@
: initialSize / 8 * 8;
}
- // Find the max x that 1 / x >= scale.
+ // Find the max x that 1 / x <= scale.
public static int computeSampleSize(float scale) {
Utils.assertTrue(scale > 0);
- int initialSize = Math.max(1, (int) Math.ceil(1 / scale));
+ int initialSize = Math.max(1, (int) FloatMath.ceil(1 / scale));
return initialSize <= 8
? Utils.nextPowerOf2(initialSize)
: (initialSize + 7) / 8 * 8;
}
- public static Bitmap resizeDownToPixels(
- Bitmap bitmap, int targetPixels, boolean recycle) {
- int width = bitmap.getWidth();
- int height = bitmap.getHeight();
- float scale = (float) Math.sqrt(
- (double) targetPixels / (width * height));
- if (scale >= 1.0f) return bitmap;
- return resizeBitmapByScale(bitmap, scale, recycle);
- }
-
public static Bitmap resizeBitmapByScale(
Bitmap bitmap, float scale, boolean recycle) {
int width = Math.round(bitmap.getWidth() * scale);
@@ -155,43 +146,15 @@
return resizeBitmapByScale(bitmap, scale, recycle);
}
- // Resize the bitmap if each side is >= targetSize * 2
- public static Bitmap resizeDownIfTooBig(
- Bitmap bitmap, int targetSize, boolean recycle) {
- int srcWidth = bitmap.getWidth();
- int srcHeight = bitmap.getHeight();
- float scale = Math.max(
- (float) targetSize / srcWidth, (float) targetSize / srcHeight);
- if (scale > 0.5f) return bitmap;
- return resizeBitmapByScale(bitmap, scale, recycle);
- }
-
- // Crops a square from the center of the original image.
- public static Bitmap cropCenter(Bitmap bitmap, boolean recycle) {
- int width = bitmap.getWidth();
- int height = bitmap.getHeight();
- if (width == height) return bitmap;
- int size = Math.min(width, height);
-
- Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
- Canvas canvas = new Canvas(target);
- canvas.translate((size - width) / 2, (size - height) / 2);
- Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
- canvas.drawBitmap(bitmap, 0, 0, paint);
- if (recycle) bitmap.recycle();
- return target;
- }
-
- public static Bitmap resizeDownAndCropCenter(Bitmap bitmap, int size,
- boolean recycle) {
+ public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
- int minSide = Math.min(w, h);
- if (w == h && minSide <= size) return bitmap;
- size = Math.min(size, minSide);
+ if (w == size && h == size) return bitmap;
- float scale = Math.max((float) size / bitmap.getWidth(),
- (float) size / bitmap.getHeight());
+ // scale the image so that the shorter side equals to the target;
+ // the longer side will be center-cropped.
+ float scale = (float) size / Math.min(w, h);
+
Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
int width = Math.round(scale * bitmap.getWidth());
int height = Math.round(scale * bitmap.getHeight());
@@ -272,11 +235,14 @@
return null;
}
- public static byte[] compressBitmap(Bitmap bitmap) {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- bitmap.compress(Bitmap.CompressFormat.JPEG,
- COMPRESS_JPEG_QUALITY, os);
- return os.toByteArray();
+ public static byte[] compressToBytes(Bitmap bitmap) {
+ return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
+ }
+
+ public static byte[] compressToBytes(Bitmap bitmap, int quality) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
+ bitmap.compress(CompressFormat.JPEG, quality, baos);
+ return baos.toByteArray();
}
public static boolean isSupportedByRegionDecoder(String mimeType) {
@@ -291,10 +257,4 @@
mimeType = mimeType.toLowerCase();
return mimeType.equals("image/jpeg");
}
-
- public static byte[] compressToBytes(Bitmap bitmap, int quality) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
- bitmap.compress(CompressFormat.JPEG, quality, baos);
- return baos.toByteArray();
- }
}
diff --git a/gallerycommon/src/com/android/gallery3d/common/Utils.java b/gallerycommon/src/com/android/gallery3d/common/Utils.java
index ea289a6..391b225 100644
--- a/gallerycommon/src/com/android/gallery3d/common/Utils.java
+++ b/gallerycommon/src/com/android/gallery3d/common/Utils.java
@@ -16,22 +16,17 @@
package com.android.gallery3d.common;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.os.Build;
-import android.os.Environment;
-import android.os.Parcel;
import android.os.ParcelFileDescriptor;
-import android.os.StatFs;
import android.text.TextUtils;
import android.util.Log;
import java.io.Closeable;
import java.io.InterruptedIOException;
-import java.util.Random;
public class Utils {
private static final String TAG = "Utils";
@@ -54,12 +49,14 @@
}
}
- // Throws AssertionError if the input is false.
- public static void assertTrue(boolean cond, String message, Object ... args) {
- if (!cond) {
- throw new AssertionError(
- args.length == 0 ? message : String.format(message, args));
- }
+ // Throws AssertionError with the message. We had a method having the form
+ // assertTrue(boolean cond, String message, Object ... args);
+ // However a call to that method will cause memory allocation even if the
+ // condition is false (due to autoboxing generated by "Object ... args"),
+ // so we don't use that anymore.
+ public static void fail(String message, Object ... args) {
+ throw new AssertionError(
+ args.length == 0 ? message : String.format(message, args));
}
// Throws NullPointerException if the input is null.
@@ -74,13 +71,6 @@
return (a == b) || (a == null ? false : a.equals(b));
}
- // Returns true if the input is power of 2.
- // Throws IllegalArgumentException if the input is <= 0.
- public static boolean isPowerOf2(int n) {
- if (n <= 0) throw new IllegalArgumentException();
- return (n & -n) == n;
- }
-
// Returns the next power of two.
// Returns the input if it is already power of 2.
// Throws IllegalArgumentException if the input is <= 0 or
@@ -104,13 +94,6 @@
return Integer.highestOneBit(n);
}
- // Returns the euclidean distance between (x, y) and (sx, sy).
- public static float distance(float x, float y, float sx, float sy) {
- float dx = x - sx;
- float dy = y - sy;
- return (float) Math.hypot(dx, dy);
- }
-
// Returns the input value x clamped to the range [min, max].
public static int clamp(int x, int min, int max) {
if (x > max) return max;
@@ -136,12 +119,6 @@
return color >>> 24 == 0xFF;
}
- public static <T> void swap(T[] array, int i, int j) {
- T temp = array[i];
- array[i] = array[j];
- array[j] = temp;
- }
-
public static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
@@ -259,15 +236,6 @@
return value == null ? "" : value;
}
- // Used for debugging. Should be removed before submitting.
- public static void debug(String format, Object ... args) {
- if (args.length == 0) {
- Log.d(DEBUG_TAG, format);
- } else {
- Log.d(DEBUG_TAG, String.format(format, args));
- }
- }
-
public static float parseFloatSafely(String content, float defaultValue) {
if (content == null) return defaultValue;
try {
@@ -290,22 +258,6 @@
return TextUtils.isEmpty(exifMake);
}
- public static boolean hasSpaceForSize(long size) {
- String state = Environment.getExternalStorageState();
- if (!Environment.MEDIA_MOUNTED.equals(state)) {
- return false;
- }
-
- String path = Environment.getExternalStorageDirectory().getPath();
- try {
- StatFs stat = new StatFs(path);
- return stat.getAvailableBlocks() * (long) stat.getBlockSize() > size;
- } catch (Exception e) {
- Log.i(TAG, "Fail to access external storage", e);
- }
- return false;
- }
-
public static void waitWithoutInterrupt(Object object) {
try {
object.wait();
@@ -314,16 +266,6 @@
}
}
- public static void shuffle(int array[], Random random) {
- for (int i = array.length; i > 0; --i) {
- int t = random.nextInt(i);
- if (t == i - 1) continue;
- int tmp = array[i - 1];
- array[i - 1] = array[t];
- array[t] = tmp;
- }
- }
-
public static boolean handleInterrruptedException(Throwable e) {
// A helper to deal with the interrupt exception
// If an interrupt detected, we will setup the bit again.
@@ -380,34 +322,6 @@
return result;
}
- public static PendingIntent deserializePendingIntent(byte[] rawPendingIntent) {
- Parcel parcel = null;
- try {
- if (rawPendingIntent != null) {
- parcel = Parcel.obtain();
- parcel.unmarshall(rawPendingIntent, 0, rawPendingIntent.length);
- return PendingIntent.readPendingIntentOrNullFromParcel(parcel);
- } else {
- return null;
- }
- } catch (Exception e) {
- throw new IllegalArgumentException("error parsing PendingIntent");
- } finally {
- if (parcel != null) parcel.recycle();
- }
- }
-
- public static byte[] serializePendingIntent(PendingIntent pendingIntent) {
- Parcel parcel = null;
- try {
- parcel = Parcel.obtain();
- PendingIntent.writePendingIntentOrNullToParcel(pendingIntent, parcel);
- return parcel.marshall();
- } finally {
- if (parcel != null) parcel.recycle();
- }
- }
-
// Mask information for debugging only. It returns <code>info.toString()</code> directly
// for debugging build (i.e., 'eng' and 'userdebug') and returns a mask ("****")
// in release build to protect the information (e.g. for privacy issue).
@@ -417,4 +331,9 @@
int length = Math.min(s.length(), MASK_STRING.length());
return IS_DEBUG_BUILD ? s : MASK_STRING.substring(0, length);
}
+
+ // This method should be ONLY used for debugging.
+ public static void debug(String message, Object ... args) {
+ Log.v(DEBUG_TAG, String.format(message, args));
+ }
}
diff --git a/proguard.flags b/proguard.flags
index a8cb363..8250933 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -8,3 +8,27 @@
-keep class * extends com.android.gallery3d.common.Entry {
@com.android.gallery3d.common.Entry$Column <fields>;
}
+
+# ctors of subclasses of CameraPreference are called with Java reflection.
+-keep class * extends com.android.camera.CameraPreference {
+ <init>(...);
+}
+
+-keep class com.android.camera.ActivityBase {
+ public int getResultCode();
+ public android.content.Intent getResultData();
+}
+
+-keep class com.android.camera.VideoCamera {
+ public boolean isRecording();
+ public void onCancelBgTraining(...);
+ public void onProtectiveCurtainClick(...);
+}
+
+-keep class * extends android.app.Activity {
+ @com.android.camera.OnClickAttr <methods>;
+}
+
+-keep class com.android.camera.CameraHolder {
+ public static void injectMockCamera(...);
+}
diff --git a/res/drawable-hdpi/dropdown_ic_arrow_normal_holo_dark.png b/res/drawable-hdpi/dropdown_ic_arrow_normal_holo_dark.png
new file mode 100644
index 0000000..06e5b47
--- /dev/null
+++ b/res/drawable-hdpi/dropdown_ic_arrow_normal_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/dropdown_normal_holo_dark.9.png b/res/drawable-hdpi/dropdown_normal_holo_dark.9.png
deleted file mode 100644
index 5525025..0000000
--- a/res/drawable-hdpi/dropdown_normal_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/navstrip_translucent.9.png b/res/drawable-hdpi/navstrip_translucent.9.png
deleted file mode 100644
index 5854af9..0000000
--- a/res/drawable-hdpi/navstrip_translucent.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/thumb_selected.9.png b/res/drawable-hdpi/thumb_selected.9.png
deleted file mode 100644
index 3e762f2..0000000
--- a/res/drawable-hdpi/thumb_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/dropdown_ic_arrow_normal_holo_dark.png b/res/drawable-mdpi/dropdown_ic_arrow_normal_holo_dark.png
new file mode 100644
index 0000000..81de1bb
--- /dev/null
+++ b/res/drawable-mdpi/dropdown_ic_arrow_normal_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/dropdown_normal_holo_dark.9.png b/res/drawable-mdpi/dropdown_normal_holo_dark.9.png
deleted file mode 100644
index 5525025..0000000
--- a/res/drawable-mdpi/dropdown_normal_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/navstrip_translucent.9.png b/res/drawable-mdpi/navstrip_translucent.9.png
deleted file mode 100644
index c3a0dc0..0000000
--- a/res/drawable-mdpi/navstrip_translucent.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/thumb_selected.9.png b/res/drawable-mdpi/thumb_selected.9.png
deleted file mode 100644
index b1c8fac..0000000
--- a/res/drawable-mdpi/thumb_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/dropdown_ic_arrow_normal_holo_dark.png b/res/drawable-xhdpi/dropdown_ic_arrow_normal_holo_dark.png
new file mode 100644
index 0000000..36d8cf4
--- /dev/null
+++ b/res/drawable-xhdpi/dropdown_ic_arrow_normal_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/thumb_selected.9.png b/res/drawable-xhdpi/thumb_selected.9.png
deleted file mode 100644
index 9ddbd05..0000000
--- a/res/drawable-xhdpi/thumb_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/dark_strip.9.png b/res/drawable/dark_strip.9.png
deleted file mode 100644
index dba0cae..0000000
--- a/res/drawable/dark_strip.9.png
+++ /dev/null
Binary files differ
diff --git a/res/layout/action_bar_text.xml b/res/layout/action_bar_text.xml
index 06a2ac9..2a1f031 100644
--- a/res/layout/action_bar_text.xml
+++ b/res/layout/action_bar_text.xml
@@ -16,6 +16,7 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
+ android:background="?android:attr/activatedBackgroundIndicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
diff --git a/res/layout/action_mode.xml b/res/layout/action_mode.xml
index d012b72..d4b3c23 100644
--- a/res/layout/action_mode.xml
+++ b/res/layout/action_mode.xml
@@ -20,14 +20,24 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
- <Button android:id="@+id/selection_menu"
- android:divider="?android:attr/listDividerAlertDialog"
- style="?android:attr/borderlessButtonStyle"
- android:singleLine="true"
- android:gravity="left|center_vertical"
+
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="match_parent" />
- <ImageView android:layout_marginLeft="8dip"
+ android:layout_height="match_parent">
+ <ImageView android:layout_gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:src="@drawable/dropdown_ic_arrow_normal_holo_dark" />
+ <Button android:id="@+id/selection_menu"
+ android:divider="?android:attr/listDividerAlertDialog"
+ style="?android:attr/borderlessButtonStyle"
+ android:singleLine="true"
+ android:gravity="left|center_vertical"
+ android:paddingRight="25dip"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" />
+ </FrameLayout>
+ <ImageView android:layout_marginLeft="16dip"
android:layout_marginRight="8dip"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/res/layout/main.xml b/res/layout/main.xml
index b71ea50..7dfe57a 100644
--- a/res/layout/main.xml
+++ b/res/layout/main.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/gallery_root"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/res/layout/movie_view.xml b/res/layout/movie_view.xml
index bd0415c..4e645cf 100644
--- a/res/layout/movie_view.xml
+++ b/res/layout/movie_view.xml
@@ -15,7 +15,7 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/root"
+ android:id="@+id/movie_view_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/res/layout/photoeditor_actionbar.xml b/res/layout/photoeditor_actionbar.xml
index 8bd51ed..e0adbbe 100644
--- a/res/layout/photoeditor_actionbar.xml
+++ b/res/layout/photoeditor_actionbar.xml
@@ -36,11 +36,11 @@
<LinearLayout style="@style/ActionBarLinearLayout" android:layout_alignParentRight="true">
- <ImageButton
+ <com.android.gallery3d.photoeditor.ImageActionButton
android:id="@+id/undo_button"
style="@style/ImageActionButton"
android:src="@drawable/photoeditor_undo"/>
- <ImageButton
+ <com.android.gallery3d.photoeditor.ImageActionButton
android:id="@+id/redo_button"
style="@style/ImageActionButton"
android:src="@drawable/photoeditor_redo"/>
@@ -54,7 +54,7 @@
style="@style/TextActionButton"
android:layout_width="fill_parent"
android:text="@string/save"/>
- <ImageButton
+ <com.android.gallery3d.photoeditor.ImageActionButton
android:id="@+id/share_button"
style="@style/ImageActionButton"
android:layout_width="fill_parent"
diff --git a/res/layout/photoeditor_color_seekbar.xml b/res/layout/photoeditor_color_seekbar.xml
index fb93869..ca8509d 100644
--- a/res/layout/photoeditor_color_seekbar.xml
+++ b/res/layout/photoeditor_color_seekbar.xml
@@ -16,9 +16,4 @@
<com.android.gallery3d.photoeditor.actions.ColorSeekBar
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/seekbar_width"
- android:layout_height="@dimen/seekbar_height"
- android:layout_marginBottom="@dimen/seekbar_margin_bottom"
- android:minHeight="@dimen/seekbar_height"
- android:maxHeight="@dimen/seekbar_height"
- android:progressDrawable="@android:color/transparent"/>
+ style="@style/SeekBar"/>
diff --git a/res/layout/photoeditor_crop_view.xml b/res/layout/photoeditor_crop_view.xml
index 151e6a6..bf5cacb 100644
--- a/res/layout/photoeditor_crop_view.xml
+++ b/res/layout/photoeditor_crop_view.xml
@@ -16,5 +16,4 @@
<com.android.gallery3d.photoeditor.actions.CropView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/fullscreen_effect_tool"
style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_doodle_view.xml b/res/layout/photoeditor_doodle_view.xml
index d8e0868..c202f14 100644
--- a/res/layout/photoeditor_doodle_view.xml
+++ b/res/layout/photoeditor_doodle_view.xml
@@ -16,5 +16,4 @@
<com.android.gallery3d.photoeditor.actions.DoodleView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/fullscreen_effect_tool"
style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_effect_tool_fullscreen.xml b/res/layout/photoeditor_effect_tool_fullscreen.xml
new file mode 100644
index 0000000..a6dd323
--- /dev/null
+++ b/res/layout/photoeditor_effect_tool_fullscreen.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
diff --git a/res/layout/photoeditor_effect_tool_panel.xml b/res/layout/photoeditor_effect_tool_panel.xml
index 7fd9257..4ffd52d 100644
--- a/res/layout/photoeditor_effect_tool_panel.xml
+++ b/res/layout/photoeditor_effect_tool_panel.xml
@@ -18,12 +18,13 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:padding="@dimen/effect_tool_panel_padding"
+ android:paddingTop="@dimen/effect_tool_panel_padding_top"
+ android:paddingBottom="@dimen/effect_tool_panel_padding_bottom"
android:background="@color/translucent_black"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/effect_label"
- style="@style/EffectLabel"/>
+ style="@style/EffectLabelInToolPanel"/>
</LinearLayout>
diff --git a/res/layout/photoeditor_effects_artistic.xml b/res/layout/photoeditor_effects_artistic.xml
index f5eb63a..89cb88c 100644
--- a/res/layout/photoeditor_effects_artistic.xml
+++ b/res/layout/photoeditor_effects_artistic.xml
@@ -19,8 +19,7 @@
style="@style/EffectsContainer">
<com.android.gallery3d.photoeditor.actions.CrossProcessAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_crossprocess"/>
<TextView
@@ -29,8 +28,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.CrossProcessAction>
<com.android.gallery3d.photoeditor.actions.PosterizeAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_posterize"/>
<TextView
@@ -39,8 +37,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.PosterizeAction>
<com.android.gallery3d.photoeditor.actions.LomoishAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_lomoish"/>
<TextView
@@ -49,8 +46,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.LomoishAction>
<com.android.gallery3d.photoeditor.actions.DocumentaryAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_documentary"/>
<TextView
@@ -59,8 +55,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.DocumentaryAction>
<com.android.gallery3d.photoeditor.actions.VignetteAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_vignette"/>
<TextView
@@ -69,8 +64,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.VignetteAction>
<com.android.gallery3d.photoeditor.actions.GrainAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_grain"/>
<TextView
@@ -79,8 +73,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.GrainAction>
<com.android.gallery3d.photoeditor.actions.FisheyeAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_fisheye"/>
<TextView
diff --git a/res/layout/photoeditor_effects_color.xml b/res/layout/photoeditor_effects_color.xml
index c078dfa..72cfe7a 100644
--- a/res/layout/photoeditor_effects_color.xml
+++ b/res/layout/photoeditor_effects_color.xml
@@ -19,8 +19,7 @@
style="@style/EffectsContainer">
<com.android.gallery3d.photoeditor.actions.ColorTemperatureAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_temperature"/>
<TextView
@@ -29,8 +28,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.ColorTemperatureAction>
<com.android.gallery3d.photoeditor.actions.SaturationAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_saturation"/>
<TextView
@@ -39,8 +37,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.SaturationAction>
<com.android.gallery3d.photoeditor.actions.GrayscaleAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_grayscale"/>
<TextView
@@ -49,8 +46,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.GrayscaleAction>
<com.android.gallery3d.photoeditor.actions.SepiaAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_sepia"/>
<TextView
@@ -59,8 +55,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.SepiaAction>
<com.android.gallery3d.photoeditor.actions.NegativeAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_negative"/>
<TextView
@@ -69,8 +64,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.NegativeAction>
<com.android.gallery3d.photoeditor.actions.TintAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_tint"/>
<TextView
@@ -79,8 +73,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.TintAction>
<com.android.gallery3d.photoeditor.actions.DuotoneAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_duotone"/>
<TextView
@@ -90,8 +83,7 @@
</com.android.gallery3d.photoeditor.actions.DuotoneAction>
<com.android.gallery3d.photoeditor.actions.DoodleAction style="@style/Effect"
android:tag="@string/doodle_tooltip">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_doodle"/>
<TextView
diff --git a/res/layout/photoeditor_effects_exposure.xml b/res/layout/photoeditor_effects_exposure.xml
index 5a4fb29..b353489 100644
--- a/res/layout/photoeditor_effects_exposure.xml
+++ b/res/layout/photoeditor_effects_exposure.xml
@@ -19,8 +19,7 @@
style="@style/EffectsContainer">
<com.android.gallery3d.photoeditor.actions.FillLightAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_filllight"/>
<TextView
@@ -29,8 +28,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.FillLightAction>
<com.android.gallery3d.photoeditor.actions.HighlightAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_highlight"/>
<TextView
@@ -39,8 +37,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.HighlightAction>
<com.android.gallery3d.photoeditor.actions.ShadowAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_shadow"/>
<TextView
@@ -49,8 +46,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.ShadowAction>
<com.android.gallery3d.photoeditor.actions.AutoFixAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_autofix"/>
<TextView
diff --git a/res/layout/photoeditor_effects_fix.xml b/res/layout/photoeditor_effects_fix.xml
index 9a2843c..924190b 100644
--- a/res/layout/photoeditor_effects_fix.xml
+++ b/res/layout/photoeditor_effects_fix.xml
@@ -20,8 +20,7 @@
<com.android.gallery3d.photoeditor.actions.CropAction style="@style/Effect"
android:tag="@string/crop_tooltip">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_crop"/>
<TextView
@@ -31,8 +30,7 @@
</com.android.gallery3d.photoeditor.actions.CropAction>
<com.android.gallery3d.photoeditor.actions.RedEyeAction style="@style/Effect"
android:tag="@string/redeye_tooltip">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_redeye"/>
<TextView
@@ -41,8 +39,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.RedEyeAction>
<com.android.gallery3d.photoeditor.actions.FaceliftAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_facelift"/>
<TextView
@@ -51,8 +48,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.FaceliftAction>
<com.android.gallery3d.photoeditor.actions.FaceTanAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_facetan"/>
<TextView
@@ -62,8 +58,7 @@
</com.android.gallery3d.photoeditor.actions.FaceTanAction>
<com.android.gallery3d.photoeditor.actions.StraightenAction style="@style/Effect"
android:tag="@string/straighten_tooltip">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_straighten"/>
<TextView
@@ -73,8 +68,7 @@
</com.android.gallery3d.photoeditor.actions.StraightenAction>
<com.android.gallery3d.photoeditor.actions.RotateAction style="@style/Effect"
android:tag="@string/rotate_tooltip">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_rotate"/>
<TextView
@@ -84,8 +78,7 @@
</com.android.gallery3d.photoeditor.actions.RotateAction>
<com.android.gallery3d.photoeditor.actions.FlipAction style="@style/Effect"
android:tag="@string/flip_tooltip">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_flip"/>
<TextView
@@ -94,8 +87,7 @@
style="@style/EffectLabel"/>
</com.android.gallery3d.photoeditor.actions.FlipAction>
<com.android.gallery3d.photoeditor.actions.SharpenAction style="@style/Effect">
- <ImageButton
- android:id="@+id/effect_button"
+ <ImageView
style="@style/EffectIcon"
android:src="@drawable/photoeditor_effect_sharpen"/>
<TextView
diff --git a/res/layout/photoeditor_effects_menu.xml b/res/layout/photoeditor_effects_menu.xml
index 1688a90..a0b102d 100644
--- a/res/layout/photoeditor_effects_menu.xml
+++ b/res/layout/photoeditor_effects_menu.xml
@@ -23,19 +23,19 @@
android:id="@+id/toggles"
style="@style/EffectsMenuContainer">
- <ImageButton
+ <com.android.gallery3d.photoeditor.ImageActionButton
android:id="@+id/exposure_button"
style="@style/EffectsMenuActionButton"
android:src="@drawable/photoeditor_exposure"/>
- <ImageButton
+ <com.android.gallery3d.photoeditor.ImageActionButton
android:id="@+id/artistic_button"
style="@style/EffectsMenuActionButton"
android:src="@drawable/photoeditor_artistic"/>
- <ImageButton
+ <com.android.gallery3d.photoeditor.ImageActionButton
android:id="@+id/color_button"
style="@style/EffectsMenuActionButton"
android:src="@drawable/photoeditor_color"/>
- <ImageButton
+ <com.android.gallery3d.photoeditor.ImageActionButton
android:id="@+id/fix_button"
style="@style/EffectsMenuActionButton"
android:src="@drawable/photoeditor_fix"/>
diff --git a/res/layout/photoeditor_flip_view.xml b/res/layout/photoeditor_flip_view.xml
index 0b7a7a7..150b24e 100644
--- a/res/layout/photoeditor_flip_view.xml
+++ b/res/layout/photoeditor_flip_view.xml
@@ -16,5 +16,4 @@
<com.android.gallery3d.photoeditor.actions.FlipView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/fullscreen_effect_tool"
style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_rotate_view.xml b/res/layout/photoeditor_rotate_view.xml
index 9dbe9d0..0e85fd4 100644
--- a/res/layout/photoeditor_rotate_view.xml
+++ b/res/layout/photoeditor_rotate_view.xml
@@ -16,5 +16,4 @@
<com.android.gallery3d.photoeditor.actions.RotateView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/fullscreen_effect_tool"
style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_scale_seekbar.xml b/res/layout/photoeditor_scale_seekbar.xml
index d8df19f..15fc234 100644
--- a/res/layout/photoeditor_scale_seekbar.xml
+++ b/res/layout/photoeditor_scale_seekbar.xml
@@ -16,9 +16,4 @@
<com.android.gallery3d.photoeditor.actions.ScaleSeekBar
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/seekbar_width"
- android:layout_height="@dimen/seekbar_height"
- android:layout_marginBottom="@dimen/seekbar_margin_bottom"
- android:minHeight="@dimen/seekbar_height"
- android:maxHeight="@dimen/seekbar_height"
- android:progressDrawable="@android:color/transparent"/>
+ style="@style/SeekBar"/>
diff --git a/res/layout/photoeditor_touch_view.xml b/res/layout/photoeditor_touch_view.xml
index 39a0871..a6da078 100644
--- a/res/layout/photoeditor_touch_view.xml
+++ b/res/layout/photoeditor_touch_view.xml
@@ -16,5 +16,4 @@
<com.android.gallery3d.photoeditor.actions.TouchView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/fullscreen_effect_tool"
style="@style/FullscreenToolView"/>
diff --git a/res/menu-land/photo.xml b/res/menu-land/photo.xml
index 023a93b..21f802a 100644
--- a/res/menu-land/photo.xml
+++ b/res/menu-land/photo.xml
@@ -30,16 +30,7 @@
android:icon="@drawable/ic_menu_trash_holo_light"
android:title="@string/delete"
android:visible="false"
- android:showAsAction="ifRoom">
- <menu>
- <item android:id="@+id/action_confirm_delete"
- android:icon="@drawable/ic_menu_trash_holo_light"
- android:title="@string/confirm_delete" />
- <item android:id="@+id/action_cancel_delete"
- android:icon="@drawable/ic_menu_cancel_holo_light"
- android:title="@string/cancel" />
- </menu>
- </item>
+ android:showAsAction="ifRoom" />
<item android:id="@+id/action_slideshow"
android:icon="@drawable/ic_menu_slideshow_holo_light"
android:title="@string/slideshow"
diff --git a/res/menu/albumset.xml b/res/menu/albumset.xml
index 3bb46f7..749b7f9 100644
--- a/res/menu/albumset.xml
+++ b/res/menu/albumset.xml
@@ -30,4 +30,7 @@
<item android:id="@+id/action_settings"
android:title="@string/settings"
android:showAsAction="never" />
+ <item android:id="@+id/action_general_help"
+ android:title="@string/help"
+ android:showAsAction="never" />
</menu>
diff --git a/res/menu/operation.xml b/res/menu/operation.xml
index e935684..3225e1e 100644
--- a/res/menu/operation.xml
+++ b/res/menu/operation.xml
@@ -30,16 +30,7 @@
android:icon="@drawable/ic_menu_trash_holo_light"
android:title="@string/delete"
android:visible="false"
- android:showAsAction="ifRoom">
- <menu>
- <item android:id="@+id/action_confirm_delete"
- android:icon="@drawable/ic_menu_trash_holo_light"
- android:title="@string/confirm_delete" />
- <item android:id="@+id/action_cancel_delete"
- android:icon="@drawable/ic_menu_cancel_holo_light"
- android:title="@string/cancel" />
- </menu>
- </item>
+ android:showAsAction="ifRoom" />
<item android:id="@+id/action_edit"
android:title="@string/edit"
android:showAsAction="never"
diff --git a/res/menu/photo.xml b/res/menu/photo.xml
index 0ae2549..685627a 100644
--- a/res/menu/photo.xml
+++ b/res/menu/photo.xml
@@ -30,16 +30,7 @@
android:icon="@drawable/ic_menu_trash_holo_light"
android:title="@string/delete"
android:visible="false"
- android:showAsAction="never">
- <menu>
- <item android:id="@+id/action_confirm_delete"
- android:icon="@drawable/ic_menu_trash_holo_light"
- android:title="@string/confirm_delete" />
- <item android:id="@+id/action_cancel_delete"
- android:icon="@drawable/ic_menu_cancel_holo_light"
- android:title="@string/cancel" />
- </menu>
- </item>
+ android:showAsAction="never" />
<item android:id="@+id/action_slideshow"
android:icon="@drawable/ic_menu_slideshow_holo_light"
android:title="@string/slideshow"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 8feab87..af3e90c 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Speel verder"</string>
<string name="loading" msgid="7038208555304563571">"Laai tans…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Kon nie laai nie"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Kon nie die prent laai nie"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Geen kleinkiekie nie"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Begin van voor af"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Snoei"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Raak aan \'n gesig om te begin."</string>
<string name="saving_image" msgid="7270334453636349407">"Stoor tans prent…"</string>
<string name="save_error" msgid="6857408774183654970">"Kon nie gesnoeide prent stoor nie."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Stel tans muurpapier in..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Muurpapier"</string>
<string name="delete" msgid="2839695998251824487">"Vee uit"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Vee uit"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Kanselleer"</string>
<string name="share" msgid="3619042788254195341">"Deling"</string>
<string name="select_all" msgid="3403283025220282175">"Kies almal"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Ongemerk"</string>
<string name="no_location" msgid="4043624857489331676">"Geen ligging nie"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Sommige liggings kon nie geïdentifiseer word nie weens netwerkprobleme."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Kon nie die foto\'s in hierdie album aflaai nie. Probeer later weer."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Kon nie die lys van albums aflaai nie. Probeer later weer."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Kon nie die foto\'s in hierdie album aflaai nie. Probeer later weer."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Kon nie die lys van albums aflaai nie. Probeer later weer."</string>
<string name="show_images_only" msgid="7263218480867672653">"Net prente"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Net video\'s"</string>
- <string name="show_all" msgid="4780647751652596980">"Prente en video\'s"</string>
+ <string name="show_all" msgid="6963292714584735149">"Prente en video\'s"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotogalery"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Geen foto\'s nie."</string>
<string name="crop_saved" msgid="1062612625032731770">"Gesnoeide prent gestoor in Aflaaisels."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Groepeer volgens"</string>
<string name="settings" msgid="1534847740615665736">"Instellings"</string>
<string name="add_account" msgid="4271217504968243974">"Voeg rekening by"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Laai af"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Ingevoer"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Skermkiekie"</string>
+ <string name="help" msgid="7368960711153618354">"Hulp"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index e93b7d9..ace5497 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"ማጫወት ቀጥል"</string>
<string name="loading" msgid="7038208555304563571">"በመስቀል ላይ…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"መስቀል አልተቻለም"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"ምስሉን መጫን አልተቻለም"</string>
<string name="no_thumbnail" msgid="284723185546429750">"ምንም ጥፍርአከል የለም"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"እንደገና ጀምር"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"ከርክም"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"እሺ"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"ለመጀመር ፊት ንካ::"</string>
<string name="saving_image" msgid="7270334453636349407">"ምስል በማስቀመጥ ላይ..."</string>
<string name="save_error" msgid="6857408774183654970">"የተቀመቀመውን ምስል ማስቀመጥ አልተቻለም::"</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"ልጣፍ በማቀናበር ላይ...."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"ልጣፍ"</string>
<string name="delete" msgid="2839695998251824487">"ሰርዝ"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"ሰርዝ"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"ይቅር"</string>
<string name="share" msgid="3619042788254195341">"አጋራ"</string>
<string name="select_all" msgid="3403283025220282175">"ሁሉንም ምረጥ"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"ያልተለጠፈ"</string>
<string name="no_location" msgid="4043624857489331676">"ምንም ስፍራ የለም"</string>
<string name="no_connectivity" msgid="7164037617297293668">"አንዳንድ ስፍራዎች በአውታረ መረብ ችግር ምክንያት ለየቶ ማወቅ አልተቻለም::"</string>
- <string name="sync_album_error" msgid="2218733298953719785">"በዚህ አልበም ላይ ያሉ ፎቶዎችን ለማውረድ አልተሳካም:: እባክህ እንደገና ሞክር::"</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"የአልበሞችን ዝርዝር ለማውረድ አልተሳካም:: እባክህ እንደገና ሞክር::"</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"በዚህ አልበም ላይ ያሉ ፎቶዎችን ለማውረድ አልተቻለም፡፡ በኋላ ደግመህ ሞክር፡፡"</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"የአልበሞችን ዝርዝር ለማውረድ አልተቻለም፡፡ በኋላ ደግመህ ሞክር፡፡"</string>
<string name="show_images_only" msgid="7263218480867672653">"ምስሎች ብቻ"</string>
<string name="show_videos_only" msgid="3850394623678871697">"ቪዲዮዎች ብቻ"</string>
- <string name="show_all" msgid="4780647751652596980">"ምስሎች እና ቪዲዮዎች"</string>
+ <string name="show_all" msgid="6963292714584735149">"ምስሎች & ቪዲዮዎች"</string>
<string name="appwidget_title" msgid="6410561146863700411">"የፎቶ ሥነ ጥበብ ማዕከል"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"ምንም ፎቶዎች የሉም፡፡"</string>
<string name="crop_saved" msgid="1062612625032731770">"የተከረከመ ምስል ወደ አውርዶች ተቀምጧል::"</string>
@@ -160,6 +164,11 @@
<string name="people" msgid="4114003823747292747">"ሰዎች"</string>
<string name="tags" msgid="5539648765482935955">"መለያዎች"</string>
<string name="group_by" msgid="4308299657902209357">"በቡድን አስቀምጥ"</string>
- <string name="settings" msgid="1534847740615665736">"ቅንጅቶች"</string>
+ <string name="settings" msgid="1534847740615665736">"ቅንብሮች"</string>
<string name="add_account" msgid="4271217504968243974">"መለያ አክል"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"ካሜራ"</string>
+ <string name="folder_download" msgid="7186215137642323932">"አውርድ"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"ከውጭ የገባ"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"ቅጽበታዊ ገጽ እይታ"</string>
+ <string name="help" msgid="7368960711153618354">"እገዛ"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 922919c..6bdeecc 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"استئناف التشغيل"</string>
<string name="loading" msgid="7038208555304563571">"جارٍ التحميل…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"تعذر التحميل"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"تعذر تحميل الصورة"</string>
<string name="no_thumbnail" msgid="284723185546429750">"بلا صورة مصغرة"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"البدء من جديد"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"اقتصاص"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"موافق"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"المس وجهًا للبدء."</string>
<string name="saving_image" msgid="7270334453636349407">"جارٍ حفظ الصورة..."</string>
<string name="save_error" msgid="6857408774183654970">"تعذر حفظ الصورة التي تم اقتصاصها."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"جارٍ تعيين الخلفية..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"الخلفية"</string>
<string name="delete" msgid="2839695998251824487">"حذف"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"حذف"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"إلغاء"</string>
<string name="share" msgid="3619042788254195341">"مشاركة"</string>
<string name="select_all" msgid="3403283025220282175">"تحديد الكل"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"بلا علامات"</string>
<string name="no_location" msgid="4043624857489331676">"لا موقع"</string>
<string name="no_connectivity" msgid="7164037617297293668">"تعذر تحديد بعض المواقع بسبب مشاكل في الشبكة."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"أخفق تنزيل الصور في هذا الألبوم. الرجاء إعادة المحاولة."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"أخفق تنزيل قائمة الألبومات. الرجاء إعادة المحاولة."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"تعذر تحميل الصور في هذا الألبوم. أعد المحاولة لاحقًا."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"تعذر تحميل قائمة الألبومات. أعد المحاولة لاحقًا."</string>
<string name="show_images_only" msgid="7263218480867672653">"الصور فقط"</string>
<string name="show_videos_only" msgid="3850394623678871697">"مقاطع الفيديو فقط"</string>
- <string name="show_all" msgid="4780647751652596980">"الصور ومقاطع الفيديو"</string>
+ <string name="show_all" msgid="6963292714584735149">"الصور ومقاطع الفيديو"</string>
<string name="appwidget_title" msgid="6410561146863700411">"معرض الصور"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"ليست هناك أية صور."</string>
<string name="crop_saved" msgid="1062612625032731770">"تم حفظ الصورة التي تم اقتصاصها إلى التنزيلات."</string>
@@ -155,11 +159,16 @@
<string name="widget_type" msgid="1364653978966343448">"اختيار صور"</string>
<string name="slideshow_dream_name" msgid="6915963319933437083">"عرض الشرائح"</string>
<string name="albums" msgid="7320787705180057947">"ألبومات"</string>
- <string name="times" msgid="2023033894889499219">"المرات"</string>
+ <string name="times" msgid="2023033894889499219">"التواريخ"</string>
<string name="locations" msgid="6649297994083130305">"المواقع"</string>
<string name="people" msgid="4114003823747292747">"الأشخاص"</string>
<string name="tags" msgid="5539648765482935955">"العلامات"</string>
<string name="group_by" msgid="4308299657902209357">"تجميع بحسب"</string>
<string name="settings" msgid="1534847740615665736">"الإعدادات"</string>
<string name="add_account" msgid="4271217504968243974">"إضافة حساب"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"الكاميرا"</string>
+ <string name="folder_download" msgid="7186215137642323932">"التنزيل"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"المستوردة"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"لقطة شاشة"</string>
+ <string name="help" msgid="7368960711153618354">"المساعدة"</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 718c81f..b2b322c 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Працягнуць прайграванне"</string>
<string name="loading" msgid="7038208555304563571">"Загрузка..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Не атрымалася загрузiць"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Не атрымалася загрузіць малюнак"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Няма паменшанай выявы"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Пачаць зноў"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Абрэзаць"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"ОК"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Дакранiцеся да твару, каб пачаць."</string>
<string name="saving_image" msgid="7270334453636349407">"Захаванне выявы..."</string>
<string name="save_error" msgid="6857408774183654970">"Немагчыма захаваць абрэзаную выяву."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Усталяванне шпалер..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Шпалеры"</string>
<string name="delete" msgid="2839695998251824487">"Выдаліць"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Выдаліць"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Адмяніць"</string>
<string name="share" msgid="3619042788254195341">"Апублікаваць"</string>
<string name="select_all" msgid="3403283025220282175">"Выбраць усё"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Неазначаныя"</string>
<string name="no_location" msgid="4043624857489331676">"Няма месцазнаходж."</string>
<string name="no_connectivity" msgid="7164037617297293668">"Некаторыя месцы не могуць быць вызначаны з-за праблем сеткі."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Не атрымлiваецца загрузiць фатаграфii з гэтага альбома. Паспрабуйце пазней."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Не атрымлiваецца загрузіць спіс альбомаў. Паспрабуйце пазней."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Немагчыма спампаваць фатаграфіі ў гэтым альбоме. Паўтарыце спробу пазней."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Немагчыма спампаваць спіс альбомаў. Паўтарыце спробу пазней."</string>
<string name="show_images_only" msgid="7263218480867672653">"Толькі малюнкі"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Толькі відэа"</string>
- <string name="show_all" msgid="4780647751652596980">"Выявы і відэа"</string>
+ <string name="show_all" msgid="6963292714584735149">"Выявы і відэа"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Фотагалерэя"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Фатаграфій няма"</string>
<string name="crop_saved" msgid="1062612625032731770">"Абрэзаная выява захоўваецца ў тэчцы спампаванняў."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Групаваць па"</string>
<string name="settings" msgid="1534847740615665736">"Налады"</string>
<string name="add_account" msgid="4271217504968243974">"Дадаць уліковы запіс"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Камера"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Спампаваць"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Імпартаваныя"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Скрыншот"</string>
+ <string name="help" msgid="7368960711153618354">"Даведка"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index b4f7474..e3619f7 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Продължаване"</string>
<string name="loading" msgid="7038208555304563571">"Зарежда се…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Не можаха да се заредят"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Изображението не можа да бъде заредено"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Няма миниизображение"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Стартиране отначало"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Подрязване"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Докоснете лице за начало."</string>
<string name="saving_image" msgid="7270334453636349407">"Снимката се запазва..."</string>
<string name="save_error" msgid="6857408774183654970">"Подрязаното изобр. не можа да се запази."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Тапетът се задава..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Тапет"</string>
<string name="delete" msgid="2839695998251824487">"Изтриване"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Изтриване"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Отказ"</string>
<string name="share" msgid="3619042788254195341">"Споделяне"</string>
<string name="select_all" msgid="3403283025220282175">"Избиране на всички"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Немаркирани"</string>
<string name="no_location" msgid="4043624857489331676">"Няма местоположение"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Някои местоположения не можаха да бъдат идентифицирани поради проблеми с мрежата."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Изтеглянето на снимките в този албум не бе успешно. Моля, опитайте отново по-късно."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Изтеглянето на списъка с албуми не бе успешно. Моля, опитайте отново по-късно."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Снимките в този албум не можаха да се изтеглят. Опитайте отново по-късно."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Списъкът с албуми не можа да се изтегли. Опитайте отново по-късно."</string>
<string name="show_images_only" msgid="7263218480867672653">"Само изображения"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Само видеоклипове"</string>
- <string name="show_all" msgid="4780647751652596980">"Изображения и видеоклипове"</string>
+ <string name="show_all" msgid="6963292714584735149">"Изображения и видеоклипове"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Фотогалерия"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Няма снимки."</string>
<string name="crop_saved" msgid="1062612625032731770">"Подрязаното изображение е запазено в „Изтегляния“."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Групиране по"</string>
<string name="settings" msgid="1534847740615665736">"Настройки"</string>
<string name="add_account" msgid="4271217504968243974">"Добавяне на профил"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Камера"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Изтегляне"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Импортирани"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Екранна снимка"</string>
+ <string name="help" msgid="7368960711153618354">"Помощ"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index e053e4e..c385221 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Reprèn la reproducció"</string>
<string name="loading" msgid="7038208555304563571">"S\'està carregant…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"No s\'ha pogut carregar"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"No s\'ha pogut carregar la imatge"</string>
<string name="no_thumbnail" msgid="284723185546429750">"No hi ha cap miniatura"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Torna a començar"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Retalla"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"D\'acord"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Toca una cara per començar."</string>
<string name="saving_image" msgid="7270334453636349407">"S\'està desant la imatge..."</string>
<string name="save_error" msgid="6857408774183654970">"No s\'ha pogut desar la imatge retallada."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"S\'està establint el fons de pantalla..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Fons de pantalla"</string>
<string name="delete" msgid="2839695998251824487">"Suprimeix"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Suprimeix"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Cancel·la"</string>
<string name="share" msgid="3619042788254195341">"Comparteix"</string>
<string name="select_all" msgid="3403283025220282175">"Selecciona-ho tot"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Sense etiquetar"</string>
<string name="no_location" msgid="4043624857489331676">"Sense ubicació"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Hi ha ubicacions que no s\'han pogut identificar a causa de problemes amb la xarxa."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"No s\'han pogut baixar les fotos en aquest àlbum. Torna-ho a provar més tard."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"No s\'ha pogut baixar la llista d\'àlbums. Torna-ho a provar més tard."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"No s\'han pogut baixar les fotos en aquest àlbum. Torna-ho a provar més tard."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"No s\'ha pogut baixar la llista d\'àlbums. Torna-ho a provar més tard."</string>
<string name="show_images_only" msgid="7263218480867672653">"Només imatges"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Només vídeos"</string>
- <string name="show_all" msgid="4780647751652596980">"Imatges i vídeos"</string>
+ <string name="show_all" msgid="6963292714584735149">"Imatges i vídeos"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galeria de fotos"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Sense fotos."</string>
<string name="crop_saved" msgid="1062612625032731770">"S\'ha desat la imatge retallada a Baixades."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Agrupa per"</string>
<string name="settings" msgid="1534847740615665736">"Configuració"</string>
<string name="add_account" msgid="4271217504968243974">"Afegeix un compte"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Càmera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Baixades"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importades"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Captura de pantalla"</string>
+ <string name="help" msgid="7368960711153618354">"Ajuda"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 780295e..046830c 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Pokračovat v přehrávání"</string>
<string name="loading" msgid="7038208555304563571">"Načítání..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Nelze načíst"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Obrázek nelze načíst"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Miniatura není dostupná"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Začít znovu"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Oříznutí"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Začněte klepnutím na obličej."</string>
<string name="saving_image" msgid="7270334453636349407">"Ukládání fotografie..."</string>
<string name="save_error" msgid="6857408774183654970">"Oříznutý obrázek nelze uložit."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Nastavování tapety..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Tapeta"</string>
<string name="delete" msgid="2839695998251824487">"Smazat"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Smazat"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Zrušit"</string>
<string name="share" msgid="3619042788254195341">"Sdílet"</string>
<string name="select_all" msgid="3403283025220282175">"Vybrat vše"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Neoznačeno"</string>
<string name="no_location" msgid="4043624857489331676">"Neznámá poloha"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Některá umístění se nepodařilo identifikovat kvůli problémům se sítí."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Stažení fotografií v tomto albu se nezdařilo. Zkuste to prosím později."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Stažení seznamu alb se nezdařilo. Zkuste to prosím později."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Fotografie v tomto albu nelze stáhnout. Zkuste to znovu později."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Seznam alb nelze stáhnout. Zkuste to znovu později."</string>
<string name="show_images_only" msgid="7263218480867672653">"Pouze obrázky"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Pouze videa"</string>
- <string name="show_all" msgid="4780647751652596980">"Obrázky a videa"</string>
+ <string name="show_all" msgid="6963292714584735149">"Obrázky a videa"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galerie fotografií"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Žádné fotky."</string>
<string name="crop_saved" msgid="1062612625032731770">"Ořezaný snímek uložen do složky stažených souborů."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Seskupit podle"</string>
<string name="settings" msgid="1534847740615665736">"Nastavení"</string>
<string name="add_account" msgid="4271217504968243974">"Přidat účet"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Fotoaparát"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Stahování"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importováno"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Snímek obrazovky"</string>
+ <string name="help" msgid="7368960711153618354">"Nápověda"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 3130cbf..0c11da4 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -29,11 +29,12 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Genoptag afspilning"</string>
<string name="loading" msgid="7038208555304563571">"Indlæser..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Kunne ikke indlæses"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Billedet kunne ikke indlæses"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Ingen miniature"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Start igen"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Beskær"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Tryk på et ansigt for at begynde."</string>
- <string name="saving_image" msgid="7270334453636349407">"Gemmer billede ..."</string>
+ <string name="saving_image" msgid="7270334453636349407">"Gemmer billede..."</string>
<string name="save_error" msgid="6857408774183654970">"Det beskårne billede kunne ikke gemmes."</string>
<string name="crop_label" msgid="521114301871349328">"Beskær billede"</string>
<string name="select_image" msgid="7841406150484742140">"Vælg foto"</string>
@@ -45,10 +46,13 @@
<string name="wallpaper" msgid="140165383777262070">"Angiver baggrund..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Tapet"</string>
<string name="delete" msgid="2839695998251824487">"Slet"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Slet"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Annuller"</string>
<string name="share" msgid="3619042788254195341">"Del"</string>
- <string name="select_all" msgid="3403283025220282175">"Marker alle"</string>
+ <string name="select_all" msgid="3403283025220282175">"Markér alle"</string>
<string name="deselect_all" msgid="5758897506061723684">"Fjern markering af alle"</string>
<string name="slideshow" msgid="4355906903247112975">"Diasshow"</string>
<string name="details" msgid="8415120088556445230">"Detaljer"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Utagget"</string>
<string name="no_location" msgid="4043624857489331676">"Ingen placering"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Nogle placeringer kunne ikke identificeres på grund af netværksproblemer."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Billederne i dette album kunne ikke downloades. Prøv igen senere."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Listen over albummer kunne ikke downloades. Prøv igen senere."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Billederne i dette album kunne ikke hentes. Prøv igen senere."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Listen over albummer kunne ikke downloades. Prøv igen senere."</string>
<string name="show_images_only" msgid="7263218480867672653">"Kun billeder"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Kun videoer"</string>
- <string name="show_all" msgid="4780647751652596980">"Billeder og videoer"</string>
+ <string name="show_all" msgid="6963292714584735149">"Billeder og videoer"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Billedgalleri"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Ingen billeder."</string>
<string name="crop_saved" msgid="1062612625032731770">"Beskåret billede gemmes i Downloads."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Grupper efter"</string>
<string name="settings" msgid="1534847740615665736">"Indstillinger"</string>
<string name="add_account" msgid="4271217504968243974">"Tilføj konto"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Download"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importeret"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Skærmbillede"</string>
+ <string name="help" msgid="7368960711153618354">"Hjælp"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index a979399..05b4170 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Mit Wiedergabe fortfahren"</string>
<string name="loading" msgid="7038208555304563571">"Wird geladen..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Konnte nicht geladen werden."</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Bild konnte nicht geladen werden."</string>
<string name="no_thumbnail" msgid="284723185546429750">"Keine Miniaturansicht"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Starten"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Zuschneiden"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Zum Beginnen auf ein Gesicht tippen"</string>
<string name="saving_image" msgid="7270334453636349407">"Bild wird gespeichert..."</string>
<string name="save_error" msgid="6857408774183654970">"Zugeschnittenes Bild nicht gespeichert"</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Hintergrund wird festgelegt..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Hintergrund"</string>
<string name="delete" msgid="2839695998251824487">"Löschen"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Löschen"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Abbrechen"</string>
<string name="share" msgid="3619042788254195341">"Teilen"</string>
<string name="select_all" msgid="3403283025220282175">"Alle auswählen"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Nicht getaggt"</string>
<string name="no_location" msgid="4043624857489331676">"Kein Aufnahmeort"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Einige Standorte konnten aufgrund von Netzwerkproblemen nicht identifiziert werden."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Die Fotos aus diesem Album konnten nicht heruntergeladen werden. Bitte versuchen Sie es später erneut."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Die Albenliste konnte nicht heruntergeladen werden. Bitte versuchen Sie es später erneut."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Die Fotos aus diesem Album konnten nicht heruntergeladen werden. Bitte versuchen Sie es später erneut."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Die Albenliste konnte nicht heruntergeladen werden. Bitte versuchen Sie es später erneut."</string>
<string name="show_images_only" msgid="7263218480867672653">"Nur Bilder"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Nur Videos"</string>
- <string name="show_all" msgid="4780647751652596980">"Bilder und Videos"</string>
+ <string name="show_all" msgid="6963292714584735149">"Bilder und Videos"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotogalerie"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Keine Fotos vorhanden"</string>
<string name="crop_saved" msgid="1062612625032731770">"Zugeschnittenes Bild unter Downloads gespeichert"</string>
@@ -127,7 +131,7 @@
<string name="iso" msgid="5028296664327335940">"ISO"</string>
<string name="unit_mm" msgid="1125768433254329136">"mm"</string>
<string name="manual" msgid="6608905477477607865">"Manuell"</string>
- <string name="auto" msgid="4296941368722892821">"Autom."</string>
+ <string name="auto" msgid="4296941368722892821">"Automatisch"</string>
<string name="flash_on" msgid="7891556231891837284">"Blitz ausgelöst"</string>
<string name="flash_off" msgid="1445443413822680010">"Ohne Blitz"</string>
<plurals name="make_albums_available_offline">
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Gruppieren nach"</string>
<string name="settings" msgid="1534847740615665736">"Einstellungen"</string>
<string name="add_account" msgid="4271217504968243974">"Konto hinzufügen"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Downloads"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importiert"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Screenshots"</string>
+ <string name="help" msgid="7368960711153618354">"Hilfe"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 4d1e2e0..4b5b3c3 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Συνέχιση αναπαραγωγής"</string>
<string name="loading" msgid="7038208555304563571">"Φόρτωση..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Δεν ήταν δυνατή η φόρτωση"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Δεν ήταν δυνατή η φόρτωση της εικόνας"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Δεν υπάρχει μικρογραφία"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Έναρξη από την αρχή"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Περικοπή"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Αγγίξτε κάποιο πρόσωπο για να ξεκινήσετε."</string>
<string name="saving_image" msgid="7270334453636349407">"Αποθήκευση εικόνας..."</string>
<string name="save_error" msgid="6857408774183654970">"Αδυναμία αποθήκευσης αποκομμένης εικόνας"</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Ορισμός ταπετσαρίας..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Ταπετσαρία"</string>
<string name="delete" msgid="2839695998251824487">"Διαγραφή"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Διαγραφή"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Ακύρωση"</string>
<string name="share" msgid="3619042788254195341">"Κοινή χρήση"</string>
<string name="select_all" msgid="3403283025220282175">"Επιλογή όλων"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Χωρίς ετικέτα"</string>
<string name="no_location" msgid="4043624857489331676">"Καμία τοποθεσία"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Δεν ήταν δυνατός ο προσδιορισμός ορισμένων τοποθεσιών λόγω προβλημάτων δικτύου."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Αποτυχία λήψης των φωτογραφιών σε αυτό το λεύκωμα. Δοκιμάστε ξανά αργότερα."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Αποτυχία λήψης της λίστας λευκωμάτων. Δοκιμάστε ξανά αργότερα."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Δεν ήταν δυνατή η λήψη των φωτογραφιών σε αυτό το λεύκωμα. Δοκιμάστε ξανά αργότερα."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Δεν ήταν δυνατή η λήψη της λίστας λευκωμάτων. Δοκιμάστε ξανά αργότερα."</string>
<string name="show_images_only" msgid="7263218480867672653">"Μόνο εικόνες"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Μόνο βίντεο"</string>
- <string name="show_all" msgid="4780647751652596980">"Εικόνες και βίντεο"</string>
+ <string name="show_all" msgid="6963292714584735149">"Εικόνες και βίντεο"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Συλλογή φωτογραφιών"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Δεν υπάρχουν φωτογραφίες."</string>
<string name="crop_saved" msgid="1062612625032731770">"Η αποκομμένη εικόνα αποθηκεύτηκε στις \"Λήψεις\"."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Ομαδοποίηση κατά"</string>
<string name="settings" msgid="1534847740615665736">"Ρυθμίσεις"</string>
<string name="add_account" msgid="4271217504968243974">"Προσθήκη λογαριασμού"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Φωτογραφική μηχανή"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Λήψη"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Έγινε εισαγωγή"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Στιγμιότυπο οθόνης"</string>
+ <string name="help" msgid="7368960711153618354">"Βοήθεια"</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index ca72583..0bdceb4 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Resume playing"</string>
<string name="loading" msgid="7038208555304563571">"Loading…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Couldn\'t load"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Couldn\'t load the image"</string>
<string name="no_thumbnail" msgid="284723185546429750">"No thumbnail"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Start again"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Crop"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Touch a face to begin."</string>
<string name="saving_image" msgid="7270334453636349407">"Saving picture…"</string>
<string name="save_error" msgid="6857408774183654970">"Couldn\'t save cropped image."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Setting wallpaper..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Wallpaper"</string>
<string name="delete" msgid="2839695998251824487">"Delete"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Delete"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Cancel"</string>
<string name="share" msgid="3619042788254195341">"Share"</string>
<string name="select_all" msgid="3403283025220282175">"Select all"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Untagged"</string>
<string name="no_location" msgid="4043624857489331676">"No location"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Some locations couldn\'t be identified due to network problems."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Failed to download the photos in this album. Please retry later."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Failed to download the list of albums. Please retry later."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Couldn\'t download the photos in this album. Retry later."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Couldn\'t download the list of albums. Retry later."</string>
<string name="show_images_only" msgid="7263218480867672653">"Images only"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Videos only"</string>
- <string name="show_all" msgid="4780647751652596980">"Images and videos"</string>
+ <string name="show_all" msgid="6963292714584735149">"Images & videos"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Photo Gallery"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"No photos."</string>
<string name="crop_saved" msgid="1062612625032731770">"Cropped image saved to Downloads."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Group by"</string>
<string name="settings" msgid="1534847740615665736">"Settings"</string>
<string name="add_account" msgid="4271217504968243974">"Add account"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Camera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Download"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Imported"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Screenshot"</string>
+ <string name="help" msgid="7368960711153618354">"Was this helpful?"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index af0aeac..9a13cdf 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Retomar la reproducción"</string>
<string name="loading" msgid="7038208555304563571">"Cargando…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"No se pudo cargar"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"No se pudo cargar la imagen."</string>
<string name="no_thumbnail" msgid="284723185546429750">"Sin miniatura"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Empezar de nuevo"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Cortar"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"Aceptar"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Toca una cara para comenzar."</string>
<string name="saving_image" msgid="7270334453636349407">"Guardando imagen..."</string>
<string name="save_error" msgid="6857408774183654970">"No se pudo guardar la imagen recortada."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Estableciendo fondo de pantalla..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Papel tapiz"</string>
<string name="delete" msgid="2839695998251824487">"Eliminar"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Borrar"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Cancelar"</string>
<string name="share" msgid="3619042788254195341">"Compartir"</string>
<string name="select_all" msgid="3403283025220282175">"Seleccionar todo"</string>
@@ -53,7 +57,7 @@
<string name="slideshow" msgid="4355906903247112975">"Presentación de diapositivas"</string>
<string name="details" msgid="8415120088556445230">"Detalles"</string>
<string name="details_title" msgid="2611396603977441273">"%1$d de %2$d elementos:"</string>
- <string name="close" msgid="5585646033158453043">"Cerca"</string>
+ <string name="close" msgid="5585646033158453043">"Cerrar"</string>
<string name="switch_to_camera" msgid="7280111806675169992">"Cambiar a cámara"</string>
<plurals name="number_of_items_selected">
<item quantity="zero" msgid="2142579311530586258">"%1$d seleccionado(s)"</item>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"No etiquetado"</string>
<string name="no_location" msgid="4043624857489331676">"No hay ubicación"</string>
<string name="no_connectivity" msgid="7164037617297293668">"No se pudieron identificar algunas ubicaciones debido a problemas de la red."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Se produjo un error al descargar las fotos en este álbum. Vuelve a intentarlo más tarde."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Se produjo un error al descargar la lista de álbumes. Vuelve a intentarlo más tarde."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"No se pudieron descargar las fotos de este álbum. Vuelve a intentarlo más adelante."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"No se pudo descargar la lista de álbumes. Vuelve a intentarlo más adelante."</string>
<string name="show_images_only" msgid="7263218480867672653">"Sólo imágenes"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Sólo videos"</string>
- <string name="show_all" msgid="4780647751652596980">"Imágenes y videos"</string>
+ <string name="show_all" msgid="6963292714584735149">"Imágenes y videos"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galería de fotos"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"No hay fotos."</string>
<string name="crop_saved" msgid="1062612625032731770">"La imagen recortada se guardó en Descargas."</string>
@@ -155,11 +159,16 @@
<string name="widget_type" msgid="1364653978966343448">"Elegir imágenes"</string>
<string name="slideshow_dream_name" msgid="6915963319933437083">"Presentación de diapositivas"</string>
<string name="albums" msgid="7320787705180057947">"Álbumes"</string>
- <string name="times" msgid="2023033894889499219">"Horarios"</string>
- <string name="locations" msgid="6649297994083130305">"Ubicaciones"</string>
+ <string name="times" msgid="2023033894889499219">"Fecha"</string>
+ <string name="locations" msgid="6649297994083130305">"Ubicación"</string>
<string name="people" msgid="4114003823747292747">"Personas"</string>
<string name="tags" msgid="5539648765482935955">"Etiquetas"</string>
<string name="group_by" msgid="4308299657902209357">"Agrupar por"</string>
<string name="settings" msgid="1534847740615665736">"Configuración"</string>
<string name="add_account" msgid="4271217504968243974">"Agregar cuenta"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Cámara"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Descargas"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importadas"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Capturas de pantalla"</string>
+ <string name="help" msgid="7368960711153618354">"Ayuda"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index b0b2178..dcca6e6 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Reanudar reproducción"</string>
<string name="loading" msgid="7038208555304563571">"Cargando..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Error al cargar"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"No se puede cargar la imagen."</string>
<string name="no_thumbnail" msgid="284723185546429750">"No hay miniaturas."</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Volver a reproducir"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Recortar"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"Aceptar"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Toca una cara para comenzar."</string>
<string name="saving_image" msgid="7270334453636349407">"Guardando imagen..."</string>
<string name="save_error" msgid="6857408774183654970">"Error al guardar la imagen recortada"</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Estableciendo fondo de pantalla..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Fondo de pantalla"</string>
<string name="delete" msgid="2839695998251824487">"Borrar"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Eliminar"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Cancelar"</string>
<string name="share" msgid="3619042788254195341">"Compartir"</string>
<string name="select_all" msgid="3403283025220282175">"Seleccionar todo"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Sin etiquetas"</string>
<string name="no_location" msgid="4043624857489331676">"Sin ubicación"</string>
<string name="no_connectivity" msgid="7164037617297293668">"No se ha podido identificar algunas ubicaciones debido a errores en la red."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Error al descargar las fotos del álbum. Inténtalo de nuevo más tarde."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Error al descargar la lista de álbumes. Inténtalo de nuevo más tarde."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"No se ha podido descargar las fotos del álbum. Inténtalo de nuevo más tarde."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"No se ha podido descargar la lista de álbumes. Inténtalo de nuevo más tarde."</string>
<string name="show_images_only" msgid="7263218480867672653">"Solo imágenes"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Solo vídeos"</string>
- <string name="show_all" msgid="4780647751652596980">"Imágenes y vídeos"</string>
+ <string name="show_all" msgid="6963292714584735149">"Imágenes y vídeos"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galería de fotos"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"No hay fotos."</string>
<string name="crop_saved" msgid="1062612625032731770">"La imagen recortada se ha guardado en Descargas."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Agrupar por"</string>
<string name="settings" msgid="1534847740615665736">"Ajustes"</string>
<string name="add_account" msgid="4271217504968243974">"Añadir cuenta"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Cámara"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Descargadas"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importadas"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Capturas de pantalla"</string>
+ <string name="help" msgid="7368960711153618354">"Ayuda"</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 1072aa7..a7bc4ed 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Jätka esitust"</string>
<string name="loading" msgid="7038208555304563571">"Laadimine ..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Laadimine ebaõnnestus"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Kujutist ei õnnestunud laadida"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Pisipilti pole"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Alusta uuesti"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Kärbi"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Alustamiseks näo puudutamine."</string>
<string name="saving_image" msgid="7270334453636349407">"Pildi salvestamine ..."</string>
<string name="save_error" msgid="6857408774183654970">"Kärbitud kujutist ei saanud salvestada."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Taustapildi määramine ..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Taustapilt"</string>
<string name="delete" msgid="2839695998251824487">"Kustuta"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Kustuta"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Tühista"</string>
<string name="share" msgid="3619042788254195341">"Jaga"</string>
<string name="select_all" msgid="3403283025220282175">"Vali kõik"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Märgendita"</string>
<string name="no_location" msgid="4043624857489331676">"Asukoht puudub"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Mõnda asukohta ei suudetud võrguprobleemide tõttu tuvastada."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Selle albumi fotode allalaadimine ebaõnnestus. Proovige hiljem uuesti."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Albumite loetelu allalaadimine ebaõnnestus. Proovige hiljem uuesti."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Fotosid ei saa sellesse albumisse alla laadida. Proovige hiljem uuesti."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Albumite loendit ei saa alla laadida. Proovige hiljem uuesti."</string>
<string name="show_images_only" msgid="7263218480867672653">"Ainult kujutised"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Ainult videod"</string>
- <string name="show_all" msgid="4780647751652596980">"Kujutised ja videod"</string>
+ <string name="show_all" msgid="6963292714584735149">"Kujutised ja videod"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotogalerii"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Fotod puuduvad."</string>
<string name="crop_saved" msgid="1062612625032731770">"Kärbitud kujutis salvestati allalaadimiste kausta."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Grupeerimisalus:"</string>
<string name="settings" msgid="1534847740615665736">"Seaded"</string>
<string name="add_account" msgid="4271217504968243974">"Lisa konto"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kaamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Alla laaditud"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Imporditud"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Ekraanipilt"</string>
+ <string name="help" msgid="7368960711153618354">"Abi"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 9017b40..cecd272 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"از سرگیری پخش"</string>
<string name="loading" msgid="7038208555304563571">"در حال بارگیری…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"بارگیری نشد"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"تصویر بارگیری نمیشود"</string>
<string name="no_thumbnail" msgid="284723185546429750">"تصویر کوچکی وجود ندارد"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"شروع مجدد"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"برش"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"تائید"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"برای شروع یک چهره را لمس کنید."</string>
<string name="saving_image" msgid="7270334453636349407">"در حال ذخیره عکس..."</string>
<string name="save_error" msgid="6857408774183654970">"ذخیره تصویر برشخورده امکانپذیر نیست."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"تنظیم تصویر زمینه..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"تصویر زمینه"</string>
<string name="delete" msgid="2839695998251824487">"حذف"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"حذف"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"لغو"</string>
<string name="share" msgid="3619042788254195341">"اشتراک گذاری"</string>
<string name="select_all" msgid="3403283025220282175">"انتخاب همه"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"بدون برچسب گذاری"</string>
<string name="no_location" msgid="4043624857489331676">"مکانی موجود نیست"</string>
<string name="no_connectivity" msgid="7164037617297293668">"شناسایی برخی از مکانها به دلیل مشکلات شبکه امکانپذیر نیست."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"عکسها در این آلبوم دانلود نشد. لطفاً بعداً دوباره امتحان کنید."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"لیست آلبومها دانلود نشد. لطفاً بعداً دوباره امتحان کنید."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"عکسهای این آلبوم را نمیتوان دانلود کرد. بعداً دوباره امتحان کنید."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"لیست آلبومها را نمیتوان دانلود کرد. بعداً دوباره امتحان کنید."</string>
<string name="show_images_only" msgid="7263218480867672653">"فقط تصاویر"</string>
<string name="show_videos_only" msgid="3850394623678871697">"فقط ویدیوها"</string>
- <string name="show_all" msgid="4780647751652596980">"تصاویر و ویدیوها"</string>
+ <string name="show_all" msgid="6963292714584735149">"تصاویر و ویدیوها"</string>
<string name="appwidget_title" msgid="6410561146863700411">"گالری عکس"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"عکسی موجود نیست."</string>
<string name="crop_saved" msgid="1062612625032731770">"تصویر بریده شده در \"دانلودها\" ذخیره شد."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"گروه بندی براساس"</string>
<string name="settings" msgid="1534847740615665736">"تنظیمات"</string>
<string name="add_account" msgid="4271217504968243974">"افزودن حساب"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"دوربین"</string>
+ <string name="folder_download" msgid="7186215137642323932">"دانلود"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"وارد شده"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"عکس صفحه"</string>
+ <string name="help" msgid="7368960711153618354">"راهنما"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 2527a70..9f370ea 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Jatka toistoa"</string>
<string name="loading" msgid="7038208555304563571">"Ladataan…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Ei voi ladata"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Kuvaa ei voi ladata"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Ei pikkukuvaa"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Aloita alusta"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Rajaa"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Aloita koskettamalla kasvoja."</string>
<string name="saving_image" msgid="7270334453636349407">"Tallennetaan kuvaa…"</string>
<string name="save_error" msgid="6857408774183654970">"Rajattua kuvaa ei voitu tallentaa."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Asetetaan taustakuvaa..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"taustakuvaksi"</string>
<string name="delete" msgid="2839695998251824487">"Poista"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Poista"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Peruuta"</string>
<string name="share" msgid="3619042788254195341">"Jaa"</string>
<string name="select_all" msgid="3403283025220282175">"Valitse kaikki"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Merkitsemättömät"</string>
<string name="no_location" msgid="4043624857489331676">"Ei sijaintia"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Verkkoyhteysongelma – joitakin paikkoja ei voi tunnistaa."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Albumin valokuvien lataaminen epäonnistui. Yritä myöhemmin uudelleen."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Albumiluettelon lataaminen epäonnistui. Yritä myöhemmin uudelleen."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Albumin valokuvia ei voi ladata. Yritä myöhemmin uudelleen."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Albumiluetteloa ei voi ladata. Yritä myöhemmin uudelleen."</string>
<string name="show_images_only" msgid="7263218480867672653">"Vain kuvat"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Vain videot"</string>
- <string name="show_all" msgid="4780647751652596980">"Kuvat ja videot"</string>
+ <string name="show_all" msgid="6963292714584735149">"Kuvat ja videot"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Kuvagalleria"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Ei valokuvia."</string>
<string name="crop_saved" msgid="1062612625032731770">"Rajattu kuva tallennetaan Lataukset-kansioon."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Ryhmittely:"</string>
<string name="settings" msgid="1534847740615665736">"Asetukset"</string>
<string name="add_account" msgid="4271217504968243974">"Lisää tili"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Lataus"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Tuonti"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Kuvakaappaus"</string>
+ <string name="help" msgid="7368960711153618354">"Ohje"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 4873b9f..36c04f6 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Reprendre la lecture"</string>
<string name="loading" msgid="7038208555304563571">"Chargement en cours…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Impossible de charger"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Impossible de charger l\'image."</string>
<string name="no_thumbnail" msgid="284723185546429750">"Aucune vignette"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Démarrer"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Rogner"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Appuyer sur un visage pour commencer"</string>
<string name="saving_image" msgid="7270334453636349407">"Enregistrement de l\'image"</string>
<string name="save_error" msgid="6857408774183654970">"Impossible d\'enregistrer l\'image rognée."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Définition du fond d\'écran…"</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Fond d\'écran"</string>
<string name="delete" msgid="2839695998251824487">"Supprimer"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Supprimer"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Annuler"</string>
<string name="share" msgid="3619042788254195341">"Partager"</string>
<string name="select_all" msgid="3403283025220282175">"Tout sélectionner"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Aucun tag"</string>
<string name="no_location" msgid="4043624857489331676">"Aucun lieu"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Certains lieux n\'ont pas pu être identifiés en raison de problèmes réseau."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Impossible de télécharger les photos dans cet album. Veuillez réessayer ultérieurement."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Impossible de télécharger la liste des albums. Veuillez réessayer ultérieurement."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Impossible de télécharger les photos de cet album. Veuillez réessayer ultérieurement."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Impossible de télécharger la liste des albums. Veuillez réessayer ultérieurement."</string>
<string name="show_images_only" msgid="7263218480867672653">"Images uniquement"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Vidéos uniquement"</string>
- <string name="show_all" msgid="4780647751652596980">"Images et vidéos"</string>
+ <string name="show_all" msgid="6963292714584735149">"Images et vidéos"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galerie photos"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Aucune photo"</string>
<string name="crop_saved" msgid="1062612625032731770">"Image rognée enregistrée dans \"Téléchargements\"."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Regrouper par"</string>
<string name="settings" msgid="1534847740615665736">"Paramètres"</string>
<string name="add_account" msgid="4271217504968243974">"Ajouter un compte"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Appareil photo"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Téléchargements"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importations"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Captures d\'écran"</string>
+ <string name="help" msgid="7368960711153618354">"Aide"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 7788858..93d95fa 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"चलाना फिर से शुरू करें"</string>
<string name="loading" msgid="7038208555304563571">"लोड हो रहा है..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"लोड नहीं कर सका"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"छवि लोड नहीं हो सकी"</string>
<string name="no_thumbnail" msgid="284723185546429750">"कोई थंबनेल नहीं"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"पुन: प्रारंभ करें"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"काट-छांट करें"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"ठीक"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"शुरू करने के लिए कोई चेहरा स्पर्श करें."</string>
<string name="saving_image" msgid="7270334453636349407">"चित्र सहेज रहा है…"</string>
<string name="save_error" msgid="6857408774183654970">"काट-छांट की गई छवि को नहीं सहेज सका."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"वॉलपेपर सेट कर रहा है..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"वॉलपेपर"</string>
<string name="delete" msgid="2839695998251824487">"हटाएं"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"हटाएं"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"रद्द करें"</string>
<string name="share" msgid="3619042788254195341">"शेयर करें"</string>
<string name="select_all" msgid="3403283025220282175">"सभी का चयन करें"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"टैग नहीं किया गया"</string>
<string name="no_location" msgid="4043624857489331676">"कोई स्थान नहीं"</string>
<string name="no_connectivity" msgid="7164037617297293668">"नेटवर्क समस्याओं के कारण कुछ स्थानों को पहचाना नहीं जा सका."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"इस एल्बम में फ़ोटो डाउनलोड करना विफल हो गया है. कृपया बाद में पुनः प्रयास करें."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"एल्बम की सूची डाउनलोड करना विफल हो गया है. कृपया बाद में पुनः प्रयास करें."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"इस एल्बम के फ़ोटो डाउनलोड नहीं किए जा सके. बाद में पुन: प्रयास करें."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"एल्बम की सूची डाउनलोड नहीं की जा सकी. बाद में पुन: प्रयास करें."</string>
<string name="show_images_only" msgid="7263218480867672653">"केवल छवियां"</string>
<string name="show_videos_only" msgid="3850394623678871697">"केवल वीडियो"</string>
- <string name="show_all" msgid="4780647751652596980">"छवियां और वीडियो"</string>
+ <string name="show_all" msgid="6963292714584735149">"छवियां और वीडियो"</string>
<string name="appwidget_title" msgid="6410561146863700411">"फ़ोटो गैलरी"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"कोई फ़ोटो नहीं."</string>
<string name="crop_saved" msgid="1062612625032731770">"काट-छांट की गई छवि को डाउनलोड में सहेजा गया."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"इसके द्वारा समूहीकृत"</string>
<string name="settings" msgid="1534847740615665736">"सेटिंग"</string>
<string name="add_account" msgid="4271217504968243974">"खाता जोड़ें"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"कैमरा"</string>
+ <string name="folder_download" msgid="7186215137642323932">"डाउनलोड करें"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"आयातित"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"स्क्रीनशॉट"</string>
+ <string name="help" msgid="7368960711153618354">"सहायता"</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 6c57869..f2c755b 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Nastavak reprodukcije"</string>
<string name="loading" msgid="7038208555304563571">"Učitavanje…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Nije bilo moguće učitati"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Nije moguće učitati sliku"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Nema minijatura"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Počni ispočetka"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Obreži"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"U redu"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Dodirnite lice za početak."</string>
<string name="saving_image" msgid="7270334453636349407">"Spremanje slike..."</string>
<string name="save_error" msgid="6857408774183654970">"Nije moguće spremiti obrezanu sliku."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Postavljanje pozadinske slike…"</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Pozadinska slika"</string>
<string name="delete" msgid="2839695998251824487">"Izbriši"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Izbriši"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Odustani"</string>
<string name="share" msgid="3619042788254195341">"Podijeli"</string>
<string name="select_all" msgid="3403283025220282175">"Odaberi sve"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Neoznačeno"</string>
<string name="no_location" msgid="4043624857489331676">"Nema lokacije"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Neke lokacije nisu se mogle identificirati zbog poteškoća s mrežnom."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Preuzimanje fotografija iz ovog albuma nije uspjelo. Pokušajte opet kasnije."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Preuzimanje popisa albuma nije uspjelo. Pokušajte opet kasnije."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Preuzimanje fotografija iz ovog albuma nije uspjelo. Pokušajte ponovo kasnije."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Preuzimanje popisa albuma nije uspjelo. Pokušajte ponovo kasnije."</string>
<string name="show_images_only" msgid="7263218480867672653">"Samo slike"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Samo videozapisi"</string>
- <string name="show_all" msgid="4780647751652596980">"Slike i videozapisi"</string>
+ <string name="show_all" msgid="6963292714584735149">"Slike i videozapisi"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galerija fotografija"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Nema fotografija."</string>
<string name="crop_saved" msgid="1062612625032731770">"Obrezana slika spremljena u Preuzimanja."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Grupiraj po"</string>
<string name="settings" msgid="1534847740615665736">"Postavke"</string>
<string name="add_account" msgid="4271217504968243974">"Dodaj račun"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Fotoaparat"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Preuzimanja"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Uvezeno"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Snimak zaslona"</string>
+ <string name="help" msgid="7368960711153618354">"Pomoć"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index dd905ae..f07ab56 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Lejátszás folytatása"</string>
<string name="loading" msgid="7038208555304563571">"Betöltés…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Nem sikerült betölteni"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Nem sikerült betölteni a képet"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Nincs indexkép"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Újrakezdés"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Körülvágás"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Érintsen meg egy arcot a kezdéshez."</string>
<string name="saving_image" msgid="7270334453636349407">"Kép mentése..."</string>
<string name="save_error" msgid="6857408774183654970">"Nem lehet menteni a vágott képet."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Háttérkép beállítása..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Háttérkép"</string>
<string name="delete" msgid="2839695998251824487">"Törlés"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Törlés"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Mégse"</string>
<string name="share" msgid="3619042788254195341">"Megosztás"</string>
<string name="select_all" msgid="3403283025220282175">"Az összes kijelölése"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Címke nélküli"</string>
<string name="no_location" msgid="4043624857489331676">"Nincs helyadat"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Néhány helyet nem lehetett azonosítani hálózati problémák miatt."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Nem sikerült letölteni az albumban található képeket. Kérjük, próbálja újra később."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Nem sikerült letölteni az albumok listáját. Kérjük, próbálja újra később."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Nem sikerült letölteni ennek az albumnak a képeit. Próbálja újra később."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Nem sikerült letölteni az albumok listáját. Próbálja újra később."</string>
<string name="show_images_only" msgid="7263218480867672653">"Csak képek"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Csak videók"</string>
- <string name="show_all" msgid="4780647751652596980">"Képek és videók"</string>
+ <string name="show_all" msgid="6963292714584735149">"Képek és videók"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotógaléria"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Nincsenek fényképek."</string>
<string name="crop_saved" msgid="1062612625032731770">"A vágott kép a Letöltések közé került."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Csoportosítás"</string>
<string name="settings" msgid="1534847740615665736">"Beállítások"</string>
<string name="add_account" msgid="4271217504968243974">"Fiók hozzáadása"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Letöltés"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importált"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Képernyőkép"</string>
+ <string name="help" msgid="7368960711153618354">"Súgó"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 9f62b25..48d03ed 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Lanjutkan pemutaran"</string>
<string name="loading" msgid="7038208555304563571">"Memuat…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Tidak dapat memuat"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Tidak dapat memuat gambar"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Tidak ada gambar mini"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Memulai"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Pangkas"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"Oke"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Sentuh wajah untuk memulai."</string>
<string name="saving_image" msgid="7270334453636349407">"Menyimpan gambar…"</string>
<string name="save_error" msgid="6857408774183654970">"Tak dapat menyimpan gambar yg dipangkas."</string>
@@ -45,12 +46,15 @@
<string name="wallpaper" msgid="140165383777262070">"Menyetel wallpaper..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Wallpaper"</string>
<string name="delete" msgid="2839695998251824487">"Hapus"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Hapus"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Batal"</string>
<string name="share" msgid="3619042788254195341">"Bagikan"</string>
<string name="select_all" msgid="3403283025220282175">"Pilih semua"</string>
<string name="deselect_all" msgid="5758897506061723684">"Batalkan semua pilihan"</string>
- <string name="slideshow" msgid="4355906903247112975">"Rangkai salindia"</string>
+ <string name="slideshow" msgid="4355906903247112975">"Rangkai slide"</string>
<string name="details" msgid="8415120088556445230">"Detail"</string>
<string name="details_title" msgid="2611396603977441273">"%1$d dari %2$d item:"</string>
<string name="close" msgid="5585646033158453043">"Tutup"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Tidak di-tag"</string>
<string name="no_location" msgid="4043624857489331676">"Tidak ada lokasi"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Beberapa lokasi tidak dapat diidentifikasi karena masalah jaringan."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Gagal mengunduh foto dalam album ini. Coba lagi nanti."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Gagal mengunduh daftar album. Coba lagi nanti."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Tidak dapat mengunduh foto di album ini. Coba lagi nanti."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Tidak dapat mengunduh daftar album. Coba lagi nanti."</string>
<string name="show_images_only" msgid="7263218480867672653">"Hanya gambar"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Hanya video"</string>
- <string name="show_all" msgid="4780647751652596980">"Gambar dan video"</string>
+ <string name="show_all" msgid="6963292714584735149">"Gambar & video"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galeri Foto"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Tidak ada foto."</string>
<string name="crop_saved" msgid="1062612625032731770">"Gambar yang dipangkas disimpan ke Unduhan."</string>
@@ -102,12 +106,12 @@
<string name="no_albums_alert" msgid="4111744447491690896">"Tidak ada album yang tersedia."</string>
<string name="empty_album" msgid="4542880442593595494">"O gambar/video tersedia."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Pos"</string>
- <string name="make_available_offline" msgid="5157950985488297112">"Jadikan agar tersedia luring"</string>
+ <string name="make_available_offline" msgid="5157950985488297112">"Jadikan agar tersedia offline"</string>
<string name="sync_picasa_albums" msgid="8522572542111169872">"Segarkan"</string>
<string name="done" msgid="217672440064436595">"Selesai"</string>
<string name="sequence_in_set" msgid="7235465319919457488">"%1$d dari %2$d item:"</string>
<string name="title" msgid="7622928349908052569">"Judul"</string>
- <string name="description" msgid="3016729318096557520">"Uraian"</string>
+ <string name="description" msgid="3016729318096557520">"Deskripsi"</string>
<string name="time" msgid="1367953006052876956">"Waktu"</string>
<string name="location" msgid="3432705876921618314">"Lokasi"</string>
<string name="path" msgid="4725740395885105824">"Jalur"</string>
@@ -116,7 +120,7 @@
<string name="orientation" msgid="4958327983165245513">"Orientasi"</string>
<string name="duration" msgid="8160058911218541616">"Durasi"</string>
<string name="mimetype" msgid="8024168704337990470">"Jenis MIME"</string>
- <string name="file_size" msgid="8486169301588318915">"Ukuran berkas"</string>
+ <string name="file_size" msgid="8486169301588318915">"Ukuran file"</string>
<string name="maker" msgid="7921835498034236197">"Pembuat"</string>
<string name="model" msgid="8240207064064337366">"Model"</string>
<string name="flash" msgid="2816779031261147723">"Lampu Kilat"</string>
@@ -131,10 +135,10 @@
<string name="flash_on" msgid="7891556231891837284">"Lampu kilat aktif"</string>
<string name="flash_off" msgid="1445443413822680010">"Tanpa lampu kilat"</string>
<plurals name="make_albums_available_offline">
- <item quantity="one" msgid="2171596356101611086">"Menjadikan album tersedia secara luring."</item>
- <item quantity="other" msgid="4948604338155959389">"Menjadikan album tersedia secara luring."</item>
+ <item quantity="one" msgid="2171596356101611086">"Menjadikan album tersedia secara offline."</item>
+ <item quantity="other" msgid="4948604338155959389">"Menjadikan album tersedia secara offline."</item>
</plurals>
- <string name="try_to_set_local_album_available_offline" msgid="2184754031896160755">"Item ini tersimpan secara lokal dan tersedia secara luring."</string>
+ <string name="try_to_set_local_album_available_offline" msgid="2184754031896160755">"Item ini tersimpan secara lokal dan tersedia secara offline."</string>
<string name="set_label_all_albums" msgid="4581863582996336783">"Semua album"</string>
<string name="set_label_local_albums" msgid="6698133661656266702">"Album lokal"</string>
<string name="set_label_mtp_devices" msgid="1283513183744896368">"Perangkat MTP"</string>
@@ -153,7 +157,7 @@
<string name="widget_type_shuffle" msgid="8594622705019763768">"Kocok semua gambar"</string>
<string name="widget_type_photo" msgid="6267065337367795355">"Pilih gambar"</string>
<string name="widget_type" msgid="1364653978966343448">"Pilih gambar"</string>
- <string name="slideshow_dream_name" msgid="6915963319933437083">"Rangkai salindia"</string>
+ <string name="slideshow_dream_name" msgid="6915963319933437083">"Rangkai slide"</string>
<string name="albums" msgid="7320787705180057947">"Album"</string>
<string name="times" msgid="2023033894889499219">"Waktu"</string>
<string name="locations" msgid="6649297994083130305">"Lokasi"</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Kelompokkan menurut"</string>
<string name="settings" msgid="1534847740615665736">"Setelan"</string>
<string name="add_account" msgid="4271217504968243974">"Tambah akun"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Unduhan"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Diimpor"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Tangkapan Layar"</string>
+ <string name="help" msgid="7368960711153618354">"Bantuan"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 321bf67..e1975ac 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Riprendi riproduzione"</string>
<string name="loading" msgid="7038208555304563571">"Caricamento..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Impossibile caricare"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Impossibile caricare l\'immagine"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Nessuna miniatura"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Ricomincia"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Ritaglia"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Tocca un viso per iniziare."</string>
<string name="saving_image" msgid="7270334453636349407">"Salvataggio foto..."</string>
<string name="save_error" msgid="6857408774183654970">"Impossibile salvare immagine ritagliata."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Impostazione sfondo..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Sfondo"</string>
<string name="delete" msgid="2839695998251824487">"Elimina"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Elimina"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Annulla"</string>
<string name="share" msgid="3619042788254195341">"Condividi"</string>
<string name="select_all" msgid="3403283025220282175">"Seleziona tutti"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Senza tag"</string>
<string name="no_location" msgid="4043624857489331676">"Nessun luogo"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Impossibile identificare alcune località a causa di problemi di rete."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Download delle foto in questo album non riuscito. Riprova più tardi."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Download dell\'elenco di album non riuscito. Riprova più tardi."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Download delle foto in questo album non riuscito. Riprova più tardi."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Download dell\'elenco di album non riuscito. Riprova più tardi."</string>
<string name="show_images_only" msgid="7263218480867672653">"Solo immagini"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Solo video"</string>
- <string name="show_all" msgid="4780647751652596980">"Immagini e video"</string>
+ <string name="show_all" msgid="6963292714584735149">"Immagini e video"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galleria fotografica"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Nessuna foto."</string>
<string name="crop_saved" msgid="1062612625032731770">"Immagine ritagliata salvata in Download."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Raggruppa per"</string>
<string name="settings" msgid="1534847740615665736">"Impostazioni"</string>
<string name="add_account" msgid="4271217504968243974">"Aggiungi account"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Fotocamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Download"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importate"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Screenshot"</string>
+ <string name="help" msgid="7368960711153618354">"Guida"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 76e7133..41bd60a 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"המשך את ההפעלה"</string>
<string name="loading" msgid="7038208555304563571">"טוען..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"לא ניתן לטעון"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"לא היתה אפשרות לטעון את התמונה"</string>
<string name="no_thumbnail" msgid="284723185546429750">"ללא תמונה ממוזערת"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"התחל מחדש"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"חתוך"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"אישור"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"גע בפנים כלשהם כדי להתחיל."</string>
<string name="saving_image" msgid="7270334453636349407">"שומר תמונה..."</string>
<string name="save_error" msgid="6857408774183654970">"לא ניתן לשמור את התמונה החתוכה."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"מגדיר טפט..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"טפט"</string>
<string name="delete" msgid="2839695998251824487">"מחק"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"מחק"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"ביטול"</string>
<string name="share" msgid="3619042788254195341">"שיתוף"</string>
<string name="select_all" msgid="3403283025220282175">"בחר הכול"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"ללא תג"</string>
<string name="no_location" msgid="4043624857489331676">"ללא מיקום"</string>
<string name="no_connectivity" msgid="7164037617297293668">"לא ניתן לזהות מיקומים מסוימים בשל בעיות ברשת."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"הורדת התמונות לאלבום זה נכשלה. נסה שוב מאוחר יותר."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"כשל בהורדת רשימת האלבומים. נסה שוב מאוחר יותר."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"לא ניתן להוריד את התמונות באלבום זה. נסה שוב מאוחר יותר."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"לא ניתן להוריד את רשימת האלבומים. נסה שוב מאוחר יותר."</string>
<string name="show_images_only" msgid="7263218480867672653">"תמונות בלבד"</string>
<string name="show_videos_only" msgid="3850394623678871697">"סרטונים בלבד"</string>
- <string name="show_all" msgid="4780647751652596980">"תמונות וסרטונים"</string>
+ <string name="show_all" msgid="6963292714584735149">"תמונות וסרטונים"</string>
<string name="appwidget_title" msgid="6410561146863700411">"גלריית תמונות"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"אין תמונות."</string>
<string name="crop_saved" msgid="1062612625032731770">"התמונה החתוכה נשמרה ב\'הורדות\'."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"קבץ לפי"</string>
<string name="settings" msgid="1534847740615665736">"הגדרות"</string>
<string name="add_account" msgid="4271217504968243974">"הוסף חשבון"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"מצלמה"</string>
+ <string name="folder_download" msgid="7186215137642323932">"הורד"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"מיובאות"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"צילום מסך"</string>
+ <string name="help" msgid="7368960711153618354">"עזרה"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 460fe6d..6ad2735 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"再生を再開"</string>
<string name="loading" msgid="7038208555304563571">"読み込み中..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"読み込めませんでした"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"画像を読み込めませんでした"</string>
<string name="no_thumbnail" msgid="284723185546429750">"サムネイルなし"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"最初から再生"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"トリミング"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"始めるには顔をタップします。"</string>
<string name="saving_image" msgid="7270334453636349407">"写真を保存中…"</string>
<string name="save_error" msgid="6857408774183654970">"トリミングした画像を保存できません。"</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"壁紙を設定しています..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"壁紙"</string>
<string name="delete" msgid="2839695998251824487">"削除"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"削除"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"キャンセル"</string>
<string name="share" msgid="3619042788254195341">"共有"</string>
<string name="select_all" msgid="3403283025220282175">"すべて選択"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"タグなし"</string>
<string name="no_location" msgid="4043624857489331676">"位置情報なし"</string>
<string name="no_connectivity" msgid="7164037617297293668">"ネットワークの問題により一部の位置情報を特定できませんでした。"</string>
- <string name="sync_album_error" msgid="2218733298953719785">"このアルバムの画像をダウンロードできませんでした。しばらくしてからもう一度お試しください。"</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"アルバムのリストをダウンロードできませんでした。しばらくしてからもう一度お試しください。"</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"このアルバムの画像をダウンロードできませんでした。しばらくしてからもう一度お試しください。"</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"アルバムのリストをダウンロードできませんでした。しばらくしてからもう一度お試しください。"</string>
<string name="show_images_only" msgid="7263218480867672653">"画像のみ"</string>
<string name="show_videos_only" msgid="3850394623678871697">"動画のみ"</string>
- <string name="show_all" msgid="4780647751652596980">"画像と動画"</string>
+ <string name="show_all" msgid="6963292714584735149">"画像と動画"</string>
<string name="appwidget_title" msgid="6410561146863700411">"フォトギャラリー"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"画像がありません。"</string>
<string name="crop_saved" msgid="1062612625032731770">"トリミングした画像を[ダウンロード]に保存しました。"</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"グループ化"</string>
<string name="settings" msgid="1534847740615665736">"設定"</string>
<string name="add_account" msgid="4271217504968243974">"アカウントを追加"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"カメラ"</string>
+ <string name="folder_download" msgid="7186215137642323932">"ダウンロード"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"インポート済み"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"スクリーンショット"</string>
+ <string name="help" msgid="7368960711153618354">"ヘルプ"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1f8f45b..e285dce 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"이어서 보기"</string>
<string name="loading" msgid="7038208555304563571">"로드 중..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"로드하지 못했습니다."</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"이미지를 로드할 수 없습니다."</string>
<string name="no_thumbnail" msgid="284723185546429750">"미리보기 이미지 없음"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"처음부터 보기"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"자르기"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"확인"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"시작하려면 얼굴을 터치하세요."</string>
<string name="saving_image" msgid="7270334453636349407">"사진 저장 중..."</string>
<string name="save_error" msgid="6857408774183654970">"잘린 이미지를 저장하지 못했습니다."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"배경화면을 설정하는 중..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"배경화면"</string>
<string name="delete" msgid="2839695998251824487">"삭제"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"삭제"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"취소"</string>
<string name="share" msgid="3619042788254195341">"공유"</string>
<string name="select_all" msgid="3403283025220282175">"모두 선택"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"태그 지정 안함"</string>
<string name="no_location" msgid="4043624857489331676">"위치 없음"</string>
<string name="no_connectivity" msgid="7164037617297293668">"네트워크 문제로 인해 일부 위치를 식별하지 못했습니다."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"앨범 사진을 다운로드하지 못했습니다. 나중에 다시 시도해 주세요."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"앨범 목록을 다운로드하지 못했습니다. 나중에 다시 시도해 주세요."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"앨범 사진을 다운로드하지 못했습니다. 나중에 다시 시도해 주세요."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"앨범 목록을 다운로드하지 못했습니다. 나중에 다시 시도해 주세요."</string>
<string name="show_images_only" msgid="7263218480867672653">"이미지"</string>
<string name="show_videos_only" msgid="3850394623678871697">"동영상"</string>
- <string name="show_all" msgid="4780647751652596980">"이미지 및 동영상"</string>
+ <string name="show_all" msgid="6963292714584735149">"이미지 및 동영상"</string>
<string name="appwidget_title" msgid="6410561146863700411">"사진 갤러리"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"사진이 없습니다."</string>
<string name="crop_saved" msgid="1062612625032731770">"잘린 이미지가 다운로드에 저장되었습니다."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"그룹화 기준"</string>
<string name="settings" msgid="1534847740615665736">"설정"</string>
<string name="add_account" msgid="4271217504968243974">"계정 추가"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"카메라"</string>
+ <string name="folder_download" msgid="7186215137642323932">"다운로드"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"가져옴"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"스크린샷"</string>
+ <string name="help" msgid="7368960711153618354">"도움말"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 1a2b9fd..677cc5b 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Atnaujinti grojimą"</string>
<string name="loading" msgid="7038208555304563571">"Įkeliama…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Nepavyko įkelti"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Nepavyko įkelti vaizdo"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Nėra miniatiūros"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Pradėti iš naujo"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Apkarpyti"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"Gerai"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Jei norite pradėti, palieskite veidą."</string>
<string name="saving_image" msgid="7270334453636349407">"Išsaugomas paveikslėlis..."</string>
<string name="save_error" msgid="6857408774183654970">"Nepavyko išsaugoti apkarpyto vaizdo."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Nustatomas darbalaukio fonas…"</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Darbalaukio fonas"</string>
<string name="delete" msgid="2839695998251824487">"Ištrinti"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Ištrinti"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Atšaukti"</string>
<string name="share" msgid="3619042788254195341">"Bendrinti"</string>
<string name="select_all" msgid="3403283025220282175">"Pasirinkti viską"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Nepažymėta"</string>
<string name="no_location" msgid="4043624857489331676">"Nėra vietos"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Kai kurių vietų nepavyko nustatyti dėl tinklo problemų."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Atsiunčiant šio albumo nuotraukas įvyko klaida. Bandykite dar kartą vėliau."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Atsiunčiant albumų sąrašą įvyko klaida. Bandykite dar kartą vėliau."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Šio albumo nuotraukų atsisiųsti nepavyko. Bandykite dar kartą vėliau."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Albumų sąrašo atsisiųsti nepavyko. Bandykite dar kartą vėliau."</string>
<string name="show_images_only" msgid="7263218480867672653">"Tik vaizdai"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Tik vaizdo įrašai"</string>
- <string name="show_all" msgid="4780647751652596980">"Vaizdai ir vaizdo įrašai"</string>
+ <string name="show_all" msgid="6963292714584735149">"Vaizdai ir vaizdo įrašai"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Nuotraukų galerija"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Nėra nuotraukų."</string>
<string name="crop_saved" msgid="1062612625032731770">"Apkarpytas vaizdas išsaug. skilt. „Atsisiuntimai“."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Grupuoti pagal"</string>
<string name="settings" msgid="1534847740615665736">"Nustatymai"</string>
<string name="add_account" msgid="4271217504968243974">"Pridėti paskyrą"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Fotoaparatas"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Atsisiųsti"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importuota"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Ekrano kopija"</string>
+ <string name="help" msgid="7368960711153618354">"Pagalba"</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 2da2540..c66b0cc 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Atsākt atskaņošanu"</string>
<string name="loading" msgid="7038208555304563571">"Notiek ielāde…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Nevarēja ielādēt"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Nevarēja ielādēt attēlu."</string>
<string name="no_thumbnail" msgid="284723185546429750">"Nav sīktēla."</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Sākt vēlreiz"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Apgriezt"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"Labi"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Pieskarieties sejai, lai sāktu."</string>
<string name="saving_image" msgid="7270334453636349407">"Notiek attēla saglabāšana..."</string>
<string name="save_error" msgid="6857408774183654970">"Nevarēja saglabāt izgriezto attēlu."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Notiek fona tapetes iestatīšana..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Fona tapete"</string>
<string name="delete" msgid="2839695998251824487">"Dzēst"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Dzēst"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Atcelt"</string>
<string name="share" msgid="3619042788254195341">"Dalies"</string>
<string name="select_all" msgid="3403283025220282175">"Atlasīt visu"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Bez atzīmēm"</string>
<string name="no_location" msgid="4043624857489331676">"Nav vietas inform."</string>
<string name="no_connectivity" msgid="7164037617297293668">"Tīkla problēmu dēļ nevarēja noteikt dažas atrašanās vietas."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Neizdevās lejupielādēt šī albuma fotoattēlus. Lūdzu, vēlāk mēģiniet vēlreiz."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Neizdevās lejupielādēt albumu sarakstu. Lūdzu, vēlāk mēģiniet vēlreiz."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Nevarēja lejupielādēt šī albuma fotoattēlus. Vēlāk mēģiniet vēlreiz."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Nevarēja lejupielādēt albumu sarakstu. Vēlāk mēģiniet vēlreiz."</string>
<string name="show_images_only" msgid="7263218480867672653">"Tikai attēli"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Tikai videoklipi"</string>
- <string name="show_all" msgid="4780647751652596980">"Attēli un videoklipi"</string>
+ <string name="show_all" msgid="6963292714584735149">"Attēli un videoklipi"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotogalerija"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Nav fotoattēlu."</string>
<string name="crop_saved" msgid="1062612625032731770">"Izgrieztais attēls ir saglabāts mapē Lejupielādes."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Grupēt pēc:"</string>
<string name="settings" msgid="1534847740615665736">"Iestatījumi"</string>
<string name="add_account" msgid="4271217504968243974">"Konta pievienošana"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Lejupielādētie"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importētie"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Ekrānuzņēmums"</string>
+ <string name="help" msgid="7368960711153618354">"Palīdzība"</string>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index eead8b1..0a0c4dc 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Sambung semula proses main"</string>
<string name="loading" msgid="7038208555304563571">"Memuatkan..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Tidak dapat memuatkan"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Tidak dapat memuatkan imej"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Tiada lakaran kenit"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Mainkan semula dari mula"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Pangkas"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Sentuh muka untuk bermula."</string>
<string name="saving_image" msgid="7270334453636349407">"Menyimpan gambar..."</string>
<string name="save_error" msgid="6857408774183654970">"Tidak dapat menyimpan imej yang dipangkas."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Menetapkan kertas dinding…"</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Kertas dinding"</string>
<string name="delete" msgid="2839695998251824487">"Padam"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Padam"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Batal"</string>
<string name="share" msgid="3619042788254195341">"Kongsi"</string>
<string name="select_all" msgid="3403283025220282175">"Pilih semua"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Tidak ditanda namakan"</string>
<string name="no_location" msgid="4043624857489331676">"Tiada lokasi"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Beberapa lokasi tidak dapat dikenal pasti kerana masalah rangkaian."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Gagal untuk memuat turun foto dalam album ini. Sila cuba lagi nanti."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Gagal untuk memuat turun senarai album. Sila cuba lagi nanti."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Tidak dapat memuat turun foto dalam album ini. Cuba lagi nanti."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Tidak dapat memuat turun senarai album. Cuba lagi nanti."</string>
<string name="show_images_only" msgid="7263218480867672653">"Imej sahaja"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Video sahaja"</string>
- <string name="show_all" msgid="4780647751652596980">"Imej dan video"</string>
+ <string name="show_all" msgid="6963292714584735149">"Imej & video"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galeri Foto"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Tiada foto."</string>
<string name="crop_saved" msgid="1062612625032731770">"Imej yang dipangkas disimpan ke Muat Turun."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Kumpulkan mengikut"</string>
<string name="settings" msgid="1534847740615665736">"Tetapan"</string>
<string name="add_account" msgid="4271217504968243974">"Tambah akaun"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Muat Turun"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Diimport"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Tangkapan skrin"</string>
+ <string name="help" msgid="7368960711153618354">"Bantuan"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index d8a708a..20ec589 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Fortsett avspilling"</string>
<string name="loading" msgid="7038208555304563571">"Laster inn ..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Kunne ikke laste"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Kunne ikke laste inn bildet"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Ingen miniatyrbilder"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Begynn på nytt"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Beskjæring"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Berør et ansikt for å begynne."</string>
<string name="saving_image" msgid="7270334453636349407">"Lagrer bilde ..."</string>
<string name="save_error" msgid="6857408774183654970">"Kan ikke lagre det beskårede bildet."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Angir bakgrunn …"</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Bakgrunnsbilde"</string>
<string name="delete" msgid="2839695998251824487">"Slett"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Slett"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Avbryt"</string>
<string name="share" msgid="3619042788254195341">"Del"</string>
<string name="select_all" msgid="3403283025220282175">"Velg alle"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Uten etikett"</string>
<string name="no_location" msgid="4043624857489331676">"Ingen posisjon"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Noen steder kunne ikke identifiseres på grunn av nettverksproblemer."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Kunne ikke laste ned bildene i dette albumet. Prøv på nytt senere."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Kunne ikke laste ned listen over albumer. Prøv på nytt senere."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Kunne ikke laste ned bildene i dette albumet. Prøv på nytt senere."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Kunne ikke laste ned albumlisten. Prøv på nytt senere."</string>
<string name="show_images_only" msgid="7263218480867672653">"Kun bilder"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Kun videoer"</string>
- <string name="show_all" msgid="4780647751652596980">"Bilder og videoer"</string>
+ <string name="show_all" msgid="6963292714584735149">"Bilder og videoer"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotogalleri"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Ingen bilder."</string>
<string name="crop_saved" msgid="1062612625032731770">"Beskåret bilde lagret i Nedlastinger."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Gruppér etter"</string>
<string name="settings" msgid="1534847740615665736">"Innstillinger"</string>
<string name="add_account" msgid="4271217504968243974">"Legg til konto"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Nedlasting"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importert"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Skjermdump"</string>
+ <string name="help" msgid="7368960711153618354">"Brukerstøtte"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 9e43d85..ec93df3 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Afspelen hervatten"</string>
<string name="loading" msgid="7038208555304563571">"Laden..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Kan niet laden"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Kan de afbeelding niet laden"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Geen miniatuur"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Opnieuw starten"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Bijsnijden"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Raak een gezicht aan om te beginnen."</string>
<string name="saving_image" msgid="7270334453636349407">"Foto opslaan..."</string>
<string name="save_error" msgid="6857408774183654970">"Kan bijgesneden afbeelding niet opslaan."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Achtergrond instellen..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Achtergrond"</string>
<string name="delete" msgid="2839695998251824487">"Verwijderen"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Verwijderen"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Annuleren"</string>
<string name="share" msgid="3619042788254195341">"Delen"</string>
<string name="select_all" msgid="3403283025220282175">"Alles selecteren"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Geen tags"</string>
<string name="no_location" msgid="4043624857489331676">"Geen locatie"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Sommige locaties kunnen wegens problemen met het netwerk niet worden geïdentificeerd."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Het downloaden van de foto\'s in dit album is mislukt. Probeer het later opnieuw."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Het downloaden van de lijst met albums is mislukt. Probeer het later opnieuw."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Kan de foto\'s in dit album niet downloaden. Probeer het later opnieuw."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Kan de lijst met albums niet downloaden. Probeer het later opnieuw."</string>
<string name="show_images_only" msgid="7263218480867672653">"Alleen afbeeldingen"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Alleen video\'s"</string>
- <string name="show_all" msgid="4780647751652596980">"Afbeeldingen en video\'s"</string>
+ <string name="show_all" msgid="6963292714584735149">"Afbeeldingen en video\'s"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotogalerij"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Geen foto\'s."</string>
<string name="crop_saved" msgid="1062612625032731770">"Bijgesneden afbeelding opgeslagen in Downloads."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Groeperen op"</string>
<string name="settings" msgid="1534847740615665736">"Instellingen"</string>
<string name="add_account" msgid="4271217504968243974">"Account toevoegen"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Camera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Downloaden"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Geïmporteerd"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Schermafbeelding"</string>
+ <string name="help" msgid="7368960711153618354">"Help"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 82f4310..ead7859 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Wznów odtwarzanie"</string>
<string name="loading" msgid="7038208555304563571">"Wczytywanie…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Nie można wczytać"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Nie można wczytać zdjęcia"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Brak miniatury"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Rozpocznij"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Przytnij"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Dotknij twarzy, aby rozpocząć."</string>
<string name="saving_image" msgid="7270334453636349407">"Zapisywanie zdjęcia…"</string>
<string name="save_error" msgid="6857408774183654970">"Nie można zapisać przyciętego obrazu."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Ustawianie tapety…"</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Tapeta"</string>
<string name="delete" msgid="2839695998251824487">"Usuń"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Usuń"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Anuluj"</string>
<string name="share" msgid="3619042788254195341">"Udostępnij"</string>
<string name="select_all" msgid="3403283025220282175">"Zaznacz wszystkie"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Nieoznaczone tagami"</string>
<string name="no_location" msgid="4043624857489331676">"Brak lokalizacji"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Niektórych lokalizacji nie można zidentyfikować z powodu problemów z siecią."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Nie udało się pobrać zdjęć z tego albumu. Spróbuj ponownie później."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Nie udało się pobrać listy albumów. Spróbuj ponownie później."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Nie można pobrać zdjęć z tego albumu. Spróbuj ponownie później."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Nie można pobrać listy albumów. Spróbuj ponownie później."</string>
<string name="show_images_only" msgid="7263218480867672653">"Tylko obrazy"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Tylko filmy"</string>
- <string name="show_all" msgid="4780647751652596980">"Obrazy i filmy"</string>
+ <string name="show_all" msgid="6963292714584735149">"Obrazy i filmy"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galeria zdjęć"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Brak zdjęć"</string>
<string name="crop_saved" msgid="1062612625032731770">"Przycięty obraz zapisano w Pobranych plikach."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Grupuj według"</string>
<string name="settings" msgid="1534847740615665736">"Ustawienia"</string>
<string name="add_account" msgid="4271217504968243974">"Dodaj konto"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Aparat"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Pobrane"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Zaimportowane"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Zrzuty ekranu"</string>
+ <string name="help" msgid="7368960711153618354">"Pomoc"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index d0b54fe..d96049b 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Retomar a reprodução"</string>
<string name="loading" msgid="7038208555304563571">"A carregar..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Não foi possível carregar"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Não foi possível carregar a imagem"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Sem miniatura"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Recomeçar"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Recortar"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Toque num rosto para começar."</string>
<string name="saving_image" msgid="7270334453636349407">"A guardar imagem..."</string>
<string name="save_error" msgid="6857408774183654970">"Impossível guardar imagem recortada."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"A definir imagem de fundo…"</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Imagem de fundo"</string>
<string name="delete" msgid="2839695998251824487">"Eliminar"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Eliminar"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Cancelar"</string>
<string name="share" msgid="3619042788254195341">"Partilhar"</string>
<string name="select_all" msgid="3403283025220282175">"Selecionar tudo"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Sem etiqueta"</string>
<string name="no_location" msgid="4043624857489331676">"Sem localização"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Não foi possível identificar algumas localizações devido a problemas de rede."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Falha ao transferir as fotografias deste álbum. Tente novamente mais tarde."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Falha ao transferir a lista de álbuns. Tente novamente mais tarde."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Não foi possível transferir as fotografias deste álbum. Tente novamente mais tarde."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Não foi possível transferir a lista de álbuns. Tente novamente mais tarde."</string>
<string name="show_images_only" msgid="7263218480867672653">"Apenas imagens"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Apenas vídeos"</string>
- <string name="show_all" msgid="4780647751652596980">"Imagens e vídeos"</string>
+ <string name="show_all" msgid="6963292714584735149">"Imagens e vídeos"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galeria de fotografias"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Não existem fotografias."</string>
<string name="crop_saved" msgid="1062612625032731770">"Imagem recortada guardada em Transferências."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Agrupar por"</string>
<string name="settings" msgid="1534847740615665736">"Definições"</string>
<string name="add_account" msgid="4271217504968243974">"Adicionar conta"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Câmara"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Transferidas"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importada"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Captura de ecrã"</string>
+ <string name="help" msgid="7368960711153618354">"Ajuda"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index a5fce6c..9b516df 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Retomar a reprodução"</string>
<string name="loading" msgid="7038208555304563571">"Carregando..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Não foi possível carregar"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Não foi possível carregar a imagem"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Sem miniatura"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Reiniciar"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Cortar"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Toque em um rosto para começar."</string>
<string name="saving_image" msgid="7270334453636349407">"Salvando imagem…"</string>
<string name="save_error" msgid="6857408774183654970">"Não é possível salvar a imagem cortada."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Definindo plano de fundo..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Plano de fundo"</string>
<string name="delete" msgid="2839695998251824487">"Excluir"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Excluir"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Cancelar"</string>
<string name="share" msgid="3619042788254195341">"Compartilhar"</string>
<string name="select_all" msgid="3403283025220282175">"Selecionar tudo"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Sem tags"</string>
<string name="no_location" msgid="4043624857489331676">"Nenhum local"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Alguns locais não foram identificados por problemas na rede."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Falha ao fazer download das fotos neste álbum. Tente novamente mais tarde."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Falha ao fazer download da lista de álbuns. Tente novamente mais tarde."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Não foi possível fazer download das fotos neste álbum. Tente novamente mais tarde."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Não foi possível fazer download da lista de álbuns. Tente novamente mais tarde."</string>
<string name="show_images_only" msgid="7263218480867672653">"Apenas imagens"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Somente vídeos"</string>
- <string name="show_all" msgid="4780647751652596980">"Imagens e vídeos"</string>
+ <string name="show_all" msgid="6963292714584735149">"Imagens e vídeos"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galeria de fotos"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Nenhuma foto."</string>
<string name="crop_saved" msgid="1062612625032731770">"A imagem cortada foi salva em Downloads."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Agrupar por"</string>
<string name="settings" msgid="1534847740615665736">"Configurações"</string>
<string name="add_account" msgid="4271217504968243974">"Adicionar conta"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Câmera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Downloads"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importado"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Capturas de tela"</string>
+ <string name="help" msgid="7368960711153618354">"Ajuda"</string>
</resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
index cbc2659..6cfa1d0 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -20,7 +20,8 @@
<string name="gadget_title" msgid="259405922673466798">"Rom da maletgs"</string>
<string name="details_ms" msgid="940634969189855292">"%1$02d:%2$02d"</string>
<string name="details_hms" msgid="3215779248094151255">"%1$d:%2$02d:%3$02d"</string>
- <!-- outdated translation 3697303290960009886 --> <string name="movie_view_label" msgid="3526526872644898229">"Films"</string>
+ <!-- no translation found for movie_view_label (3526526872644898229) -->
+ <skip />
<string name="loading_video" msgid="4013492720121891585">"Chargiar il video…"</string>
<!-- no translation found for loading_image (1200894415793838191) -->
<skip />
@@ -33,11 +34,15 @@
<skip />
<!-- no translation found for fail_to_load (8394392853646664505) -->
<skip />
+ <!-- no translation found for fail_to_load_image (6155388718549782425) -->
+ <skip />
<!-- no translation found for no_thumbnail (284723185546429750) -->
<skip />
<string name="resume_playing_restart" msgid="5471008499835769292">"Cumenzar"</string>
- <!-- outdated translation 8140440041190264400 --> <string name="crop_save_text" msgid="4972430481677741184">"Memorisar"</string>
- <!-- outdated translation 3127018992717032779 --> <string name="multiface_crop_help" msgid="2554690102655855657">"Smatgai sin ina fatscha per cumenzar."</string>
+ <!-- no translation found for crop_save_text (152200178986698300) -->
+ <skip />
+ <!-- no translation found for multiface_crop_help (2554690102655855657) -->
+ <skip />
<string name="saving_image" msgid="7270334453636349407">"Memorisar il maletg..."</string>
<!-- no translation found for save_error (6857408774183654970) -->
<skip />
@@ -53,14 +58,20 @@
<!-- no translation found for select_group (6744208543323307114) -->
<skip />
<string name="set_image" msgid="2331476809308010401">"Definir il maletg sco"</string>
- <!-- outdated translation 9222901738515471972 --> <string name="wallpaper" msgid="140165383777262070">"La culissa vegn configurada. Spetgai per plaschair..."</string>
+ <!-- no translation found for wallpaper (140165383777262070) -->
+ <skip />
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Culissa"</string>
<string name="delete" msgid="2839695998251824487">"Stizzar"</string>
- <!-- outdated translation 5731757674837098707 --> <string name="confirm_delete" msgid="7358770022173660511">"Confermar il stizzar"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Interrumper"</string>
<string name="share" msgid="3619042788254195341">"Cundivider"</string>
- <!-- outdated translation 8623593677101437957 --> <string name="select_all" msgid="3403283025220282175">"Selecziunar tut"</string>
- <!-- outdated translation 7397531298370285581 --> <string name="deselect_all" msgid="5758897506061723684">"Deselecziunar tut"</string>
+ <!-- no translation found for select_all (3403283025220282175) -->
+ <skip />
+ <!-- no translation found for deselect_all (5758897506061723684) -->
+ <skip />
<string name="slideshow" msgid="4355906903247112975">"Preschentaziun da dia"</string>
<string name="details" msgid="8415120088556445230">"Detagls"</string>
<!-- no translation found for details_title (2611396603977441273) -->
@@ -94,7 +105,8 @@
<!-- no translation found for crop_action (3427470284074377001) -->
<skip />
<string name="set_as" msgid="3636764710790507868">"Definir sco"</string>
- <!-- outdated translation 7917736494827857757 --> <string name="video_err" msgid="7003051631792271009">"Impussibel da reproducir il video"</string>
+ <!-- no translation found for video_err (7003051631792271009) -->
+ <skip />
<!-- no translation found for group_by_location (316641628989023253) -->
<skip />
<!-- no translation found for group_by_time (9046168567717963573) -->
@@ -113,15 +125,15 @@
<skip />
<!-- no translation found for no_connectivity (7164037617297293668) -->
<skip />
- <!-- no translation found for sync_album_error (2218733298953719785) -->
+ <!-- no translation found for sync_album_error (1020688062900977530) -->
<skip />
- <!-- no translation found for sync_album_set_error (9016732535181154028) -->
+ <!-- no translation found for sync_album_set_error (3250258387046904444) -->
<skip />
<!-- no translation found for show_images_only (7263218480867672653) -->
<skip />
<!-- no translation found for show_videos_only (3850394623678871697) -->
<skip />
- <!-- no translation found for show_all (4780647751652596980) -->
+ <!-- no translation found for show_all (6963292714584735149) -->
<skip />
<!-- no translation found for appwidget_title (6410561146863700411) -->
<skip />
@@ -248,4 +260,14 @@
<string name="settings" msgid="1534847740615665736">"Parameters"</string>
<!-- no translation found for add_account (4271217504968243974) -->
<skip />
+ <!-- no translation found for folder_camera (4714658994809533480) -->
+ <skip />
+ <!-- no translation found for folder_download (7186215137642323932) -->
+ <skip />
+ <!-- no translation found for folder_imported (2773581395524747099) -->
+ <skip />
+ <!-- no translation found for folder_screenshot (7200396565864213450) -->
+ <skip />
+ <!-- no translation found for help (7368960711153618354) -->
+ <skip />
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 3caf4bc..0b531fb 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Reluaţi redarea"</string>
<string name="loading" msgid="7038208555304563571">"Se încarcă..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Nu s-au putut încărca"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Nu s-a putut încărca imaginea"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Nu există o miniatură"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Începeţi din nou"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Decupaţi"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Atingeţi o faţă pentru a începe."</string>
<string name="saving_image" msgid="7270334453636349407">"Se salvează fotografia..."</string>
<string name="save_error" msgid="6857408774183654970">"Imaginea decupată nu s-a putut salva."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Se setează imaginea de fundal..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Imagine de fundal"</string>
<string name="delete" msgid="2839695998251824487">"Ştergeţi"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Ştergeţi"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Anulaţi"</string>
<string name="share" msgid="3619042788254195341">"Distribuiţi"</string>
<string name="select_all" msgid="3403283025220282175">"Selectaţi-le pe toate"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Neetichetate"</string>
<string name="no_location" msgid="4043624857489331676">"Fără locaţie"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Unele locaţii nu au putut fi identificate din cauza unor probleme de reţea."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Descărcarea fotografiilor din acest album a eşuat. Încercaţi din nou mai târziu."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Descărcarea listei de albume a eşuat. Încercaţi din nou mai târziu."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Descărcarea fotografiilor din acest album a eşuat. Încercaţi din nou mai târziu."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Descărcarea listei de albume a eşuat. Încercaţi din nou mai târziu."</string>
<string name="show_images_only" msgid="7263218480867672653">"Numai imagini"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Numai videoclipuri"</string>
- <string name="show_all" msgid="4780647751652596980">"Imagini şi videoclipuri"</string>
+ <string name="show_all" msgid="6963292714584735149">"Imagini şi videoclipuri"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Galerie foto"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Nicio fotografie."</string>
<string name="crop_saved" msgid="1062612625032731770">"Imaginea decupată a fost salvată la Descărcări."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Grupaţi după"</string>
<string name="settings" msgid="1534847740615665736">"Setări"</string>
<string name="add_account" msgid="4271217504968243974">"Adăugaţi un cont"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Cameră foto"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Descărcate"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importate"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Captură de ecran"</string>
+ <string name="help" msgid="7368960711153618354">"Ajutor"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 1212a91..1d62e92 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Продолжить воспроизведение"</string>
<string name="loading" msgid="7038208555304563571">"Идет загрузка…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Не загружено"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Не удалось загрузить изображение"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Нет уменьшенного изображения"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Начать с начала"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Кадрировать"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"ОК"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Нажмите на лицо, чтобы начать."</string>
<string name="saving_image" msgid="7270334453636349407">"Сохранение картинки..."</string>
<string name="save_error" msgid="6857408774183654970">"Изображение не сохранено."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Установка обоев..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Обои"</string>
<string name="delete" msgid="2839695998251824487">"Удалить"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Удалить"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Отмена"</string>
<string name="share" msgid="3619042788254195341">"Отправить"</string>
<string name="select_all" msgid="3403283025220282175">"Выбрать все"</string>
@@ -54,21 +58,21 @@
<string name="details" msgid="8415120088556445230">"Сведения"</string>
<string name="details_title" msgid="2611396603977441273">"Элементов %1$d из %2$d:"</string>
<string name="close" msgid="5585646033158453043">"Закрыть"</string>
- <string name="switch_to_camera" msgid="7280111806675169992">"Переключиться на камеру"</string>
+ <string name="switch_to_camera" msgid="7280111806675169992">"Режим \"Фото\""</string>
<plurals name="number_of_items_selected">
- <item quantity="zero" msgid="2142579311530586258">"Выбрано: %1$d"</item>
- <item quantity="one" msgid="2478365152745637768">"Выбрано: %1$d"</item>
- <item quantity="other" msgid="754722656147810487">"Выбрано: %1$d"</item>
+ <item quantity="zero" msgid="2142579311530586258">"%1$d"</item>
+ <item quantity="one" msgid="2478365152745637768">"%1$d"</item>
+ <item quantity="other" msgid="754722656147810487">"%1$d"</item>
</plurals>
<plurals name="number_of_albums_selected">
- <item quantity="zero" msgid="749292746814788132">"Выбрано: %1$d"</item>
- <item quantity="one" msgid="6184377003099987825">"Выбрано: %1$d"</item>
- <item quantity="other" msgid="53105607141906130">"Выбрано: %1$d"</item>
+ <item quantity="zero" msgid="749292746814788132">"%1$d"</item>
+ <item quantity="one" msgid="6184377003099987825">"%1$d"</item>
+ <item quantity="other" msgid="53105607141906130">"%1$d"</item>
</plurals>
<plurals name="number_of_groups_selected">
- <item quantity="zero" msgid="3466388370310869238">"Выбрано: %1$d"</item>
- <item quantity="one" msgid="5030162638216034260">"Выбрано: %1$d"</item>
- <item quantity="other" msgid="3512041363942842738">"Выбрано: %1$d"</item>
+ <item quantity="zero" msgid="3466388370310869238">"%1$d"</item>
+ <item quantity="one" msgid="5030162638216034260">"%1$d"</item>
+ <item quantity="other" msgid="3512041363942842738">"%1$d"</item>
</plurals>
<string name="show_on_map" msgid="6157544221201750980">"Показать на карте"</string>
<string name="rotate_left" msgid="5888273317282539839">"Повернуть влево"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Без тегов"</string>
<string name="no_location" msgid="4043624857489331676">"Место не указано"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Не удалось найти все места из-за проблем с подключением."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Не удалось загрузить фотографии. Повторите попытку позже."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Не удалось загрузить альбомы. Повторите попытку позже."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Не удалось загрузить фото. Повторите попытку позже."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Не удалось загрузить список альбомов. Повторите попытку позже."</string>
<string name="show_images_only" msgid="7263218480867672653">"Только изображения"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Только видео"</string>
- <string name="show_all" msgid="4780647751652596980">"Изображения и видео"</string>
+ <string name="show_all" msgid="6963292714584735149">"Изображения и видео"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Фотогалерея"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Нет фотографий"</string>
<string name="crop_saved" msgid="1062612625032731770">"Изображение сохранено в папке \"Загрузки\"."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Сгруппировать"</string>
<string name="settings" msgid="1534847740615665736">"Настройки"</string>
<string name="add_account" msgid="4271217504968243974">"Добавить аккаунт"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Камера"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Загруженные"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Импортированные"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Скриншоты"</string>
+ <string name="help" msgid="7368960711153618354">"Справка"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index c25ea40..b107b91 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Obnoviť prehrávanie"</string>
<string name="loading" msgid="7038208555304563571">"Prebieha načítavanie…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Nepodarilo sa načítať"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Obrázok sa nepodarilo načítať"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Žiadne miniatúry"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Začať odznova"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Orezať"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Začnite dotknutím sa tváre."</string>
<string name="saving_image" msgid="7270334453636349407">"Prebieha ukladanie fotografie..."</string>
<string name="save_error" msgid="6857408774183654970">"Orezaný obrázok sa nepodarilo uložiť."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Prebieha nastavenie tapety..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Tapeta"</string>
<string name="delete" msgid="2839695998251824487">"Odstrániť"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Odstrániť"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Zrušiť"</string>
<string name="share" msgid="3619042788254195341">"Zdieľať"</string>
<string name="select_all" msgid="3403283025220282175">"Vybrať všetko"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Neoznačené"</string>
<string name="no_location" msgid="4043624857489331676">"Bez údajov o polohe"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Niektoré umiestnenia nebolo možné identifikovať kvôli problémom so sieťou."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Fotografie z tohto albumu sa nepodarilo prevziať. Skúste to znova neskôr."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Zoznam albumov sa nepodarilo prevziať. Skúste to znova neskôr."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Fotografie z tohto albumu sa nepodarilo prevziať. Skúste to znova neskôr."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Zoznam albumov sa nepodarilo prevziať. Skúste to znova neskôr."</string>
<string name="show_images_only" msgid="7263218480867672653">"Iba obrázky"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Iba videá"</string>
- <string name="show_all" msgid="4780647751652596980">"Obrázky a videá"</string>
+ <string name="show_all" msgid="6963292714584735149">"Obrázky a videá"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotogaléria"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Žiadne fotografie."</string>
<string name="crop_saved" msgid="1062612625032731770">"Orez. obrázok bol uložený do Prevzatých súborov."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Zoskupiť podľa"</string>
<string name="settings" msgid="1534847740615665736">"Nastavenia"</string>
<string name="add_account" msgid="4271217504968243974">"Pridať účet"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Fotoaparát"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Preberanie"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importované"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Snímka obrazovky"</string>
+ <string name="help" msgid="7368960711153618354">"Pomocník"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 39c7266..40c8787 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Nadaljuj predvajanje"</string>
<string name="loading" msgid="7038208555304563571">"Prenos …"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Ni bilo mogoče naložiti"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Slike ni bilo mogoče naložiti"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Ni sličice"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Začni znova"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Obreži"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"V redu"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Dotaknite se obraza, če želite začeti."</string>
<string name="saving_image" msgid="7270334453636349407">"Shranjevanje slike ..."</string>
<string name="save_error" msgid="6857408774183654970">"Obrezane slike ni bilo mogoče shraniti."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Nastavljanje slike za ozadje ..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Slika za ozadje"</string>
<string name="delete" msgid="2839695998251824487">"Izbriši"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Izbriši"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Prekliči"</string>
<string name="share" msgid="3619042788254195341">"Skupna raba"</string>
<string name="select_all" msgid="3403283025220282175">"Izberi vse"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Neoznačeno"</string>
<string name="no_location" msgid="4043624857489331676">"Ni lokacije"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Nekaterih lokacij ni bilo mogoče določiti zaradi težav v omrežju."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Napaka pri prenosu slik v tem albumu. Poskusite pozneje."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Napaka pri prenosu seznama albumov. Poskusite pozneje."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Slik v tem albumu ni mogoče prenesti. Poskusite znova pozneje."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Seznama albumov ni mogoče prenesti. Poskusite znova pozneje."</string>
<string name="show_images_only" msgid="7263218480867672653">"Samo slike"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Samo videoposnetki"</string>
- <string name="show_all" msgid="4780647751652596980">"Slike in videoposnetki"</string>
+ <string name="show_all" msgid="6963292714584735149">"Slike in videoposnetki"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotogalerija"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Ni fotografij."</string>
<string name="crop_saved" msgid="1062612625032731770">"Obrezana slika je shranjena v prenose."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Razvrsti po"</string>
<string name="settings" msgid="1534847740615665736">"Nastavitve"</string>
<string name="add_account" msgid="4271217504968243974">"Dodaj račun"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Fotoaparat"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Prenosi"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Uvoženo"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Posnetek zaslona"</string>
+ <string name="help" msgid="7368960711153618354">"Pomoč"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 341096a..c200f2c 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Настави репродукцију"</string>
<string name="loading" msgid="7038208555304563571">"Учитавање…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Учитавање није могуће"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Није могуће учитати слику"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Нема сличице"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Започни поново"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Опсеци"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"Потврди"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Додирните неко лице за почетак."</string>
<string name="saving_image" msgid="7270334453636349407">"Чување слике…"</string>
<string name="save_error" msgid="6857408774183654970">"Није могуће сачувати опсечену слику."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Подешавање позадине..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Позадина"</string>
<string name="delete" msgid="2839695998251824487">"Избриши"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Избриши"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Откажи"</string>
<string name="share" msgid="3619042788254195341">"Дели"</string>
<string name="select_all" msgid="3403283025220282175">"Изабери све"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Није означено"</string>
<string name="no_location" msgid="4043624857489331676">"Без локације"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Није могуће идентификовати неке локације због проблема са мрежом."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Преузимање фотографија из овог албума није успело. Покушајте поново касније."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Преузимање листе албума није успело. Покушајте поново касније."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Није могуће преузети фотографије у овом албуму. Покушајте поново касније."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Није могуће преузети листу албума. Покушајте поново касније."</string>
<string name="show_images_only" msgid="7263218480867672653">"Само слике"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Само видео снимци"</string>
- <string name="show_all" msgid="4780647751652596980">"Слике и видео снимци"</string>
+ <string name="show_all" msgid="6963292714584735149">"Слике и видео снимци"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Фото-галерија"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Нема фотографија."</string>
<string name="crop_saved" msgid="1062612625032731770">"Опсечена слика је сачувана у Преузимањима."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Групиши према"</string>
<string name="settings" msgid="1534847740615665736">"Подешавања"</string>
<string name="add_account" msgid="4271217504968243974">"Додавање налога"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Камера"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Преузето"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Увезено"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Снимак екрана"</string>
+ <string name="help" msgid="7368960711153618354">"Помоћ"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 59f8a8f..f61cbaa 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Fortsätt spela upp"</string>
<string name="loading" msgid="7038208555304563571">"Läser in..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Kunde inte läsas in"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Det gick inte att läsa in bilden"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Ingen miniatyrbild"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Börja om"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Beskär"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Tryck på ett ansikte när du vill börja."</string>
<string name="saving_image" msgid="7270334453636349407">"Sparar bild…"</string>
<string name="save_error" msgid="6857408774183654970">"Det gick inte att spara den beskurna bilden."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Bakgrund ställs in ..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Bakgrund"</string>
<string name="delete" msgid="2839695998251824487">"Ta bort"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Ta bort"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Avbryt"</string>
<string name="share" msgid="3619042788254195341">"Dela"</string>
<string name="select_all" msgid="3403283025220282175">"Markera alla"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Saknar etikett"</string>
<string name="no_location" msgid="4043624857489331676">"Ingen plats"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Det gick inte att identifiera vissa platser på grund av nätverksproblem."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Det gick inte att hämta fotona i albumet. Försök igen senare."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Det gick inte att hämta albumlistan. Försök igen senare."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Det gick inte att hämta bilderna i albumet. Försök igen senare."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Det gick inte att hämta albumlistan. Försök igen senare."</string>
<string name="show_images_only" msgid="7263218480867672653">"Endast bilder"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Endast video"</string>
- <string name="show_all" msgid="4780647751652596980">"Bilder och videor"</string>
+ <string name="show_all" msgid="6963292714584735149">"Bilder och videor"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotogalleri"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Inga foton."</string>
<string name="crop_saved" msgid="1062612625032731770">"Den beskurna bilden sparades i Hämtningar."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Ordna efter"</string>
<string name="settings" msgid="1534847740615665736">"Inställningar"</string>
<string name="add_account" msgid="4271217504968243974">"Lägg till konto"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Hämtat"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Importerat"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Skärmbild"</string>
+ <string name="help" msgid="7368960711153618354">"Hjälp"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 193e48d..634eb25 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Endelea kucheza"</string>
<string name="loading" msgid="7038208555304563571">"Inapakia…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Imeshindwa kupakia."</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Haikuweza kupakia picha"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Hakuna kijipicha"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Anza tena"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Pogoa"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"Sawa"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Gusa sura ili kuanza."</string>
<string name="saving_image" msgid="7270334453636349407">"Inahifadhi picha…"</string>
<string name="save_error" msgid="6857408774183654970">"Haiwezi kuhifadhi picha iliyopogolewa."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Inaweka karatasi ya ukuta..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Mandhari"</string>
<string name="delete" msgid="2839695998251824487">"Futa"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Futa"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Ghairi"</string>
<string name="share" msgid="3619042788254195341">"Shiriki"</string>
<string name="select_all" msgid="3403283025220282175">"Chagua zote"</string>
@@ -78,10 +82,10 @@
<string name="rotate_right" msgid="6776325835923384839">"Zungusha kulia"</string>
<string name="no_such_item" msgid="5315144556325243400">"Hakuweza kupata kipengee."</string>
<string name="edit" msgid="1502273844748580847">"Hariri"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Hakuna programu inayopatikana ili kukamilisha tendo hilo."</string>
+ <string name="activity_not_found" msgid="5619154886080878023">"Hakuna prog inayopatikana ili kukamilisha tendo hilo."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Maombi ya kuakibisha michakato"</string>
<string name="caching_label" msgid="4521059045896269095">"Inaakibisha..."</string>
- <string name="crop_action" msgid="3427470284074377001">"Puna"</string>
+ <string name="crop_action" msgid="3427470284074377001">"Punguza"</string>
<string name="set_as" msgid="3636764710790507868">"Weka kama"</string>
<string name="video_err" msgid="7003051631792271009">"Video haiwezi kuchezwa."</string>
<string name="group_by_location" msgid="316641628989023253">"Kwa mahali"</string>
@@ -93,11 +97,11 @@
<string name="untagged" msgid="7281481064509590402">"Ondoa lebo"</string>
<string name="no_location" msgid="4043624857489331676">"Hakuna Mahali"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Baadhi ya maeneo haikuweza kutambuliwa kutokana na matatizo ya mtandao."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Imeshindwa kupakua picha katika albamu hii. Tafadhali Jaribu tena baadaye."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Imeshindwa kupakua orodha ya albamu. Tafadhali Jaribu tena baadaye."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Haikuwezi kupakua picha zilizo kwenye albamu hii. Jaribu tena baadaye."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Haikuweza kupakua orodha ya albamu. Jaribu tena baadaye."</string>
<string name="show_images_only" msgid="7263218480867672653">"Picha tu"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Video tu"</string>
- <string name="show_all" msgid="4780647751652596980">"Picha na video"</string>
+ <string name="show_all" msgid="6963292714584735149">"Picha & video"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Matunzio ya picha"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Hakuna picha."</string>
<string name="crop_saved" msgid="1062612625032731770">"Picha iliyopunguzwa imehifadhiwa kwa Vipakuzi."</string>
@@ -165,4 +169,9 @@
<string name="group_by" msgid="4308299657902209357">"Panga kwa kikundi na"</string>
<string name="settings" msgid="1534847740615665736">"Mipangilio"</string>
<string name="add_account" msgid="4271217504968243974">"Ongeza akaunti"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Pakua"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Zilizoletwa"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Picha kiwamba"</string>
+ <string name="help" msgid="7368960711153618354">"Msaada"</string>
</resources>
diff --git a/res/values-sw320dp/photoeditor_dimens.xml b/res/values-sw320dp/photoeditor_dimens.xml
index ff76e25..a7d6a24 100755
--- a/res/values-sw320dp/photoeditor_dimens.xml
+++ b/res/values-sw320dp/photoeditor_dimens.xml
@@ -27,9 +27,11 @@
<dimen name="action_bar_icon_padding_right">5dp</dimen>
<dimen name="action_button_padding_horizontal">13dp</dimen>
<dimen name="effects_menu_container_width">320dp</dimen>
- <dimen name="effect_tool_panel_padding">10dp</dimen>
- <dimen name="seekbar_width">290dp</dimen>
- <dimen name="seekbar_height">27dp</dimen>
- <dimen name="seekbar_margin_bottom">3dp</dimen>
+ <dimen name="effect_tool_panel_padding_top">3dp</dimen>
+ <dimen name="effect_tool_panel_padding_bottom">8dp</dimen>
+ <dimen name="seekbar_width">350dp</dimen>
+ <dimen name="seekbar_height">43dp</dimen>
+ <dimen name="seekbar_padding_horizontal">30dp</dimen>
+ <dimen name="seekbar_padding_vertical">8dp</dimen>
<dimen name="crop_indicator_size">35dp</dimen>
</resources>
diff --git a/res/values-sw600dp/photoeditor_dimens.xml b/res/values-sw600dp/photoeditor_dimens.xml
index 29d7f53..b861150 100755
--- a/res/values-sw600dp/photoeditor_dimens.xml
+++ b/res/values-sw600dp/photoeditor_dimens.xml
@@ -29,9 +29,11 @@
<dimen name="action_button_padding_vertical">8dp</dimen>
<dimen name="action_button_padding_horizontal">22dp</dimen>
<dimen name="effects_menu_container_width">400dp</dimen>
- <dimen name="effect_tool_panel_padding">13dp</dimen>
- <dimen name="seekbar_width">560dp</dimen>
- <dimen name="seekbar_height">35dp</dimen>
- <dimen name="seekbar_margin_bottom">4dp</dimen>
+ <dimen name="effect_tool_panel_padding_top">4dp</dimen>
+ <dimen name="effect_tool_panel_padding_bottom">11dp</dimen>
+ <dimen name="seekbar_width">640dp</dimen>
+ <dimen name="seekbar_height">57dp</dimen>
+ <dimen name="seekbar_padding_horizontal">40dp</dimen>
+ <dimen name="seekbar_padding_vertical">11dp</dimen>
<dimen name="crop_indicator_size">43dp</dimen>
</resources>
diff --git a/res/values-sw800dp/photoeditor_dimens.xml b/res/values-sw800dp/photoeditor_dimens.xml
index e869acf..804a8ca 100755
--- a/res/values-sw800dp/photoeditor_dimens.xml
+++ b/res/values-sw800dp/photoeditor_dimens.xml
@@ -29,9 +29,11 @@
<dimen name="action_button_padding_vertical">8dp</dimen>
<dimen name="action_button_padding_horizontal">28dp</dimen>
<dimen name="effects_menu_container_width">400dp</dimen>
- <dimen name="effect_tool_panel_padding">15dp</dimen>
- <dimen name="seekbar_width">560dp</dimen>
- <dimen name="seekbar_height">35dp</dimen>
- <dimen name="seekbar_margin_bottom">5dp</dimen>
+ <dimen name="effect_tool_panel_padding_top">5dp</dimen>
+ <dimen name="effect_tool_panel_padding_bottom">13dp</dimen>
+ <dimen name="seekbar_width">640dp</dimen>
+ <dimen name="seekbar_height">61dp</dimen>
+ <dimen name="seekbar_padding_horizontal">40dp</dimen>
+ <dimen name="seekbar_padding_vertical">13dp</dimen>
<dimen name="crop_indicator_size">48dp</dimen>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 8ee83a4..53024ea 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"เล่นต่อ"</string>
<string name="loading" msgid="7038208555304563571">"กำลังโหลด…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"ไม่สามารถโหลด"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"ไม่สามารถโหลดภาพ"</string>
<string name="no_thumbnail" msgid="284723185546429750">"ไม่มีภาพขนาดย่อ"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"เริ่มต้นใหม่"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"ครอบตัด"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"ตกลง"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"แตะใบหน้าเพื่อเริ่มต้น"</string>
<string name="saving_image" msgid="7270334453636349407">"กำลังบันทึกภาพ..."</string>
<string name="save_error" msgid="6857408774183654970">"ไม่สามารถบันทึกภาพที่ตัดได้"</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"กำลังตั้งค่าวอลเปเปอร์..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"วอลเปเปอร์"</string>
<string name="delete" msgid="2839695998251824487">"ลบ"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"ลบ"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"ยกเลิก"</string>
<string name="share" msgid="3619042788254195341">"แบ่งปัน"</string>
<string name="select_all" msgid="3403283025220282175">"เลือกทั้งหมด"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"ยกเลิกการติดแท็ก"</string>
<string name="no_location" msgid="4043624857489331676">"ไม่มีสถานที่"</string>
<string name="no_connectivity" msgid="7164037617297293668">"ไม่สามารถระบุบางตำแหน่งได้เนื่องจากปัญหาเกี่ยวกับเครือข่าย"</string>
- <string name="sync_album_error" msgid="2218733298953719785">"ไม่สามารถดาวน์โหลดรูปภาพในอัลบั้มนี้ โปรดลองอีกครั้งในภายหลัง"</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"ไม่สามารถดาวน์โหลดรายชื่ออัลบั้ม โปรดลองอีกครั้งในภายหลัง"</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"ไม่สามารถดาวน์โหลดรูปภาพในอัลบั้มนี้ โปรดลองอีกครั้งในภายหลัง"</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"ไม่สามารถดาวน์โหลดรายการอัลบั้ม โปรดลองอีกครั้งในภายหลัง"</string>
<string name="show_images_only" msgid="7263218480867672653">"เฉพาะภาพ"</string>
<string name="show_videos_only" msgid="3850394623678871697">"เฉพาะวิดีโอ"</string>
- <string name="show_all" msgid="4780647751652596980">"ภาพและวิดีโอ"</string>
+ <string name="show_all" msgid="6963292714584735149">"ภาพและวิดีโอ"</string>
<string name="appwidget_title" msgid="6410561146863700411">"แกลเลอรีรูปภาพ"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"ไม่มีรูปภาพ"</string>
<string name="crop_saved" msgid="1062612625032731770">"ภาพที่ตัดจะได้รับการบันทึกไว้ในโฟลเดอร์ดาวน์โหลด"</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"จัดกลุ่มตาม"</string>
<string name="settings" msgid="1534847740615665736">"การตั้งค่า"</string>
<string name="add_account" msgid="4271217504968243974">"เพิ่มบัญชี"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"กล้องถ่ายรูป"</string>
+ <string name="folder_download" msgid="7186215137642323932">"ดาวน์โหลด"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"นำเข้าแล้ว"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"ภาพหน้าจอ"</string>
+ <string name="help" msgid="7368960711153618354">"ความช่วยเหลือ"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index f52540b..46a1a98 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Ipagpatuloy ang pag-play"</string>
<string name="loading" msgid="7038208555304563571">"Naglo-load…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Hindi mai-load"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Hindi ma-load ang larawan"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Walang thumbnail"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Magsimula na"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"I-crop"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Pumindot ng mukha upang magsimula."</string>
<string name="saving_image" msgid="7270334453636349407">"Nagse-save ng larawan..."</string>
<string name="save_error" msgid="6857408774183654970">"Hindi mai-save ang na-crop na larawan."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Itinatakda ang wallpaper…"</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Wallpaper"</string>
<string name="delete" msgid="2839695998251824487">"Tanggalin"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Tanggalin"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Kanselahin"</string>
<string name="share" msgid="3619042788254195341">"Ibahagi"</string>
<string name="select_all" msgid="3403283025220282175">"Piliin lahat"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Hindi naka-tag"</string>
<string name="no_location" msgid="4043624857489331676">"Walang lokasyon"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Hindi matukoy ang ilang mga lokasyon dahil sa mga problema sa network."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Nabigong i-download ang mga larawan sa album na ito. Pakisubukang muli sa ibang pagkakataon."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Nabigong i-download ang listahan ng mga album. Pakisubukang muli sa ibang pagkakataon."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Hindi ma-download ang mga larawan sa album na ito. Pakisubukang muli sa ibang pagkakataon."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Hindi ma-download ang listahan ng mga album. Pakisubukang muli sa ibang pagkakataon."</string>
<string name="show_images_only" msgid="7263218480867672653">"Mga larawan lamang"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Mga video lamang"</string>
- <string name="show_all" msgid="4780647751652596980">"Mga larawan at mga video"</string>
+ <string name="show_all" msgid="6963292714584735149">"Mga larawan & video"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Photo Gallery"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Walang mga larawan."</string>
<string name="crop_saved" msgid="1062612625032731770">"Nai-save ang na-crop na larawan sa Mga Download."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Ipangkat ayon sa"</string>
<string name="settings" msgid="1534847740615665736">"Mga Setting"</string>
<string name="add_account" msgid="4271217504968243974">"Magdagdag ng account"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Camera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"I-download"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Na-import"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Screenshot"</string>
+ <string name="help" msgid="7368960711153618354">"Tulong"</string>
</resources>
diff --git a/res/values-tr/photoeditor_strings.xml b/res/values-tr/photoeditor_strings.xml
index 35b9ece..5321e4d 100644
--- a/res/values-tr/photoeditor_strings.xml
+++ b/res/values-tr/photoeditor_strings.xml
@@ -48,7 +48,7 @@
<string name="sepia" msgid="7978093531824705601">"Sepya Tonu"</string>
<string name="shadow" msgid="8235188588101973090">"Gölgeler"</string>
<string name="sharpen" msgid="8449662378104403230">"Keskinleştir"</string>
- <string name="straighten" msgid="5217801513491493491">"Düzelt"</string>
+ <string name="straighten" msgid="5217801513491493491">"Düzleştir"</string>
<string name="temperature" msgid="1607987938521534517">"Sıcaklık"</string>
<string name="tint" msgid="154435943863418434">"Tonlama"</string>
<string name="vignette" msgid="7648125924662648282">"Vinyet"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index da4d8ff..dab1423 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Yürütmeyi sürdür"</string>
<string name="loading" msgid="7038208555304563571">"Yükleniyor…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Yüklenemedi"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Resim yüklenemedi"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Küçük resim yok"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Başlat"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Kırp"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"Tamam"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Başlamak için bir yüze dokunun."</string>
<string name="saving_image" msgid="7270334453636349407">"Resim kaydediliyor..."</string>
<string name="save_error" msgid="6857408774183654970">"Kırpılmış resim kaydedilemedi."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Duvar kağıdı ayarlanıyor..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Duvar Kağıdı"</string>
<string name="delete" msgid="2839695998251824487">"Sil"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Sil"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"İptal"</string>
<string name="share" msgid="3619042788254195341">"Paylaş"</string>
<string name="select_all" msgid="3403283025220282175">"Tümünü seç"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Etiketlenmemiş"</string>
<string name="no_location" msgid="4043624857489331676">"Konum bilgisi yok"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Ağ sorunları nedeniyle bazı konumlar tanımlanamadı."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Bu albümdeki fotoğraflar indirilemedi. Lütfen daha sonra tekrar deneyin."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Albüm listesi indirilemedi. Lütfen daha sonra tekrar deneyin."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Bu albümdeki fotoğraflar indirilemedi. Daha sonra tekrar deneyin."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Albüm listesi indirilemedi. Daha sonra tekrar deneyin."</string>
<string name="show_images_only" msgid="7263218480867672653">"Yalnızca resimler"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Yalnızca videolar"</string>
- <string name="show_all" msgid="4780647751652596980">"Resimler ve videolar"</string>
+ <string name="show_all" msgid="6963292714584735149">"Resimler ve videolar"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Fotoğraf Galerisi"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Fotoğraf yok."</string>
<string name="crop_saved" msgid="1062612625032731770">"Kırpılmış resim İndirilenler klasörüne kaydedildi."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Grupla:"</string>
<string name="settings" msgid="1534847740615665736">"Ayarlar"</string>
<string name="add_account" msgid="4271217504968243974">"Hesap ekle"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Kamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"İndir"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"İçe aktarıldı"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Ekran görüntüsü"</string>
+ <string name="help" msgid="7368960711153618354">"Yardım"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index de76648..27aae30 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Віднов. відтвор."</string>
<string name="loading" msgid="7038208555304563571">"Завантаж…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Не вдалося завантажити"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Не вдалося завантажити зображення"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Немає ескізу"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Почати знову"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Обрізати"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Торкніться обличчя, щоб почати."</string>
<string name="saving_image" msgid="7270334453636349407">"Зберіг-ня фото…"</string>
<string name="save_error" msgid="6857408774183654970">"Не вдалося зберегти обрізане зображення."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Встановлення фонового малюнка..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Фоновий мал."</string>
<string name="delete" msgid="2839695998251824487">"Видалити"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Видалити"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Скасувати"</string>
<string name="share" msgid="3619042788254195341">"Надісл."</string>
<string name="select_all" msgid="3403283025220282175">"Вибрати всі"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Без тегів"</string>
<string name="no_location" msgid="4043624857489331676">"Без місцезнаходження"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Не вдалося визначити деякі місцезнаходження через проблеми з мережею."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Не вдалося завантажити фото цього альбому. Повторіть спробу пізніше."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Не вдалося завантажити список альбомів. Повторіть спробу пізніше."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Не вдалося завантажити фото цього альбому. Повторіть спробу пізніше."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Не вдалося завантажити список альбомів. Повторіть спробу пізніше."</string>
<string name="show_images_only" msgid="7263218480867672653">"Лише зображення"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Лише відео"</string>
- <string name="show_all" msgid="4780647751652596980">"Зображення та відео"</string>
+ <string name="show_all" msgid="6963292714584735149">"Зображення й відео"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Фотогалерея"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Фотографій немає."</string>
<string name="crop_saved" msgid="1062612625032731770">"Обрізане зображення збережено в папці завантажень."</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Групувати за"</string>
<string name="settings" msgid="1534847740615665736">"Налаштування"</string>
<string name="add_account" msgid="4271217504968243974">"Додати обліковий запис"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"З камери"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Звантаження"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Імпортовані"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Знімки екрана"</string>
+ <string name="help" msgid="7368960711153618354">"Довідка"</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index d8df856..325b0a8 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Tiếp tục phát"</string>
<string name="loading" msgid="7038208555304563571">"Đang tải…"</string>
<string name="fail_to_load" msgid="8394392853646664505">"Không thể tải"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Không thể tải hình ảnh"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Không có hình thu nhỏ"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Bắt đầu lại"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Cắt"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Chạm vào một khuôn mặt để bắt đầu."</string>
<string name="saving_image" msgid="7270334453636349407">"Đang lưu ảnh…"</string>
<string name="save_error" msgid="6857408774183654970">"Không thể lưu hình ảnh được cắt."</string>
@@ -39,13 +40,16 @@
<string name="select_image" msgid="7841406150484742140">"Chọn ảnh"</string>
<string name="select_video" msgid="4859510992798615076">"Chọn video"</string>
<string name="select_item" msgid="2816923896202086390">"Chọn mục"</string>
- <string name="select_album" msgid="1557063764849434077">"Chọn anbom"</string>
+ <string name="select_album" msgid="1557063764849434077">"Chọn album"</string>
<string name="select_group" msgid="6744208543323307114">"Chọn nhóm"</string>
<string name="set_image" msgid="2331476809308010401">"Đặt ảnh làm"</string>
<string name="wallpaper" msgid="140165383777262070">"Đang đặt hình nền..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Hình nền"</string>
<string name="delete" msgid="2839695998251824487">"Xóa"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Xóa"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Hủy"</string>
<string name="share" msgid="3619042788254195341">"Chia sẻ"</string>
<string name="select_all" msgid="3403283025220282175">"Chọn tất cả"</string>
@@ -61,9 +65,9 @@
<item quantity="other" msgid="754722656147810487">"%1$d mục được chọn"</item>
</plurals>
<plurals name="number_of_albums_selected">
- <item quantity="zero" msgid="749292746814788132">"%1$d anbom được chọn"</item>
- <item quantity="one" msgid="6184377003099987825">"%1$d anbom được chọn"</item>
- <item quantity="other" msgid="53105607141906130">"%1$d anbom được chọn"</item>
+ <item quantity="zero" msgid="749292746814788132">"%1$d album được chọn"</item>
+ <item quantity="one" msgid="6184377003099987825">"%1$d album được chọn"</item>
+ <item quantity="other" msgid="53105607141906130">"%1$d album được chọn"</item>
</plurals>
<plurals name="number_of_groups_selected">
<item quantity="zero" msgid="3466388370310869238">"%1$d nhóm được chọn"</item>
@@ -85,21 +89,21 @@
<string name="group_by_time" msgid="9046168567717963573">"Theo thời gian"</string>
<string name="group_by_tags" msgid="3568731317210676160">"Theo thẻ"</string>
<string name="group_by_faces" msgid="1566351636227274906">"Theo người"</string>
- <string name="group_by_album" msgid="1532818636053818958">"Theo anbom"</string>
+ <string name="group_by_album" msgid="1532818636053818958">"Theo album"</string>
<string name="group_by_size" msgid="153766174950394155">"Theo kích thước"</string>
<string name="untagged" msgid="7281481064509590402">"Không được gắn thẻ"</string>
<string name="no_location" msgid="4043624857489331676">"Không có vị trí nào"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Không thể xác định một số vị trí do sự cố mạng."</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Không thể tải xuống các ảnh trong anbom này. Vui lòng thử lại sau."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Không thể tải xuống danh sách anbom. Vui lòng thử lại sau."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Không thể tải xuống ảnh trong album này. Hãy thử lại sau."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Không thể tải xuống danh sách album. Hãy thử lại sau."</string>
<string name="show_images_only" msgid="7263218480867672653">"Chỉ hình ảnh"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Chỉ video"</string>
- <string name="show_all" msgid="4780647751652596980">"Hình ảnh và video"</string>
+ <string name="show_all" msgid="6963292714584735149">"Hình ảnh & video"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Thư viện ảnh"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Không có ảnh."</string>
<string name="crop_saved" msgid="1062612625032731770">"Đã lưu hình ảnh được cắt vào Tải xuống."</string>
<string name="crop_not_saved" msgid="3400773981839556">"Hình ảnh được cắt chưa được lưu."</string>
- <string name="no_albums_alert" msgid="4111744447491690896">"Không có anbom nào."</string>
+ <string name="no_albums_alert" msgid="4111744447491690896">"Không có album nào."</string>
<string name="empty_album" msgid="4542880442593595494">"O có hình ảnh/video nào."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Bài đăng"</string>
<string name="make_available_offline" msgid="5157950985488297112">"Làm cho sẵn có khi ngoại tuyến"</string>
@@ -131,14 +135,14 @@
<string name="flash_on" msgid="7891556231891837284">"Sử dụng flash"</string>
<string name="flash_off" msgid="1445443413822680010">"Không có flash"</string>
<plurals name="make_albums_available_offline">
- <item quantity="one" msgid="2171596356101611086">"Đặt anbom khả dụng ở chế độ ngoại tuyến."</item>
- <item quantity="other" msgid="4948604338155959389">"Đặt anbom khả dụng ở chế độ ngoại tuyến."</item>
+ <item quantity="one" msgid="2171596356101611086">"Đặt album khả dụng ở chế độ ngoại tuyến."</item>
+ <item quantity="other" msgid="4948604338155959389">"Đặt album khả dụng ở chế độ ngoại tuyến."</item>
</plurals>
<string name="try_to_set_local_album_available_offline" msgid="2184754031896160755">"Mục này được lưu cục bộ và khả dụng ngoại tuyến."</string>
- <string name="set_label_all_albums" msgid="4581863582996336783">"Tất cả anbom"</string>
- <string name="set_label_local_albums" msgid="6698133661656266702">"Anbom cục bộ"</string>
+ <string name="set_label_all_albums" msgid="4581863582996336783">"Tất cả album"</string>
+ <string name="set_label_local_albums" msgid="6698133661656266702">"Album cục bộ"</string>
<string name="set_label_mtp_devices" msgid="1283513183744896368">"Thiết bị MTP"</string>
- <string name="set_label_picasa_albums" msgid="5356258354953935895">"Anbom Picasa"</string>
+ <string name="set_label_picasa_albums" msgid="5356258354953935895">"Album Picasa"</string>
<string name="free_space_format" msgid="8766337315709161215">"<xliff:g id="BYTES">%s</xliff:g> trống"</string>
<string name="size_below" msgid="2074956730721942260">"<xliff:g id="SIZE">%1$s</xliff:g> trở xuống"</string>
<string name="size_above" msgid="5324398253474104087">"<xliff:g id="SIZE">%1$s</xliff:g> hoặc cao hơn"</string>
@@ -149,12 +153,12 @@
<string name="camera_connected" msgid="916021826223448591">"Đã kết nối máy ảnh."</string>
<string name="camera_disconnected" msgid="2100559901676329496">"Đã ngắt kết nối máy ảnh."</string>
<string name="click_import" msgid="6407959065464291972">"Chạm vào đây để nhập"</string>
- <string name="widget_type_album" msgid="6013045393140135468">"Chọn anbom"</string>
+ <string name="widget_type_album" msgid="6013045393140135468">"Chọn album"</string>
<string name="widget_type_shuffle" msgid="8594622705019763768">"Hiển thị ngẫu nhiên tất cả hình ảnh"</string>
<string name="widget_type_photo" msgid="6267065337367795355">"Chọn ảnh"</string>
<string name="widget_type" msgid="1364653978966343448">"Chọn hình ảnh"</string>
<string name="slideshow_dream_name" msgid="6915963319933437083">"Trình chiếu"</string>
- <string name="albums" msgid="7320787705180057947">"Anbom"</string>
+ <string name="albums" msgid="7320787705180057947">"Album"</string>
<string name="times" msgid="2023033894889499219">"Lần"</string>
<string name="locations" msgid="6649297994083130305">"Vị trí"</string>
<string name="people" msgid="4114003823747292747">"Mọi người"</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Nhóm theo"</string>
<string name="settings" msgid="1534847740615665736">"Cài đặt"</string>
<string name="add_account" msgid="4271217504968243974">"Thêm tài khoản"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Máy ảnh"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Tải xuống"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Đã nhập"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Ảnh chụp màn hình"</string>
+ <string name="help" msgid="7368960711153618354">"Trợ giúp"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index a58e1fe..3115784 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"继续播放"</string>
<string name="loading" msgid="7038208555304563571">"正在载入..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"无法加载"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"无法加载此图片"</string>
<string name="no_thumbnail" msgid="284723185546429750">"无缩略图"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"重新开始"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"裁剪"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"确定"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"触摸一张脸开始裁剪。"</string>
<string name="saving_image" msgid="7270334453636349407">"正在保存照片..."</string>
<string name="save_error" msgid="6857408774183654970">"无法保存经过裁剪的图片。"</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"正在设置壁纸..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"壁纸"</string>
<string name="delete" msgid="2839695998251824487">"删除"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"删除"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"取消"</string>
<string name="share" msgid="3619042788254195341">"分享"</string>
<string name="select_all" msgid="3403283025220282175">"全选"</string>
@@ -75,7 +79,7 @@
<string name="rotate_right" msgid="6776325835923384839">"向右旋转"</string>
<string name="no_such_item" msgid="5315144556325243400">"找不到指定的项。"</string>
<string name="edit" msgid="1502273844748580847">"编辑"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"没有可完成该操作的应用程序。"</string>
+ <string name="activity_not_found" msgid="5619154886080878023">"没有可完成该操作的应用。"</string>
<string name="process_caching_requests" msgid="8722939570307386071">"正在处理缓存请求"</string>
<string name="caching_label" msgid="4521059045896269095">"正在缓存..."</string>
<string name="crop_action" msgid="3427470284074377001">"修剪"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"未加标签"</string>
<string name="no_location" msgid="4043624857489331676">"无位置信息"</string>
<string name="no_connectivity" msgid="7164037617297293668">"出现网络问题,系统无法识别某些位置。"</string>
- <string name="sync_album_error" msgid="2218733298953719785">"无法下载此相册中的照片,请稍后重试。"</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"无法下载相册列表,请稍后重试。"</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"无法下载此相册中的照片,请稍后重试。"</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"无法下载相册列表,请稍后重试。"</string>
<string name="show_images_only" msgid="7263218480867672653">"仅限图片"</string>
<string name="show_videos_only" msgid="3850394623678871697">"仅限视频"</string>
- <string name="show_all" msgid="4780647751652596980">"图片和视频"</string>
+ <string name="show_all" msgid="6963292714584735149">"图片和视频"</string>
<string name="appwidget_title" msgid="6410561146863700411">"照片库"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"无照片。"</string>
<string name="crop_saved" msgid="1062612625032731770">"经过裁剪的图片已保存至“下载内容”文件夹。"</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"分组依据"</string>
<string name="settings" msgid="1534847740615665736">"设置"</string>
<string name="add_account" msgid="4271217504968243974">"添加帐户"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"相机"</string>
+ <string name="folder_download" msgid="7186215137642323932">"下载"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"已导入"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"屏幕截图"</string>
+ <string name="help" msgid="7368960711153618354">"帮助"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 1a4592f..10d4392 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"繼續播放"</string>
<string name="loading" msgid="7038208555304563571">"載入中..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"無法載入"</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"無法載入圖片"</string>
<string name="no_thumbnail" msgid="284723185546429750">"無縮圖"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"重新開始"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"裁剪"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"確定"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"輕觸臉孔即可開始。"</string>
<string name="saving_image" msgid="7270334453636349407">"正在儲存相片..."</string>
<string name="save_error" msgid="6857408774183654970">"無法儲存裁剪的圖片。"</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"正在設定桌布..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"桌布"</string>
<string name="delete" msgid="2839695998251824487">"刪除"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"刪除"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"取消"</string>
<string name="share" msgid="3619042788254195341">"分享"</string>
<string name="select_all" msgid="3403283025220282175">"全選"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"無標記"</string>
<string name="no_location" msgid="4043624857489331676">"無位置資訊"</string>
<string name="no_connectivity" msgid="7164037617297293668">"網路發生問題,因此無法辨識部分位置。"</string>
- <string name="sync_album_error" msgid="2218733298953719785">"無法下載這本相簿中的相片,請稍後再試。"</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"無法下載相簿清單,請稍後再試。"</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"無法下載這個相簿中的相片,請稍後再試。"</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"無法下載相簿清單,請稍後再試。"</string>
<string name="show_images_only" msgid="7263218480867672653">"僅顯示圖片"</string>
<string name="show_videos_only" msgid="3850394623678871697">"僅顯示影片"</string>
- <string name="show_all" msgid="4780647751652596980">"圖片和影片"</string>
+ <string name="show_all" msgid="6963292714584735149">"圖片和影片"</string>
<string name="appwidget_title" msgid="6410561146863700411">"相片庫"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"沒有任何相片。"</string>
<string name="crop_saved" msgid="1062612625032731770">"裁剪的圖片已儲存至「下載」資料夾。"</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"分組依據"</string>
<string name="settings" msgid="1534847740615665736">"設定"</string>
<string name="add_account" msgid="4271217504968243974">"新增帳戶"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"相機"</string>
+ <string name="folder_download" msgid="7186215137642323932">"下載"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"匯入"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"螢幕擷取畫面"</string>
+ <string name="help" msgid="7368960711153618354">"說明"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index e5a8e57..58cc4af 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -29,9 +29,10 @@
<string name="resume_playing_resume" msgid="3847915469173852416">"Qalisa ukudlala"</string>
<string name="loading" msgid="7038208555304563571">"Iyalayisha..."</string>
<string name="fail_to_load" msgid="8394392853646664505">"Ayikwazanga ukulayisha."</string>
+ <string name="fail_to_load_image" msgid="6155388718549782425">"Ayikwazanga ukulayisha umfanekiso"</string>
<string name="no_thumbnail" msgid="284723185546429750">"Asikho isithoombe esincane"</string>
<string name="resume_playing_restart" msgid="5471008499835769292">"Qala phansi"</string>
- <string name="crop_save_text" msgid="4972430481677741184">"Khropha"</string>
+ <string name="crop_save_text" msgid="152200178986698300">"Kulungile"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Cindezela ubuso ukuze uqale."</string>
<string name="saving_image" msgid="7270334453636349407">"Ilondoloza isithombe..."</string>
<string name="save_error" msgid="6857408774183654970">"Yehlulekile ukulondoloza umfanekiso onqampuniwe."</string>
@@ -45,7 +46,10 @@
<string name="wallpaper" msgid="140165383777262070">"Isetha iphephadonga..."</string>
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Iphephadonga"</string>
<string name="delete" msgid="2839695998251824487">"Susa"</string>
- <string name="confirm_delete" msgid="7358770022173660511">"Susa"</string>
+ <!-- no translation found for confirm_action (1642211585193899041) -->
+ <skip />
+ <!-- no translation found for confirm (8646870096527848520) -->
+ <skip />
<string name="cancel" msgid="3637516880917356226">"Khansela"</string>
<string name="share" msgid="3619042788254195341">"Yabelana"</string>
<string name="select_all" msgid="3403283025220282175">"Khetha konke"</string>
@@ -90,11 +94,11 @@
<string name="untagged" msgid="7281481064509590402">"Akunasilengiso"</string>
<string name="no_location" msgid="4043624857489331676">"Ayikho indawo"</string>
<string name="no_connectivity" msgid="7164037617297293668">"Ezinye izindawo azikwazanga ukubonakala ngenxa yezinkinga zokuxhumeka kuhleloxhumano"</string>
- <string name="sync_album_error" msgid="2218733298953719785">"Ayikwazanga ukulayisha ngokungenisa izithombe kule alibhamu. Zama futhi kamuva."</string>
- <string name="sync_album_set_error" msgid="9016732535181154028">"Ihlulekile ukungenisa ngokulayisha uhlu lwama-alibhumu. Sicela uzame kabusha."</string>
+ <string name="sync_album_error" msgid="1020688062900977530">"Ayikwazanga ukulanda izithombe kule albhamu. Zama ngemva kwesikhashana."</string>
+ <string name="sync_album_set_error" msgid="3250258387046904444">"Ayikwazanga ukulanda uhlu lwama-albhamu. Zama futhi ngemva kwesikhashana."</string>
<string name="show_images_only" msgid="7263218480867672653">"Izithombe kuphela"</string>
<string name="show_videos_only" msgid="3850394623678871697">"Amavidiyo kuphela"</string>
- <string name="show_all" msgid="4780647751652596980">"Izithombe namavidiyo"</string>
+ <string name="show_all" msgid="6963292714584735149">"Izithombe namavidiyo"</string>
<string name="appwidget_title" msgid="6410561146863700411">"Igalari Yesithombe"</string>
<string name="appwidget_empty_text" msgid="1228925628357366957">"Azikho izithombe"</string>
<string name="crop_saved" msgid="1062612625032731770">"Isithombe esincishisiwe silondolozwe kokulayishwe ngokungenisa."</string>
@@ -122,7 +126,7 @@
<string name="flash" msgid="2816779031261147723">"Ifuleshi"</string>
<string name="aperture" msgid="5920657630303915195">"Imbobo"</string>
<string name="focal_length" msgid="1291383769749877010">"Ubude Befokasi"</string>
- <string name="white_balance" msgid="1582509289994216078">"Bhalansa ubumhlophe"</string>
+ <string name="white_balance" msgid="1582509289994216078">"Ukulingana kokumhlophe"</string>
<string name="exposure_time" msgid="3990163680281058826">"Isikhathi esisobala"</string>
<string name="iso" msgid="5028296664327335940">"i-ISO"</string>
<string name="unit_mm" msgid="1125768433254329136">"mm"</string>
@@ -162,4 +166,9 @@
<string name="group_by" msgid="4308299657902209357">"Qoqa nge-"</string>
<string name="settings" msgid="1534847740615665736">"Izilungiselelo"</string>
<string name="add_account" msgid="4271217504968243974">"Yengeza i-akhawunti"</string>
+ <string name="folder_camera" msgid="4714658994809533480">"Ikhamera"</string>
+ <string name="folder_download" msgid="7186215137642323932">"Laysha"</string>
+ <string name="folder_imported" msgid="2773581395524747099">"Okulandiwe"</string>
+ <string name="folder_screenshot" msgid="7200396565864213450">"Isithombe-skrini"</string>
+ <string name="help" msgid="7368960711153618354">"Usizo"</string>
</resources>
diff --git a/res/values/dimensions.xml b/res/values/dimensions.xml
index 39b1b73..92e6713 100644
--- a/res/values/dimensions.xml
+++ b/res/values/dimensions.xml
@@ -41,16 +41,6 @@
<dimen name="cache_pin_size">24dp</dimen>
<dimen name="cache_pin_margin">8dp</dimen>
- <!-- configuration for film strip in photo page -->
- <dimen name="filmstrip_top_margin">12dp</dimen>
- <dimen name="filmstrip_mid_margin">0dp</dimen>
- <dimen name="filmstrip_bottom_margin">2dp</dimen>
- <dimen name="filmstrip_thumb_size">48dp</dimen>
- <dimen name="filmstrip_content_size">56dp</dimen>
- <dimen name="filmstrip_grip_size">10dp</dimen>
- <dimen name="filmstrip_bar_size">10dp</dimen>
- <dimen name="filmstrip_grip_width">96dp</dimen>
-
<!-- for manage cache bar -->
<dimen name="manage_cache_bottom_height">48dp</dimen>
</resources>
diff --git a/res/values/photoeditor_styles.xml b/res/values/photoeditor_styles.xml
index 7bd7fd0..c02296d 100644
--- a/res/values/photoeditor_styles.xml
+++ b/res/values/photoeditor_styles.xml
@@ -54,6 +54,20 @@
<item name="android:layout_alignParentBottom">true</item>
<item name="android:orientation">vertical</item>
</style>
+ <style name="EffectLabelInToolPanel" parent="@style/EffectLabel">
+ <item name="android:layout_marginTop">0dp</item>
+ </style>
+ <style name="SeekBar">
+ <item name="android:layout_width">@dimen/seekbar_width</item>
+ <item name="android:layout_height">@dimen/seekbar_height</item>
+ <item name="android:paddingTop">@dimen/seekbar_padding_vertical</item>
+ <item name="android:paddingBottom">@dimen/seekbar_padding_vertical</item>
+ <item name="android:paddingLeft">@dimen/seekbar_padding_horizontal</item>
+ <item name="android:paddingRight">@dimen/seekbar_padding_horizontal</item>
+ <item name="android:minHeight">@dimen/seekbar_height</item>
+ <item name="android:maxHeight">@dimen/seekbar_height</item>
+ <item name="android:background">@android:color/transparent</item>
+ </style>
<style name="ActionBarInner">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">?android:attr/actionBarSize</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index af62883..e8bf0f4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -48,6 +48,10 @@
[CHAR LIMIT=50]-->
<string name="fail_to_load">Couldn\'t load</string>
+ <!-- Used in a toast message when an image fails to be loaded
+ [CHAR LIMIT=50]-->
+ <string name="fail_to_load_image">Couldn\'t load the image</string>
+
<!-- Displayed in place of the picture when we fail to get the thumbnail of it.
[CHAR LIMIT=50]-->
<string name="no_thumbnail">No thumbnail</string>
@@ -57,7 +61,7 @@
<!-- Title of a menu item to indicate performing the image crop operation
[CHAR LIMIT=20] -->
- <string name="crop_save_text">Crop</string>
+ <string name="crop_save_text">OK</string>
<!-- Button indicating that the cropped image should be reverted back to the original -->
<!-- Hint that appears when cropping an image with more than one face -->
<string name="multiface_crop_help">Touch a face to begin.</string>
@@ -90,7 +94,8 @@
<!-- Details dialog "OK" button. Dismisses dialog. -->
<string name="delete">Delete</string>
- <string name="confirm_delete">Delete</string>
+ <string name="confirm_action">Confirm deletion?</string>
+ <string name="confirm">Confirm</string>
<string name="cancel">Cancel</string>
<string name="share">Share</string>
@@ -200,9 +205,9 @@
<string name="no_connectivity">Some locations couldn\'t be identified due to network problems.</string>
<!-- This toast message is shown when failed to load the album data. [CHAR LIMIT=NONE] -->
- <string name="sync_album_error">Failed to download the photos in this album. Please retry later.</string>
+ <string name="sync_album_error">Couldn\'t download the photos in this album. Retry later.</string>
<!-- This toast message is shown when failed to load the album list data. [CHAR LIMIT=NONE] -->
- <string name="sync_album_set_error">Failed to download the list of albums. Please retry later.</string>
+ <string name="sync_album_set_error">Couldn\'t download the list of albums. Retry later.</string>
<!-- The title of the menu item to let user choose the which portion of
the media items the user wants to see. When pressed, a submenu will
@@ -216,7 +221,7 @@
<string name="show_videos_only">Videos only</string>
<!-- Title of a menu item to show all (both images and videos) [CHAR LIMIT=30]-->
- <string name="show_all">Images and videos</string>
+ <string name="show_all">Images & videos</string>
<!-- Title of the StackView AppWidget -->
<string name="appwidget_title">Photo Gallery</string>
@@ -415,4 +420,23 @@
<!-- The title of menu item where user can add a new account -->
<string name="add_account">Add account</string>
+
+ <!-- The label for the folder contains pictures taken by the camera. [CHAR LIMIT=20]-->
+ <string name="folder_camera">Camera</string>
+
+ <!-- The label for the folder contains downloaded pictures. [CHAR LIMIT=20]-->
+ <string name="folder_download">Download</string>
+
+ <!-- The label for the folder contains pictures that was imported from an
+ external camera. [CHAR LIMIT=20]-->
+ <string name="folder_imported">Imported</string>
+
+ <!-- The label for the folder contains screenshot images. [CHAR LIMIT=20]-->
+ <string name="folder_screenshot">Screenshot</string>
+
+ <!-- The title of the menu item which display online help in browser. [CHAR LIMIT=20]-->
+ <string name="help">Help</string>
+
+ <!-- Web address for gallery help. DO NOT TRANSLATE -->
+ <string name="general_help_link" translatable="false">http://support.google.com/mobile/?p=gallery_top</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index b26c728..0ef23f9 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -17,14 +17,17 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<style name="Theme.Gallery" parent="android:Theme.Holo">
<item name="android:displayOptions"></item>
- <item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:actionBarStyle">@style/Holo.ActionBar</item>
<item name="android:windowBackground">@null</item>
<item name="android:colorBackground">@null</item>
<item name="android:colorBackgroundCacheHint">@null</item>
</style>
+ <style name="Theme.MovieActivity" parent="Theme.Gallery">
+ <item name="android:windowBackground">@android:color/black</item>
+ </style>
<style name="Holo.ActionBar" parent="android:Widget.Holo.ActionBar">
+ <item name="android:displayOptions">useLogo|showHome</item>
<item name="android:background">@drawable/actionbar_translucent</item>
<item name="android:backgroundStacked">@null</item>
</style>
diff --git a/src/com/android/gallery3d/anim/Animation.java b/src/com/android/gallery3d/anim/Animation.java
index bd5a6cd..cc117bb 100644
--- a/src/com/android/gallery3d/anim/Animation.java
+++ b/src/com/android/gallery3d/anim/Animation.java
@@ -16,10 +16,10 @@
package com.android.gallery3d.anim;
-import com.android.gallery3d.common.Utils;
-
import android.view.animation.Interpolator;
+import com.android.gallery3d.common.Utils;
+
// Animation calculates a value according to the current input time.
//
// 1. First we need to use setDuration(int) to set the duration of the
diff --git a/src/com/android/gallery3d/anim/AnimationSet.java b/src/com/android/gallery3d/anim/AnimationSet.java
deleted file mode 100644
index 773cb43..0000000
--- a/src/com/android/gallery3d/anim/AnimationSet.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.anim;
-
-import com.android.gallery3d.ui.GLCanvas;
-
-import java.util.ArrayList;
-
-public class AnimationSet extends CanvasAnimation {
-
- private final ArrayList<CanvasAnimation> mAnimations =
- new ArrayList<CanvasAnimation>();
- private int mSaveFlags = 0;
-
-
- public void addAnimation(CanvasAnimation anim) {
- mAnimations.add(anim);
- mSaveFlags |= anim.getCanvasSaveFlags();
- }
-
- @Override
- public void apply(GLCanvas canvas) {
- for (int i = 0, n = mAnimations.size(); i < n; i++) {
- mAnimations.get(i).apply(canvas);
- }
- }
-
- @Override
- public int getCanvasSaveFlags() {
- return mSaveFlags;
- }
-
- @Override
- protected void onCalculate(float progress) {
- // DO NOTHING
- }
-
- @Override
- public boolean calculate(long currentTimeMillis) {
- boolean more = false;
- for (CanvasAnimation anim : mAnimations) {
- more |= anim.calculate(currentTimeMillis);
- }
- return more;
- }
-
- @Override
- public void start() {
- for (CanvasAnimation anim : mAnimations) {
- anim.start();
- }
- }
-
- @Override
- public boolean isActive() {
- for (CanvasAnimation anim : mAnimations) {
- if (anim.isActive()) return true;
- }
- return false;
- }
-
-}
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
index d25f60e..09e72c0 100644
--- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java
+++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
@@ -16,15 +16,6 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.R;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.ImageCacheService;
-import com.android.gallery3d.ui.GLRoot;
-import com.android.gallery3d.ui.GLRootView;
-import com.android.gallery3d.ui.PositionRepository;
-import com.android.gallery3d.util.ThreadPool;
-
-import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
@@ -36,13 +27,24 @@
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.GLRootView;
+import com.android.gallery3d.util.ThreadPool;
public class AbstractGalleryActivity extends Activity implements GalleryActivity {
@SuppressWarnings("unused")
private static final String TAG = "AbstractGalleryActivity";
private GLRootView mGLRootView;
private StateManager mStateManager;
- private PositionRepository mPositionRepository = new PositionRepository();
+ private GalleryActionBar mActionBar;
+ private boolean mDisableToggleStatusBar;
private AlertDialog mAlertDialog = null;
private BroadcastReceiver mMountReceiver = new BroadcastReceiver() {
@@ -54,6 +56,12 @@
private IntentFilter mMountFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
@Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ toggleStatusBarByOrientation();
+ }
+
+ @Override
protected void onSaveInstanceState(Bundle outState) {
mGLRootView.lockRenderThread();
try {
@@ -69,16 +77,13 @@
super.onConfigurationChanged(config);
mStateManager.onConfigurationChange(config);
invalidateOptionsMenu();
+ toggleStatusBarByOrientation();
}
public Context getAndroidContext() {
return this;
}
- public ImageCacheService getImageCacheService() {
- return ((GalleryApp) getApplication()).getImageCacheService();
- }
-
public DataManager getDataManager() {
return ((GalleryApp) getApplication()).getDataManager();
}
@@ -87,10 +92,6 @@
return ((GalleryApp) getApplication()).getThreadPool();
}
- public GalleryApp getGalleryApplication() {
- return (GalleryApp) getApplication();
- }
-
public synchronized StateManager getStateManager() {
if (mStateManager == null) {
mStateManager = new StateManager(this);
@@ -102,21 +103,12 @@
return mGLRootView;
}
- public PositionRepository getPositionRepository() {
- return mPositionRepository;
- }
-
@Override
public void setContentView(int resId) {
super.setContentView(resId);
mGLRootView = (GLRootView) findViewById(R.id.gl_root_view);
}
- public int getActionBarHeight() {
- ActionBar actionBar = getActionBar();
- return actionBar != null ? actionBar.getHeight() : 0;
- }
-
protected void onStorageReady() {
if (mAlertDialog != null) {
mAlertDialog.dismiss();
@@ -186,6 +178,20 @@
} finally {
mGLRootView.unlockRenderThread();
}
+ MediaItem.getMicroThumbPool().clear();
+ MediaItem.getThumbPool().clear();
+ MediaItem.getBytesBufferPool().clear();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mGLRootView.lockRenderThread();
+ try {
+ getStateManager().destroy();
+ } finally {
+ mGLRootView.unlockRenderThread();
+ }
}
@Override
@@ -200,7 +206,49 @@
}
@Override
+ public void onBackPressed() {
+ // send the back event to the top sub-state
+ GLRoot root = getGLRoot();
+ root.lockRenderThread();
+ try {
+ getStateManager().onBackPressed();
+ } finally {
+ root.unlockRenderThread();
+ }
+ }
+
+ @Override
public GalleryActionBar getGalleryActionBar() {
- return null;
+ if (mActionBar == null) {
+ mActionBar = new GalleryActionBar(this);
+ }
+ return mActionBar;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ GLRoot root = getGLRoot();
+ root.lockRenderThread();
+ try {
+ return getStateManager().itemSelected(item);
+ } finally {
+ root.unlockRenderThread();
+ }
+ }
+
+ protected void disableToggleStatusBar() {
+ mDisableToggleStatusBar = true;
+ }
+
+ // Shows status bar in portrait view, hide in landscape view
+ private void toggleStatusBarByOrientation() {
+ if (mDisableToggleStatusBar) return;
+
+ Window win = getWindow();
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ } else {
+ win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
}
}
diff --git a/src/com/android/gallery3d/app/ActivityState.java b/src/com/android/gallery3d/app/ActivityState.java
index 1563a09..443e2bd 100644
--- a/src/com/android/gallery3d/app/ActivityState.java
+++ b/src/com/android/gallery3d/app/ActivityState.java
@@ -16,15 +16,12 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.ui.GLView;
-
import android.app.ActionBar;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.Intent;
import android.content.res.Configuration;
import android.os.BatteryManager;
import android.os.Bundle;
@@ -34,10 +31,12 @@
import android.view.Window;
import android.view.WindowManager;
+import com.android.gallery3d.ui.GLView;
+
abstract public class ActivityState {
- public static final int FLAG_HIDE_ACTION_BAR = 1;
- public static final int FLAG_HIDE_STATUS_BAR = 2;
- public static final int FLAG_SCREEN_ON = 3;
+ protected static final int FLAG_HIDE_ACTION_BAR = 1;
+ protected static final int FLAG_HIDE_STATUS_BAR = 2;
+ protected static final int FLAG_SCREEN_ON = 4;
private static final int SCREEN_ON_FLAGS = (
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
@@ -56,7 +55,6 @@
public int requestCode;
public int resultCode = Activity.RESULT_CANCELED;
public Intent resultData;
- ResultEntry next;
}
private boolean mDestroyed = false;
@@ -143,10 +141,9 @@
actionBar.show();
}
int stateCount = mActivity.getStateManager().getStateCount();
- actionBar.setDisplayOptions(
- stateCount == 1 ? 0 : ActionBar.DISPLAY_HOME_AS_UP,
- ActionBar.DISPLAY_HOME_AS_UP);
- actionBar.setHomeButtonEnabled(true);
+ mActivity.getGalleryActionBar().setDisplayOptions(stateCount > 1, true);
+ // Default behavior, this can be overridden in ActivityState's onResume.
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
}
activity.invalidateOptionsMenu();
diff --git a/src/com/android/gallery3d/app/AlbumDataAdapter.java b/src/com/android/gallery3d/app/AlbumDataLoader.java
similarity index 93%
rename from src/com/android/gallery3d/app/AlbumDataAdapter.java
rename to src/com/android/gallery3d/app/AlbumDataLoader.java
index 6711786..a99cf93 100644
--- a/src/com/android/gallery3d/app/AlbumDataAdapter.java
+++ b/src/com/android/gallery3d/app/AlbumDataLoader.java
@@ -18,6 +18,7 @@
import android.os.Handler;
import android.os.Message;
+import android.os.Process;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.ContentListener;
@@ -25,7 +26,7 @@
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.ui.AlbumView;
+import com.android.gallery3d.ui.AlbumSlotRenderer;
import com.android.gallery3d.ui.SynchronizedHandler;
import java.util.ArrayList;
@@ -34,7 +35,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
-public class AlbumDataAdapter implements AlbumView.Model {
+public class AlbumDataLoader {
@SuppressWarnings("unused")
private static final String TAG = "AlbumDataAdapter";
private static final int DATA_CACHE_SIZE = 1000;
@@ -50,6 +51,11 @@
private final long[] mItemVersion;
private final long[] mSetVersion;
+ public static interface DataListener {
+ public void onContentChanged(int index);
+ public void onSizeChanged(int size);
+ }
+
private int mActiveStart = 0;
private int mActiveEnd = 0;
@@ -62,13 +68,13 @@
private final Handler mMainHandler;
private int mSize = 0;
- private AlbumView.ModelListener mModelListener;
+ private DataListener mDataListener;
private MySourceListener mSourceListener = new MySourceListener();
private LoadingListener mLoadingListener;
private ReloadTask mReloadTask;
- public AlbumDataAdapter(GalleryActivity context, MediaSet mediaSet) {
+ public AlbumDataLoader(GalleryActivity context, MediaSet mediaSet) {
mSource = mediaSet;
mData = new MediaItem[DATA_CACHE_SIZE];
@@ -119,10 +125,6 @@
return mActiveStart;
}
- public int getActiveEnd() {
- return mActiveEnd;
- }
-
public boolean isActive(int index) {
return index >= mActiveStart && index < mActiveEnd;
}
@@ -192,8 +194,8 @@
}
}
- public void setModelListener(AlbumView.ModelListener listener) {
- mModelListener = listener;
+ public void setDataListener(DataListener listener) {
+ mDataListener = listener;
}
public void setLoadingListener(LoadingListener listener) {
@@ -261,7 +263,7 @@
mSourceVersion = info.version;
if (mSize != info.size) {
mSize = info.size;
- if (mModelListener != null) mModelListener.onSizeChanged(mSize);
+ if (mDataListener != null) mDataListener.onSizeChanged(mSize);
if (mContentEnd > mSize) mContentEnd = mSize;
if (mActiveEnd > mSize) mActiveEnd = mSize;
}
@@ -280,8 +282,8 @@
if (mItemVersion[index] != itemVersion) {
mItemVersion[index] = itemVersion;
mData[index] = updateItem;
- if (mModelListener != null && i >= mActiveStart && i < mActiveEnd) {
- mModelListener.onWindowContentChanged(i);
+ if (mDataListener != null && i >= mActiveStart && i < mActiveEnd) {
+ mDataListener.onContentChanged(i);
}
}
}
@@ -318,6 +320,8 @@
@Override
public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
boolean updateComplete = false;
while (mActive) {
synchronized (this) {
diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java
index 6fb4143..c3b04d6 100644
--- a/src/com/android/gallery3d/app/AlbumPage.java
+++ b/src/com/android/gallery3d/app/AlbumPage.java
@@ -19,8 +19,11 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
import android.os.Vibrator;
import android.provider.MediaStore;
import android.view.ActionMode;
@@ -40,30 +43,30 @@
import com.android.gallery3d.data.Path;
import com.android.gallery3d.ui.ActionModeHandler;
import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
-import com.android.gallery3d.ui.AlbumView;
+import com.android.gallery3d.ui.AlbumSlotRenderer;
import com.android.gallery3d.ui.DetailsHelper;
import com.android.gallery3d.ui.DetailsHelper.CloseListener;
+import com.android.gallery3d.ui.FadeTexture;
import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.ui.GLView;
-import com.android.gallery3d.ui.GridDrawer;
-import com.android.gallery3d.ui.HighlightDrawer;
-import com.android.gallery3d.ui.PositionProvider;
-import com.android.gallery3d.ui.PositionRepository;
-import com.android.gallery3d.ui.PositionRepository.Position;
+import com.android.gallery3d.ui.RelativePosition;
+import com.android.gallery3d.ui.ScreenNailHolder;
import com.android.gallery3d.ui.SelectionManager;
import com.android.gallery3d.ui.SlotView;
-import com.android.gallery3d.ui.StaticBackground;
+import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.GalleryUtils;
-import java.util.Random;
-
public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
SelectionManager.SelectionListener, MediaSet.SyncListener {
@SuppressWarnings("unused")
private static final String TAG = "AlbumPage";
+ private static final int MSG_PICK_PHOTO = 1;
+
public static final String KEY_MEDIA_PATH = "media-path";
+ public static final String KEY_PARENT_MEDIA_PATH = "parent-media-path";
public static final String KEY_SET_CENTER = "set-center";
public static final String KEY_AUTO_SELECT_ALL = "auto-select-all";
public static final String KEY_SHOW_CLUSTER_MENU = "cluster-menu";
@@ -76,18 +79,18 @@
private static final int BIT_LOADING_SYNC = 2;
private static final float USER_DISTANCE_METER = 0.3f;
+ private static final boolean TEST_CAMERA_PREVIEW = false;
private boolean mIsActive = false;
- private StaticBackground mStaticBackground;
- private AlbumView mAlbumView;
+ private AlbumSlotRenderer mAlbumView;
private Path mMediaSetPath;
+ private String mParentMediaSetString;
+ private SlotView mSlotView;
- private AlbumDataAdapter mAlbumDataAdapter;
+ private AlbumDataLoader mAlbumDataAdapter;
protected SelectionManager mSelectionManager;
private Vibrator mVibrator;
- private GridDrawer mGridDrawer;
- private HighlightDrawer mHighlightDrawer;
private boolean mGetContent;
private boolean mShowClusterMenu;
@@ -100,35 +103,41 @@
private MediaSet mMediaSet;
private boolean mShowDetails;
private float mUserDistance; // in pixel
+ private Handler mHandler;
private Future<Integer> mSyncTask = null;
private int mLoadingBits = 0;
private boolean mInitialSynced = false;
+ private RelativePosition mOpenCenter = new RelativePosition();
private final GLView mRootPane = new GLView() {
private final float mMatrix[] = new float[16];
@Override
+ protected void renderBackground(GLCanvas view) {
+ view.clearBuffer();
+ }
+
+ @Override
protected void onLayout(
boolean changed, int left, int top, int right, int bottom) {
- mStaticBackground.layout(0, 0, right - left, bottom - top);
- int slotViewTop = GalleryActionBar.getHeight((Activity) mActivity);
+ int slotViewTop = mActivity.getGalleryActionBar().getHeight();
int slotViewBottom = bottom - top;
int slotViewRight = right - left;
if (mShowDetails) {
mDetailsHelper.layout(left, slotViewTop, right, bottom);
} else {
- mAlbumView.setSelectionDrawer(mGridDrawer);
+ mAlbumView.setHighlightItemPath(null);
}
- mAlbumView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
+ // Set the mSlotView as a reference point to the open animation
+ mOpenCenter.setReferencePosition(0, slotViewTop);
+ mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
GalleryUtils.setViewPointMatrix(mMatrix,
(right - left) / 2, (bottom - top) / 2, -mUserDistance);
- PositionRepository.getInstance(mActivity).setOffset(
- 0, slotViewTop);
}
@Override
@@ -147,54 +156,79 @@
} else if (mSelectionManager.inSelectionMode()) {
mSelectionManager.leaveSelectionMode();
} else {
- mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
+ // TODO: fix this regression
+ // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
super.onBackPressed();
}
}
private void onDown(int index) {
- MediaItem item = mAlbumDataAdapter.get(index);
- Path path = (item == null) ? null : item.getPath();
- mSelectionManager.setPressedPath(path);
- mAlbumView.invalidate();
+ mAlbumView.setPressedIndex(index);
}
- private void onUp() {
- mSelectionManager.setPressedPath(null);
- mAlbumView.invalidate();
- }
-
- public void onSingleTapUp(int slotIndex) {
- MediaItem item = mAlbumDataAdapter.get(slotIndex);
- if (item == null) {
- Log.w(TAG, "item not ready yet, ignore the click");
- return;
- }
- if (mShowDetails) {
- mHighlightDrawer.setHighlightItem(item.getPath());
- mDetailsHelper.reloadDetails(slotIndex);
- } else if (!mSelectionManager.inSelectionMode()) {
- if (mGetContent) {
- onGetContent(item);
- } else {
- // Get into the PhotoPage.
- Bundle data = new Bundle();
- mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
- data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
- data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
- mMediaSetPath.toString());
- data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
- item.getPath().toString());
- mActivity.getStateManager().startStateForResult(
- PhotoPage.class, REQUEST_PHOTO, data);
- }
+ private void onUp(boolean followedByLongPress) {
+ if (followedByLongPress) {
+ // Avoid showing press-up animations for long-press.
+ mAlbumView.setPressedIndex(-1);
} else {
- mSelectionManager.toggle(item.getPath());
- mDetailsSource.findIndex(slotIndex);
- mAlbumView.invalidate();
+ mAlbumView.setPressedUp();
}
}
+ private void onSingleTapUp(int slotIndex) {
+ if (!mIsActive) return;
+
+ if (mSelectionManager.inSelectionMode()) {
+ MediaItem item = mAlbumDataAdapter.get(slotIndex);
+ if (item == null) return; // Item not ready yet, ignore the click
+ mSelectionManager.toggle(item.getPath());
+ mSlotView.invalidate();
+ } else {
+ // Show pressed-up animation for the single-tap.
+ mAlbumView.setPressedIndex(slotIndex);
+ mAlbumView.setPressedUp();
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PICK_PHOTO, slotIndex, 0),
+ FadeTexture.DURATION);
+ }
+ }
+
+ private void pickPhoto(int slotIndex) {
+ if (!mIsActive) return;
+
+ MediaItem item = mAlbumDataAdapter.get(slotIndex);
+ if (item == null) return; // Item not ready yet, ignore the click
+ if (mGetContent) {
+ onGetContent(item);
+ } else {
+ // Get into the PhotoPage.
+ // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
+ Bundle data = new Bundle();
+ data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
+ data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT,
+ getSlotRect(slotIndex));
+ data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
+ mMediaSetPath.toString());
+ data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
+ item.getPath().toString());
+ if (TEST_CAMERA_PREVIEW) {
+ ScreenNailHolder holder = new CameraScreenNailHolder(mActivity);
+ data.putParcelable(PhotoPage.KEY_SCREENNAIL_HOLDER, holder);
+ }
+ mActivity.getStateManager().startStateForResult(
+ PhotoPage.class, REQUEST_PHOTO, data);
+ }
+ }
+
+ private Rect getSlotRect(int slotIndex) {
+ // Get slot rectangle relative to this root pane.
+ Rect offset = new Rect();
+ mRootPane.getBoundsOf(mSlotView, offset);
+ Rect r = mSlotView.getSlotRect(slotIndex);
+ r.offset(offset.left - mSlotView.getScrollX(),
+ offset.top - mSlotView.getScrollY());
+ return r;
+ }
+
private void onGetContent(final MediaItem item) {
DataManager dm = mActivity.getDataManager();
Activity activity = (Activity) mActivity;
@@ -218,18 +252,14 @@
public void onLongTap(int slotIndex) {
if (mGetContent) return;
- if (mShowDetails) {
- onSingleTapUp(slotIndex);
- } else {
- MediaItem item = mAlbumDataAdapter.get(slotIndex);
- if (item == null) return;
- mSelectionManager.setAutoLeaveSelectionMode(true);
- mSelectionManager.toggle(item.getPath());
- mDetailsSource.findIndex(slotIndex);
- mAlbumView.invalidate();
- }
+ MediaItem item = mAlbumDataAdapter.get(slotIndex);
+ if (item == null) return;
+ mSelectionManager.setAutoLeaveSelectionMode(true);
+ mSelectionManager.toggle(item.getPath());
+ mSlotView.invalidate();
}
+ @Override
public void doCluster(int clusterType) {
String basePath = mMediaSet.getPath().toString();
String newPath = FilterUtils.newClusterPath(basePath, clusterType);
@@ -242,25 +272,11 @@
GalleryActionBar.getClusterByTypeString(context, clusterType));
}
- mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
+ // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
mActivity.getStateManager().startStateForResult(
AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
}
- public void doFilter(int filterType) {
- String basePath = mMediaSet.getPath().toString();
- String newPath = FilterUtils.switchFilterPath(basePath, filterType);
- Bundle data = new Bundle(getData());
- data.putString(AlbumPage.KEY_MEDIA_PATH, newPath);
- mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
- mActivity.getStateManager().switchState(this, AlbumPage.class, data);
- }
-
- public void onOperationComplete() {
- mAlbumView.invalidate();
- // TODO: enable animation
- }
-
@Override
protected void onCreate(Bundle data, Bundle restoreState) {
mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
@@ -272,52 +288,32 @@
Context context = mActivity.getAndroidContext();
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- startTransition(data);
-
// Enable auto-select-all for mtp album
if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
mSelectionManager.selectAll();
}
- }
- private void startTransition() {
- final PositionRepository repository =
- PositionRepository.getInstance(mActivity);
- mAlbumView.startTransition(new PositionProvider() {
- private final Position mTempPosition = new Position();
- public Position getPosition(long identity, Position target) {
- Position p = repository.get(identity);
- if (p != null) return p;
- mTempPosition.set(target);
- mTempPosition.z = 128;
- return mTempPosition;
+ // Don't show animation if it is restored
+ if (restoreState == null && data != null) {
+ int[] center = data.getIntArray(KEY_SET_CENTER);
+ if (center != null) {
+ mOpenCenter.setAbsolutePosition(center[0], center[1]);
+ mSlotView.startScatteringAnimation(mOpenCenter);
}
- });
- }
+ }
- private void startTransition(Bundle data) {
- final PositionRepository repository =
- PositionRepository.getInstance(mActivity);
- final int[] center = data == null
- ? null
- : data.getIntArray(KEY_SET_CENTER);
- final Random random = new Random();
- mAlbumView.startTransition(new PositionProvider() {
- private final Position mTempPosition = new Position();
- public Position getPosition(long identity, Position target) {
- Position p = repository.get(identity);
- if (p != null) return p;
- if (center != null) {
- random.setSeed(identity);
- mTempPosition.set(center[0], center[1],
- 0, random.nextInt(60) - 30, 0);
- } else {
- mTempPosition.set(target);
- mTempPosition.z = 128;
+ mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_PICK_PHOTO: {
+ pickPhoto(message.arg1);
+ break;
+ }
+ default: throw new AssertionError(message.what);
}
- return mTempPosition;
}
- });
+ };
}
@Override
@@ -326,6 +322,11 @@
mIsActive = true;
setContentPane(mRootPane);
+ Path path = mMediaSet.getPath();
+ boolean enableHomeButton = (mActivity.getStateManager().getStateCount() > 1) |
+ mParentMediaSetString != null;
+ mActivity.getGalleryActionBar().setDisplayOptions(enableHomeButton, true);
+
// Set the reload bit here to prevent it exit this page in clearLoadingBit().
setLoadingBit(BIT_LOADING_RELOAD);
mAlbumDataAdapter.resume();
@@ -349,8 +350,10 @@
if (mSyncTask != null) {
mSyncTask.cancel();
mSyncTask = null;
+ clearLoadingBit(BIT_LOADING_SYNC);
}
mActionModeHandler.pause();
+ GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
}
@Override
@@ -362,26 +365,22 @@
}
private void initializeViews() {
- mStaticBackground = new StaticBackground((Context) mActivity);
- mRootPane.addComponent(mStaticBackground);
-
mSelectionManager = new SelectionManager(mActivity, false);
mSelectionManager.setSelectionListener(this);
- mGridDrawer = new GridDrawer((Context) mActivity, mSelectionManager);
Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity);
- mAlbumView = new AlbumView(mActivity, config.slotViewSpec,
- 0 /* don't cache thumbnail */);
- mAlbumView.setSelectionDrawer(mGridDrawer);
- mRootPane.addComponent(mAlbumView);
- mAlbumView.setListener(new SlotView.SimpleListener() {
+ mSlotView = new SlotView(mActivity, config.slotViewSpec);
+ mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView, mSelectionManager);
+ mSlotView.setSlotRenderer(mAlbumView);
+ mRootPane.addComponent(mSlotView);
+ mSlotView.setListener(new SlotView.SimpleListener() {
@Override
public void onDown(int index) {
AlbumPage.this.onDown(index);
}
@Override
- public void onUp() {
- AlbumPage.this.onUp();
+ public void onUp(boolean followedByLongPress) {
+ AlbumPage.this.onUp(followedByLongPress);
}
@Override
@@ -400,17 +399,17 @@
return onItemSelected(item);
}
});
- mStaticBackground.setImage(R.drawable.background,
- R.drawable.background_portrait);
}
private void initializeData(Bundle data) {
mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
+ mParentMediaSetString = data.getString(KEY_PARENT_MEDIA_PATH);
mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
- Utils.assertTrue(mMediaSet != null,
- "MediaSet is null. Path = %s", mMediaSetPath);
+ if (mMediaSet == null) {
+ Utils.fail("MediaSet is null. Path = %s", mMediaSetPath);
+ }
mSelectionManager.setSourceMediaSet(mMediaSet);
- mAlbumDataAdapter = new AlbumDataAdapter(mActivity, mMediaSet);
+ mAlbumDataAdapter = new AlbumDataLoader(mActivity, mMediaSet);
mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
mAlbumView.setModel(mAlbumDataAdapter);
}
@@ -418,8 +417,6 @@
private void showDetails() {
mShowDetails = true;
if (mDetailsHelper == null) {
- mHighlightDrawer = new HighlightDrawer(mActivity.getAndroidContext(),
- mSelectionManager);
mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
mDetailsHelper.setCloseListener(new CloseListener() {
public void onClose() {
@@ -427,15 +424,14 @@
}
});
}
- mAlbumView.setSelectionDrawer(mHighlightDrawer);
mDetailsHelper.show();
}
private void hideDetails() {
mShowDetails = false;
mDetailsHelper.hide();
- mAlbumView.setSelectionDrawer(mGridDrawer);
- mAlbumView.invalidate();
+ mAlbumView.setHighlightItemPath(null);
+ mSlotView.invalidate();
}
@Override
@@ -476,6 +472,16 @@
@Override
protected boolean onItemSelected(MenuItem item) {
switch (item.getItemId()) {
+ case android.R.id.home: {
+ if (mActivity.getStateManager().getStateCount() > 1) {
+ onBackPressed();
+ } else if (mParentMediaSetString != null) {
+ Bundle data = new Bundle(getData());
+ data.putString(AlbumSetPage.KEY_MEDIA_PATH, mParentMediaSetString);
+ mActivity.getStateManager().switchState(this, AlbumSetPage.class, data);
+ }
+ return true;
+ }
case R.id.action_cancel:
mActivity.getStateManager().finishState(this);
return true;
@@ -516,18 +522,18 @@
// data could be null, if there is no images in the album
if (data == null) return;
mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
- mAlbumView.setCenterIndex(mFocusIndex);
+ mSlotView.setCenterIndex(mFocusIndex);
break;
}
case REQUEST_PHOTO: {
if (data == null) return;
mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0);
- mAlbumView.setCenterIndex(mFocusIndex);
- startTransition();
+ mSlotView.setCenterIndex(mFocusIndex);
+ mSlotView.startRestoringAnimation(mFocusIndex);
break;
}
case REQUEST_DO_ANIMATION: {
- startTransition(null);
+ mSlotView.startRisingAnimation();
break;
}
}
@@ -569,21 +575,26 @@
((Activity) mActivity).runOnUiThread(new Runnable() {
@Override
public void run() {
- if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
- mInitialSynced = true;
- }
- if (!mIsActive) return;
- clearLoadingBit(BIT_LOADING_SYNC);
- if (resultCode == MediaSet.SYNC_RESULT_ERROR) {
- Toast.makeText((Context) mActivity, R.string.sync_album_error,
- Toast.LENGTH_LONG).show();
+ GLRoot root = mActivity.getGLRoot();
+ root.lockRenderThread();
+ try {
+ if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
+ mInitialSynced = true;
+ }
+ clearLoadingBit(BIT_LOADING_SYNC);
+ if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
+ Toast.makeText((Context) mActivity, R.string.sync_album_error,
+ Toast.LENGTH_LONG).show();
+ }
+ } finally {
+ root.unlockRenderThread();
}
}
});
}
private void setLoadingBit(int loadTaskBit) {
- if (mLoadingBits == 0) {
+ if (mLoadingBits == 0 && mIsActive) {
GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
}
mLoadingBits |= loadTaskBit;
@@ -591,7 +602,7 @@
private void clearLoadingBit(int loadTaskBit) {
mLoadingBits &= ~loadTaskBit;
- if (mLoadingBits == 0) {
+ if (mLoadingBits == 0 && mIsActive) {
GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
if (mAlbumDataAdapter.size() == 0) {
@@ -610,7 +621,6 @@
@Override
public void onLoadingFinished() {
- if (!mIsActive) return;
clearLoadingBit(BIT_LOADING_RELOAD);
}
}
@@ -643,7 +653,7 @@
public MediaDetails getDetails() {
MediaObject item = mAlbumDataAdapter.get(mIndex);
if (item != null) {
- mHighlightDrawer.setHighlightItem(item.getPath());
+ mAlbumView.setHighlightItemPath(item.getPath());
return item.getDetails();
} else {
return null;
diff --git a/src/com/android/gallery3d/app/AlbumPicker.java b/src/com/android/gallery3d/app/AlbumPicker.java
index ef5847e..7509e63 100644
--- a/src/com/android/gallery3d/app/AlbumPicker.java
+++ b/src/com/android/gallery3d/app/AlbumPicker.java
@@ -16,12 +16,12 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.R;
-import com.android.gallery3d.data.DataManager;
-
import android.content.Intent;
import android.os.Bundle;
+import com.android.gallery3d.R;
+import com.android.gallery3d.data.DataManager;
+
public class AlbumPicker extends PickerActivity {
public static final String KEY_ALBUM_PATH = "album-path";
diff --git a/src/com/android/gallery3d/app/AlbumSetDataAdapter.java b/src/com/android/gallery3d/app/AlbumSetDataLoader.java
similarity index 84%
rename from src/com/android/gallery3d/app/AlbumSetDataAdapter.java
rename to src/com/android/gallery3d/app/AlbumSetDataLoader.java
index 5318a61..819adcc 100644
--- a/src/com/android/gallery3d/app/AlbumSetDataAdapter.java
+++ b/src/com/android/gallery3d/app/AlbumSetDataLoader.java
@@ -18,6 +18,8 @@
import android.os.Handler;
import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.ContentListener;
@@ -25,7 +27,6 @@
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.ui.AlbumSetView;
import com.android.gallery3d.ui.SynchronizedHandler;
import java.util.Arrays;
@@ -33,23 +34,26 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
-public class AlbumSetDataAdapter implements AlbumSetView.Model {
+public class AlbumSetDataLoader {
@SuppressWarnings("unused")
private static final String TAG = "AlbumSetDataAdapter";
private static final int INDEX_NONE = -1;
private static final int MIN_LOAD_COUNT = 4;
- private static final int MAX_COVER_COUNT = 1;
private static final int MSG_LOAD_START = 1;
private static final int MSG_LOAD_FINISH = 2;
private static final int MSG_RUN_OBJECT = 3;
- private static final MediaItem[] EMPTY_MEDIA_ITEMS = new MediaItem[0];
+ public static interface DataListener {
+ public void onContentChanged(int index);
+ public void onSizeChanged(int size);
+ }
private final MediaSet[] mData;
- private final MediaItem[][] mCoverData;
+ private final MediaItem[] mCoverItem;
+ private final int[] mTotalCount;
private final long[] mItemVersion;
private final long[] mSetVersion;
@@ -63,7 +67,7 @@
private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
private int mSize;
- private AlbumSetView.ModelListener mModelListener;
+ private DataListener mDataListener;
private LoadingListener mLoadingListener;
private ReloadTask mReloadTask;
@@ -71,10 +75,11 @@
private final MySourceListener mSourceListener = new MySourceListener();
- public AlbumSetDataAdapter(GalleryActivity activity, MediaSet albumSet, int cacheSize) {
+ public AlbumSetDataLoader(GalleryActivity activity, MediaSet albumSet, int cacheSize) {
mSource = Utils.checkNotNull(albumSet);
- mCoverData = new MediaItem[cacheSize][];
+ mCoverItem = new MediaItem[cacheSize];
mData = new MediaSet[cacheSize];
+ mTotalCount = new int[cacheSize];
mItemVersion = new long[cacheSize];
mSetVersion = new long[cacheSize];
Arrays.fill(mItemVersion, MediaObject.INVALID_DATA_VERSION);
@@ -110,33 +115,32 @@
mReloadTask.start();
}
- public MediaSet getMediaSet(int index) {
+ private void assertIsActive(int index) {
if (index < mActiveStart && index >= mActiveEnd) {
throw new IllegalArgumentException(String.format(
"%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
}
+ }
+
+ public MediaSet getMediaSet(int index) {
+ assertIsActive(index);
return mData[index % mData.length];
}
- public MediaItem[] getCoverItems(int index) {
- if (index < mActiveStart && index >= mActiveEnd) {
- throw new IllegalArgumentException(String.format(
- "%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
- }
- MediaItem[] result = mCoverData[index % mCoverData.length];
+ public MediaItem getCoverItem(int index) {
+ assertIsActive(index);
+ return mCoverItem[index % mCoverItem.length];
+ }
- // If the result is not ready yet, return an empty array
- return result == null ? EMPTY_MEDIA_ITEMS : result;
+ public int getTotalCount(int index) {
+ assertIsActive(index);
+ return mTotalCount[index % mTotalCount.length];
}
public int getActiveStart() {
return mActiveStart;
}
- public int getActiveEnd() {
- return mActiveEnd;
- }
-
public boolean isActive(int index) {
return index >= mActiveStart && index < mActiveEnd;
}
@@ -147,15 +151,15 @@
private void clearSlot(int slotIndex) {
mData[slotIndex] = null;
- mCoverData[slotIndex] = null;
+ mCoverItem[slotIndex] = null;
+ mTotalCount[slotIndex] = 0;
mItemVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
mSetVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
}
private void setContentWindow(int contentStart, int contentEnd) {
if (contentStart == mContentStart && contentEnd == mContentEnd) return;
- MediaItem[][] data = mCoverData;
- int length = data.length;
+ int length = mCoverItem.length;
int start = this.mContentStart;
int end = this.mContentEnd;
@@ -182,12 +186,12 @@
if (start == mActiveStart && end == mActiveEnd) return;
Utils.assertTrue(start <= end
- && end - start <= mCoverData.length && end <= mSize);
+ && end - start <= mCoverItem.length && end <= mSize);
mActiveStart = start;
mActiveEnd = end;
- int length = mCoverData.length;
+ int length = mCoverItem.length;
// If no data is visible, keep the cache content
if (start == end) return;
@@ -206,8 +210,8 @@
}
}
- public void setModelListener(AlbumSetView.ModelListener listener) {
- mModelListener = listener;
+ public void setModelListener(DataListener listener) {
+ mDataListener = listener;
}
public void setLoadingListener(LoadingListener listener) {
@@ -220,7 +224,8 @@
public int size;
public MediaSet item;
- public MediaItem covers[];
+ public MediaItem cover;
+ public int totalCount;
}
private class GetUpdateInfo implements Callable<UpdateInfo> {
@@ -268,22 +273,23 @@
mSourceVersion = info.version;
if (mSize != info.size) {
mSize = info.size;
- if (mModelListener != null) mModelListener.onSizeChanged(mSize);
+ if (mDataListener != null) mDataListener.onSizeChanged(mSize);
if (mContentEnd > mSize) mContentEnd = mSize;
if (mActiveEnd > mSize) mActiveEnd = mSize;
}
// Note: info.index could be INDEX_NONE, i.e., -1
if (info.index >= mContentStart && info.index < mContentEnd) {
- int pos = info.index % mCoverData.length;
+ int pos = info.index % mCoverItem.length;
mSetVersion[pos] = info.version;
long itemVersion = info.item.getDataVersion();
if (mItemVersion[pos] == itemVersion) return null;
mItemVersion[pos] = itemVersion;
mData[pos] = info.item;
- mCoverData[pos] = info.covers;
- if (mModelListener != null
+ mCoverItem[pos] = info.cover;
+ mTotalCount[pos] = info.totalCount;
+ if (mDataListener != null
&& info.index >= mActiveStart && info.index < mActiveEnd) {
- mModelListener.onWindowContentChanged(info.index);
+ mDataListener.onContentChanged(info.index);
}
}
return null;
@@ -317,6 +323,8 @@
@Override
public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
boolean updateComplete = false;
while (mActive) {
synchronized (this) {
@@ -331,7 +339,12 @@
long version;
synchronized (DataManager.LOCK) {
+ long start = SystemClock.uptimeMillis();
version = mSource.reload();
+ long duration = SystemClock.uptimeMillis() - start;
+ if (duration > 20) {
+ Log.v("DebugLoadingTime", "finish reload - " + duration);
+ }
}
UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
updateComplete = info == null;
@@ -353,8 +366,8 @@
if (info.index != INDEX_NONE) {
info.item = mSource.getSubMediaSet(info.index);
if (info.item == null) continue;
- MediaItem cover = info.item.getCoverMediaItem();
- info.covers = cover == null ? new MediaItem[0] : new MediaItem[] {cover};
+ info.cover = info.item.getCoverMediaItem();
+ info.totalCount = info.item.getTotalMediaItemCount();
}
}
executeAndWait(new UpdateContent(info));
diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java
index e1dcade..f8274ca 100644
--- a/src/com/android/gallery3d/app/AlbumSetPage.java
+++ b/src/com/android/gallery3d/app/AlbumSetPage.java
@@ -21,6 +21,8 @@
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
import android.os.Vibrator;
import android.provider.MediaStore;
import android.view.ActionMode;
@@ -40,19 +42,16 @@
import com.android.gallery3d.settings.GallerySettings;
import com.android.gallery3d.ui.ActionModeHandler;
import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
-import com.android.gallery3d.ui.AlbumSetView;
+import com.android.gallery3d.ui.AlbumSetSlotRenderer;
import com.android.gallery3d.ui.DetailsHelper;
import com.android.gallery3d.ui.DetailsHelper.CloseListener;
+import com.android.gallery3d.ui.FadeTexture;
import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.ui.GLView;
-import com.android.gallery3d.ui.GridDrawer;
-import com.android.gallery3d.ui.HighlightDrawer;
-import com.android.gallery3d.ui.PositionProvider;
-import com.android.gallery3d.ui.PositionRepository;
-import com.android.gallery3d.ui.PositionRepository.Position;
import com.android.gallery3d.ui.SelectionManager;
import com.android.gallery3d.ui.SlotView;
-import com.android.gallery3d.ui.StaticBackground;
+import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.GalleryUtils;
@@ -62,6 +61,8 @@
@SuppressWarnings("unused")
private static final String TAG = "AlbumSetPage";
+ private static final int MSG_PICK_ALBUM = 1;
+
public static final String KEY_MEDIA_PATH = "media-path";
public static final String KEY_SET_TITLE = "set-title";
public static final String KEY_SET_SUBTITLE = "set-subtitle";
@@ -74,20 +75,19 @@
private static final int BIT_LOADING_SYNC = 2;
private boolean mIsActive = false;
- private StaticBackground mStaticBackground;
- private AlbumSetView mAlbumSetView;
+ private SlotView mSlotView;
+ private AlbumSetSlotRenderer mAlbumSetView;
private MediaSet mMediaSet;
private String mTitle;
private String mSubtitle;
private boolean mShowClusterMenu;
+ private GalleryActionBar mActionBar;
private int mSelectedAction;
private Vibrator mVibrator;
protected SelectionManager mSelectionManager;
- private AlbumSetDataAdapter mAlbumSetDataAdapter;
- private GridDrawer mGridDrawer;
- private HighlightDrawer mHighlightDrawer;
+ private AlbumSetDataLoader mAlbumSetDataAdapter;
private boolean mGetContent;
private boolean mGetAlbum;
@@ -97,6 +97,7 @@
private MyDetailsSource mDetailsSource;
private boolean mShowDetails;
private EyePosition mEyePosition;
+ private Handler mHandler;
// The eyes' position of the user, the origin is at the center of the
// device and the unit is in pixels.
@@ -113,24 +114,26 @@
private final float mMatrix[] = new float[16];
@Override
+ protected void renderBackground(GLCanvas view) {
+ view.clearBuffer();
+ }
+
+ @Override
protected void onLayout(
boolean changed, int left, int top, int right, int bottom) {
- mStaticBackground.layout(0, 0, right - left, bottom - top);
mEyePosition.resetPosition();
- int slotViewTop = GalleryActionBar.getHeight((Activity) mActivity);
+ int slotViewTop = mActionBar.getHeight();
int slotViewBottom = bottom - top;
int slotViewRight = right - left;
if (mShowDetails) {
mDetailsHelper.layout(left, slotViewTop, right, bottom);
} else {
- mAlbumSetView.setSelectionDrawer(mGridDrawer);
+ mAlbumSetView.setHighlightItemPath(null);
}
- mAlbumSetView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
- PositionRepository.getInstance(mActivity).setOffset(
- 0, slotViewTop);
+ mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
}
@Override
@@ -161,115 +164,104 @@
} else if (mSelectionManager.inSelectionMode()) {
mSelectionManager.leaveSelectionMode();
} else {
- mAlbumSetView.savePositions(
- PositionRepository.getInstance(mActivity));
super.onBackPressed();
}
}
- private void savePositions(int slotIndex, int center[]) {
+ private void getSlotCenter(int slotIndex, int center[]) {
Rect offset = new Rect();
- mRootPane.getBoundsOf(mAlbumSetView, offset);
- mAlbumSetView.savePositions(PositionRepository.getInstance(mActivity));
- Rect r = mAlbumSetView.getSlotRect(slotIndex);
- int scrollX = mAlbumSetView.getScrollX();
- int scrollY = mAlbumSetView.getScrollY();
+ mRootPane.getBoundsOf(mSlotView, offset);
+ Rect r = mSlotView.getSlotRect(slotIndex);
+ int scrollX = mSlotView.getScrollX();
+ int scrollY = mSlotView.getScrollY();
center[0] = offset.left + (r.left + r.right) / 2 - scrollX;
center[1] = offset.top + (r.top + r.bottom) / 2 - scrollY;
}
public void onSingleTapUp(int slotIndex) {
+ if (!mIsActive) return;
+
+ if (mSelectionManager.inSelectionMode()) {
+ MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex);
+ if (targetSet == null) return; // Content is dirty, we shall reload soon
+ mSelectionManager.toggle(targetSet.getPath());
+ mSlotView.invalidate();
+ } else {
+ // Show pressed-up animation for the single-tap.
+ mAlbumSetView.setPressedIndex(slotIndex);
+ mAlbumSetView.setPressedUp();
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PICK_ALBUM, slotIndex, 0),
+ FadeTexture.DURATION);
+ }
+ }
+
+ private void pickAlbum(int slotIndex) {
+ if (!mIsActive) return;
+
MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex);
if (targetSet == null) return; // Content is dirty, we shall reload soon
+ String mediaPath = targetSet.getPath().toString();
- if (mShowDetails) {
- Path path = targetSet.getPath();
- mHighlightDrawer.setHighlightItem(path);
- mDetailsHelper.reloadDetails(slotIndex);
- } else if (!mSelectionManager.inSelectionMode()) {
- Bundle data = new Bundle(getData());
- String mediaPath = targetSet.getPath().toString();
- int[] center = new int[2];
- savePositions(slotIndex, center);
- data.putIntArray(AlbumPage.KEY_SET_CENTER, center);
- if (mGetAlbum && targetSet.isLeafAlbum()) {
- Activity activity = (Activity) mActivity;
- Intent result = new Intent()
- .putExtra(AlbumPicker.KEY_ALBUM_PATH, targetSet.getPath().toString());
- activity.setResult(Activity.RESULT_OK, result);
- activity.finish();
- } else if (targetSet.getSubMediaSetCount() > 0) {
- data.putString(AlbumSetPage.KEY_MEDIA_PATH, mediaPath);
- mActivity.getStateManager().startStateForResult(
- AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
- } else {
- if (!mGetContent && (targetSet.getSupportedOperations()
- & MediaObject.SUPPORT_IMPORT) != 0) {
- data.putBoolean(AlbumPage.KEY_AUTO_SELECT_ALL, true);
- }
- data.putString(AlbumPage.KEY_MEDIA_PATH, mediaPath);
- boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
- // We only show cluster menu in the first AlbumPage in stack
- data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum);
- mActivity.getStateManager().startStateForResult(
- AlbumPage.class, REQUEST_DO_ANIMATION, data);
- }
+ Bundle data = new Bundle(getData());
+ int[] center = new int[2];
+ getSlotCenter(slotIndex, center);
+ data.putIntArray(AlbumPage.KEY_SET_CENTER, center);
+ if (mGetAlbum && targetSet.isLeafAlbum()) {
+ Activity activity = (Activity) mActivity;
+ Intent result = new Intent()
+ .putExtra(AlbumPicker.KEY_ALBUM_PATH, targetSet.getPath().toString());
+ activity.setResult(Activity.RESULT_OK, result);
+ activity.finish();
+ } else if (targetSet.getSubMediaSetCount() > 0) {
+ data.putString(AlbumSetPage.KEY_MEDIA_PATH, mediaPath);
+ mActivity.getStateManager().startStateForResult(
+ AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
} else {
- mSelectionManager.toggle(targetSet.getPath());
- mAlbumSetView.invalidate();
+ if (!mGetContent && (targetSet.getSupportedOperations()
+ & MediaObject.SUPPORT_IMPORT) != 0) {
+ data.putBoolean(AlbumPage.KEY_AUTO_SELECT_ALL, true);
+ }
+ data.putString(AlbumPage.KEY_MEDIA_PATH, mediaPath);
+ boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
+ // We only show cluster menu in the first AlbumPage in stack
+ data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum);
+ mActivity.getStateManager().startStateForResult(
+ AlbumPage.class, REQUEST_DO_ANIMATION, data);
}
}
private void onDown(int index) {
- MediaSet set = mAlbumSetDataAdapter.getMediaSet(index);
- Path path = (set == null) ? null : set.getPath();
- mSelectionManager.setPressedPath(path);
- mAlbumSetView.invalidate();
+ mAlbumSetView.setPressedIndex(index);
}
- private void onUp() {
- mSelectionManager.setPressedPath(null);
- mAlbumSetView.invalidate();
+ private void onUp(boolean followedByLongPress) {
+ if (followedByLongPress) {
+ // Avoid showing press-up animations for long-press.
+ mAlbumSetView.setPressedIndex(-1);
+ } else {
+ mAlbumSetView.setPressedUp();
+ }
}
public void onLongTap(int slotIndex) {
if (mGetContent || mGetAlbum) return;
- if (mShowDetails) {
- onSingleTapUp(slotIndex);
- } else {
- MediaSet set = mAlbumSetDataAdapter.getMediaSet(slotIndex);
- if (set == null) return;
- mSelectionManager.setAutoLeaveSelectionMode(true);
- mSelectionManager.toggle(set.getPath());
- mDetailsSource.findIndex(slotIndex);
- mAlbumSetView.invalidate();
- }
+ MediaSet set = mAlbumSetDataAdapter.getMediaSet(slotIndex);
+ if (set == null) return;
+ mSelectionManager.setAutoLeaveSelectionMode(true);
+ mSelectionManager.toggle(set.getPath());
+ mSlotView.invalidate();
}
+ @Override
public void doCluster(int clusterType) {
String basePath = mMediaSet.getPath().toString();
String newPath = FilterUtils.switchClusterPath(basePath, clusterType);
Bundle data = new Bundle(getData());
data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
data.putInt(KEY_SELECTED_CLUSTER_TYPE, clusterType);
- mAlbumSetView.savePositions(PositionRepository.getInstance(mActivity));
mActivity.getStateManager().switchState(this, AlbumSetPage.class, data);
}
- public void doFilter(int filterType) {
- String basePath = mMediaSet.getPath().toString();
- String newPath = FilterUtils.switchFilterPath(basePath, filterType);
- Bundle data = new Bundle(getData());
- data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
- mAlbumSetView.savePositions(PositionRepository.getInstance(mActivity));
- mActivity.getStateManager().switchState(this, AlbumSetPage.class, data);
- }
-
- public void onOperationComplete() {
- mAlbumSetView.invalidate();
- // TODO: enable animation
- }
-
@Override
public void onCreate(Bundle data, Bundle restoreState) {
initializeViews();
@@ -282,17 +274,27 @@
mEyePosition = new EyePosition(context, this);
mDetailsSource = new MyDetailsSource();
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
- GalleryActionBar actionBar = mActivity.getGalleryActionBar();
- if (actionBar != null) {
- mSelectedAction = data.getInt(
- AlbumSetPage.KEY_SELECTED_CLUSTER_TYPE, FilterUtils.CLUSTER_BY_ALBUM);
- }
- startTransition();
+ mActionBar = mActivity.getGalleryActionBar();
+ mSelectedAction = data.getInt(AlbumSetPage.KEY_SELECTED_CLUSTER_TYPE,
+ FilterUtils.CLUSTER_BY_ALBUM);
+
+ mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_PICK_ALBUM: {
+ pickAlbum(message.arg1);
+ break;
+ }
+ default: throw new AssertionError(message.what);
+ }
+ }
+ };
}
private void clearLoadingBit(int loadingBit) {
mLoadingBits &= ~loadingBit;
- if (mLoadingBits == 0) {
+ if (mLoadingBits == 0 && mIsActive) {
GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
// Only show toast when there's no album and we are going to finish
@@ -308,7 +310,7 @@
}
private void setLoadingBit(int loadingBit) {
- if (mLoadingBits == 0) {
+ if (mLoadingBits == 0 && mIsActive) {
GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
}
mLoadingBits |= loadingBit;
@@ -323,13 +325,16 @@
mAlbumSetView.pause();
mEyePosition.pause();
DetailsHelper.pause();
- GalleryActionBar actionBar = mActivity.getGalleryActionBar();
- if (actionBar != null) actionBar.hideClusterMenu();
+ // Call disableClusterMenu to avoid receiving callback after paused.
+ // Don't hide menu here otherwise the list menu will disappear earlier than
+ // the action bar, which is janky and unwanted behavior.
+ mActionBar.disableClusterMenu(false);
if (mSyncTask != null) {
mSyncTask.cancel();
mSyncTask = null;
clearLoadingBit(BIT_LOADING_SYNC);
}
+ GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
}
@Override
@@ -345,9 +350,8 @@
mAlbumSetView.resume();
mEyePosition.resume();
mActionModeHandler.resume();
- GalleryActionBar actionBar = mActivity.getGalleryActionBar();
- if (mShowClusterMenu && actionBar != null) {
- actionBar.showClusterMenu(mSelectedAction, this);
+ if (mShowClusterMenu) {
+ mActionBar.enableClusterMenu(mSelectedAction, this);
}
if (!mInitialSynced) {
setLoadingBit(BIT_LOADING_SYNC);
@@ -359,7 +363,7 @@
String mediaPath = data.getString(AlbumSetPage.KEY_MEDIA_PATH);
mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
mSelectionManager.setSourceMediaSet(mMediaSet);
- mAlbumSetDataAdapter = new AlbumSetDataAdapter(
+ mAlbumSetDataAdapter = new AlbumSetDataLoader(
mActivity, mMediaSet, DATA_CACHE_SIZE);
mAlbumSetDataAdapter.setLoadingListener(new MyLoadingListener());
mAlbumSetView.setModel(mAlbumSetDataAdapter);
@@ -368,22 +372,21 @@
private void initializeViews() {
mSelectionManager = new SelectionManager(mActivity, true);
mSelectionManager.setSelectionListener(this);
- mStaticBackground = new StaticBackground(mActivity.getAndroidContext());
- mRootPane.addComponent(mStaticBackground);
- mGridDrawer = new GridDrawer((Context) mActivity, mSelectionManager);
Config.AlbumSetPage config = Config.AlbumSetPage.get((Context) mActivity);
- mAlbumSetView = new AlbumSetView(mActivity, mGridDrawer,
- config.slotViewSpec, config.labelSpec);
- mAlbumSetView.setListener(new SlotView.SimpleListener() {
+ mSlotView = new SlotView(mActivity, config.slotViewSpec);
+ mAlbumSetView = new AlbumSetSlotRenderer(
+ mActivity, mSelectionManager, mSlotView, config.labelSpec);
+ mSlotView.setSlotRenderer(mAlbumSetView);
+ mSlotView.setListener(new SlotView.SimpleListener() {
@Override
public void onDown(int index) {
AlbumSetPage.this.onDown(index);
}
@Override
- public void onUp() {
- AlbumSetPage.this.onUp();
+ public void onUp(boolean followedByLongPress) {
+ AlbumSetPage.this.onUp(followedByLongPress);
}
@Override
@@ -399,20 +402,17 @@
mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
mActionModeHandler.setActionModeListener(new ActionModeListener() {
+ @Override
public boolean onActionItemClicked(MenuItem item) {
return onItemSelected(item);
}
});
- mRootPane.addComponent(mAlbumSetView);
-
- mStaticBackground.setImage(R.drawable.background,
- R.drawable.background_portrait);
+ mRootPane.addComponent(mSlotView);
}
@Override
protected boolean onCreateActionBar(Menu menu) {
Activity activity = (Activity) mActivity;
- GalleryActionBar actionBar = mActivity.getGalleryActionBar();
MenuInflater inflater = activity.getMenuInflater();
final boolean inAlbum = mActivity.getStateManager().hasStateClass(
@@ -428,10 +428,10 @@
? R.string.select_video
: R.string.select_item;
}
- actionBar.setTitle(id);
+ mActionBar.setTitle(id);
} else if (mGetAlbum) {
inflater.inflate(R.menu.pickup, menu);
- actionBar.setTitle(R.string.select_album);
+ mActionBar.setTitle(R.string.select_album);
} else {
mShowClusterMenu = !inAlbum;
inflater.inflate(R.menu.albumset, menu);
@@ -439,7 +439,7 @@
if (selectItem != null) {
boolean selectAlbums = !inAlbum &&
- actionBar.getClusterTypeAction() == FilterUtils.CLUSTER_BY_ALBUM;
+ mActionBar.getClusterTypeAction() == FilterUtils.CLUSTER_BY_ALBUM;
if (selectAlbums) {
selectItem.setTitle(R.string.select_album);
} else {
@@ -447,14 +447,14 @@
}
}
- FilterUtils.setupMenuItems(actionBar, mMediaSet.getPath(), false);
+ FilterUtils.setupMenuItems(mActionBar, mMediaSet.getPath(), false);
MenuItem switchCamera = menu.findItem(R.id.action_camera);
if (switchCamera != null) {
switchCamera.setVisible(GalleryUtils.isCameraAvailable(activity));
}
- actionBar.setTitle(mTitle);
- actionBar.setSubtitle(mSubtitle);
+ mActionBar.setTitle(mTitle);
+ mActionBar.setSubtitle(mSubtitle);
}
return true;
}
@@ -507,6 +507,11 @@
activity.startActivity(new Intent(activity, GallerySettings.class));
return true;
}
+ case R.id.action_general_help: {
+ activity.startActivity(
+ GalleryUtils.getHelpIntent(R.string.general_help_link, activity));
+ return true;
+ }
default:
return false;
}
@@ -516,31 +521,14 @@
protected void onStateResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_DO_ANIMATION: {
- startTransition();
+ mSlotView.startRisingAnimation();
}
}
}
- private void startTransition() {
- final PositionRepository repository =
- PositionRepository.getInstance(mActivity);
- mAlbumSetView.startTransition(new PositionProvider() {
- private final Position mTempPosition = new Position();
- public Position getPosition(long identity, Position target) {
- Position p = repository.get(identity);
- if (p == null) {
- p = mTempPosition;
- p.set(target.x, target.y, 128, target.theta, 1);
- }
- return p;
- }
- });
- }
-
private String getSelectedString() {
- GalleryActionBar actionBar = mActivity.getGalleryActionBar();
int count = mSelectionManager.getSelectedCount();
- int action = actionBar.getClusterTypeAction();
+ int action = mActionBar.getClusterTypeAction();
int string = action == FilterUtils.CLUSTER_BY_ALBUM
? R.plurals.number_of_albums_selected
: R.plurals.number_of_groups_selected;
@@ -548,11 +536,11 @@
return String.format(format, count);
}
+ @Override
public void onSelectionModeChange(int mode) {
-
switch (mode) {
case SelectionManager.ENTER_SELECTION_MODE: {
- mActivity.getGalleryActionBar().hideClusterMenu();
+ mActionBar.disableClusterMenu(true);
mActionMode = mActionModeHandler.startActionMode();
mVibrator.vibrate(100);
break;
@@ -560,7 +548,7 @@
case SelectionManager.LEAVE_SELECTION_MODE: {
mActionMode.finish();
if (mShowClusterMenu) {
- mActivity.getGalleryActionBar().showClusterMenu(mSelectedAction, this);
+ mActionBar.enableClusterMenu(mSelectedAction, this);
}
mRootPane.invalidate();
break;
@@ -573,6 +561,7 @@
}
}
+ @Override
public void onSelectionChange(Path path, boolean selected) {
Utils.assertTrue(mActionMode != null);
mActionModeHandler.setTitle(getSelectedString());
@@ -582,23 +571,21 @@
private void hideDetails() {
mShowDetails = false;
mDetailsHelper.hide();
- mAlbumSetView.setSelectionDrawer(mGridDrawer);
- mAlbumSetView.invalidate();
+ mAlbumSetView.setHighlightItemPath(null);
+ mSlotView.invalidate();
}
private void showDetails() {
mShowDetails = true;
if (mDetailsHelper == null) {
- mHighlightDrawer = new HighlightDrawer(mActivity.getAndroidContext(),
- mSelectionManager);
mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
mDetailsHelper.setCloseListener(new CloseListener() {
+ @Override
public void onClose() {
hideDetails();
}
});
}
- mAlbumSetView.setSelectionDrawer(mHighlightDrawer);
mDetailsHelper.show();
}
@@ -611,42 +598,52 @@
((Activity) mActivity).runOnUiThread(new Runnable() {
@Override
public void run() {
- if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
- mInitialSynced = true;
- }
- if (!mIsActive) return;
- clearLoadingBit(BIT_LOADING_SYNC);
- if (resultCode == MediaSet.SYNC_RESULT_ERROR) {
- Toast.makeText((Context) mActivity, R.string.sync_album_set_error,
- Toast.LENGTH_LONG).show();
+ GLRoot root = mActivity.getGLRoot();
+ root.lockRenderThread();
+ try {
+ if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
+ mInitialSynced = true;
+ }
+ clearLoadingBit(BIT_LOADING_SYNC);
+ if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
+ Toast.makeText((Context) mActivity, R.string.sync_album_set_error,
+ Toast.LENGTH_LONG).show();
+ }
+ } finally {
+ root.unlockRenderThread();
}
}
});
}
private class MyLoadingListener implements LoadingListener {
+ @Override
public void onLoadingStarted() {
setLoadingBit(BIT_LOADING_RELOAD);
}
+ @Override
public void onLoadingFinished() {
- if (!mIsActive) return;
clearLoadingBit(BIT_LOADING_RELOAD);
}
}
private class MyDetailsSource implements DetailsHelper.DetailsSource {
private int mIndex;
+
+ @Override
public int size() {
return mAlbumSetDataAdapter.size();
}
+ @Override
public int getIndex() {
return mIndex;
}
// If requested index is out of active window, suggest a valid index.
// If there is no valid index available, return -1.
+ @Override
public int findIndex(int indexHint) {
if (mAlbumSetDataAdapter.isActive(indexHint)) {
mIndex = indexHint;
@@ -659,10 +656,11 @@
return mIndex;
}
+ @Override
public MediaDetails getDetails() {
MediaObject item = mAlbumSetDataAdapter.getMediaSet(mIndex);
if (item != null) {
- mHighlightDrawer.setHighlightItem(item.getPath());
+ mAlbumSetView.setHighlightItemPath(item.getPath());
return item.getDetails();
} else {
return null;
diff --git a/src/com/android/gallery3d/app/CameraScreenNail.java b/src/com/android/gallery3d/app/CameraScreenNail.java
new file mode 100644
index 0000000..24857a4
--- /dev/null
+++ b/src/com/android/gallery3d/app/CameraScreenNail.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.app;
+
+import android.app.Activity;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.ui.ScreenNail;
+import com.android.gallery3d.ui.ScreenNailHolder;
+import com.android.gallery3d.ui.SurfaceTextureScreenNail;
+
+// This is a ScreenNail which displays camera preview. This demos the usage of
+// SurfaceTextureScreenNail. It is not intended for production use.
+class CameraScreenNail extends SurfaceTextureScreenNail {
+ private static final String TAG = "CameraScreenNail";
+ private static final int CAMERA_ID = 0;
+ private static final int PREVIEW_WIDTH = 960;
+ private static final int PREVIEW_HEIGHT = 720;
+ private static final int MSG_START_CAMERA = 1;
+ private static final int MSG_STOP_CAMERA = 2;
+
+ public interface Listener {
+ void requestRender();
+ }
+
+ private Activity mActivity;
+ private Listener mListener;
+ private int mOrientation;
+ private Camera mCamera;
+
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private volatile boolean mVisible;
+ private volatile boolean mHasFrame;
+
+ public CameraScreenNail(Activity activity, Listener listener) {
+ mActivity = activity;
+ mListener = listener;
+
+ mOrientation = getCameraDisplayOrientation(mActivity, CAMERA_ID);
+ if (mOrientation % 180 == 0) {
+ setSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);
+ } else {
+ setSize(PREVIEW_HEIGHT, PREVIEW_WIDTH);
+ }
+
+ mHandlerThread = new HandlerThread("Camera");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper()) {
+ public void handleMessage(Message message) {
+ if (message.what == MSG_START_CAMERA && mCamera == null) {
+ startCamera();
+ } else if (message.what == MSG_STOP_CAMERA && mCamera != null) {
+ stopCamera();
+ }
+ }
+ };
+ mHandler.sendEmptyMessage(MSG_START_CAMERA);
+ }
+
+ private void startCamera() {
+ try {
+ acquireSurfaceTexture();
+ Camera camera = Camera.open(CAMERA_ID);
+ Camera.Parameters param = camera.getParameters();
+ param.setPreviewSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);
+ camera.setParameters(param);
+ camera.setDisplayOrientation(mOrientation);
+ camera.setPreviewTexture(getSurfaceTexture());
+ camera.startPreview();
+ synchronized (this) {
+ mCamera = camera;
+ }
+ } catch (Throwable th) {
+ Log.e(TAG, "cannot open camera", th);
+ }
+ }
+
+ private void stopCamera() {
+ releaseSurfaceTexture();
+ mCamera.stopPreview();
+ mCamera.release();
+ synchronized (this) {
+ mCamera = null;
+ notifyAll();
+ }
+ mHasFrame = false;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int width, int height) {
+ if (!mVisible) {
+ mVisible = true;
+ // Only send one message when mVisible makes transition from
+ // false to true.
+ mHandler.sendEmptyMessage(MSG_START_CAMERA);
+ }
+
+ if (mVisible && mHasFrame) {
+ super.draw(canvas, x, y, width, height);
+ }
+ }
+
+ @Override
+ public void noDraw() {
+ mVisible = false;
+ }
+
+ @Override
+ public void recycle() {
+ mVisible = false;
+ }
+
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ mHasFrame = true;
+ if (mVisible) {
+ // We need to ask for re-render if the SurfaceTexture receives a new
+ // frame (and we are visible).
+ mListener.requestRender();
+ }
+ }
+
+ public void destroy() {
+ synchronized (this) {
+ mHandler.sendEmptyMessage(MSG_STOP_CAMERA);
+
+ // Wait until camera is closed.
+ while (mCamera != null) {
+ try {
+ wait();
+ } catch (Exception ex) {
+ // ignore.
+ }
+ }
+ }
+ mHandlerThread.quit();
+ }
+
+ // The three methods below are copied from Camera.java
+ private static int getCameraDisplayOrientation(
+ Activity activity, int cameraId) {
+ int displayRotation = getDisplayRotation(activity);
+ int displayOrientation = getDisplayOrientation(
+ displayRotation, cameraId);
+ return displayOrientation;
+ }
+
+ private static int getDisplayRotation(Activity activity) {
+ int rotation = activity.getWindowManager().getDefaultDisplay()
+ .getRotation();
+ switch (rotation) {
+ case Surface.ROTATION_0: return 0;
+ case Surface.ROTATION_90: return 90;
+ case Surface.ROTATION_180: return 180;
+ case Surface.ROTATION_270: return 270;
+ }
+ return 0;
+ }
+
+ private static int getDisplayOrientation(int degrees, int cameraId) {
+ // See android.hardware.Camera.setDisplayOrientation for
+ // documentation.
+ Camera.CameraInfo info = new Camera.CameraInfo();
+ Camera.getCameraInfo(cameraId, info);
+ int result;
+ if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ result = (info.orientation + degrees) % 360;
+ result = (360 - result) % 360; // compensate the mirror
+ } else { // back-facing
+ result = (info.orientation - degrees + 360) % 360;
+ }
+ return result;
+ }
+}
+
+// This holds a CameraScreenNail, so we can pass it to a PhotoPage.
+class CameraScreenNailHolder extends ScreenNailHolder
+ implements CameraScreenNail.Listener {
+ private static final String TAG = "CameraScreenNailHolder";
+ private GalleryActivity mActivity;
+ private CameraScreenNail mCameraScreenNail;
+
+ public CameraScreenNailHolder(GalleryActivity activity) {
+ mActivity = activity;
+ }
+
+ @Override
+ public void requestRender() {
+ mActivity.getGLRoot().requestRender();
+ }
+
+ @Override
+ public ScreenNail attach() {
+ mCameraScreenNail = new CameraScreenNail((Activity) mActivity, this);
+ return mCameraScreenNail;
+ }
+
+ @Override
+ public void detach() {
+ mCameraScreenNail.destroy();
+ mCameraScreenNail = null;
+ }
+}
diff --git a/src/com/android/gallery3d/app/Config.java b/src/com/android/gallery3d/app/Config.java
index 914ea55..a95427b 100644
--- a/src/com/android/gallery3d/app/Config.java
+++ b/src/com/android/gallery3d/app/Config.java
@@ -16,19 +16,19 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.R;
-import com.android.gallery3d.ui.SlotView;
-import com.android.gallery3d.ui.AlbumSetView;
-
import android.content.Context;
import android.content.res.Resources;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ui.AlbumSetSlotRenderer;
+import com.android.gallery3d.ui.SlotView;
+
final class Config {
public static class AlbumSetPage {
private static AlbumSetPage sInstance;
public SlotView.Spec slotViewSpec;
- public AlbumSetView.LabelSpec labelSpec;
+ public AlbumSetSlotRenderer.LabelSpec labelSpec;
public static synchronized AlbumSetPage get(Context context) {
if (sInstance == null) {
@@ -45,7 +45,7 @@
slotViewSpec.rowsPort = r.getInteger(R.integer.albumset_rows_port);
slotViewSpec.slotGap = r.getDimensionPixelSize(R.dimen.albumset_slot_gap);
- labelSpec = new AlbumSetView.LabelSpec();
+ labelSpec = new AlbumSetSlotRenderer.LabelSpec();
labelSpec.labelBackgroundHeight = r.getDimensionPixelSize(
R.dimen.albumset_label_background_height);
labelSpec.titleOffset = r.getDimensionPixelSize(
@@ -105,41 +105,5 @@
cachePinMargin = r.getDimensionPixelSize(R.dimen.cache_pin_margin);
}
}
-
- public static class PhotoPage {
- private static PhotoPage sInstance;
-
- // These are all height values. See the comment in FilmStripView for
- // the meaning of these values.
- public final int filmstripTopMargin;
- public final int filmstripMidMargin;
- public final int filmstripBottomMargin;
- public final int filmstripThumbSize;
- public final int filmstripContentSize;
- public final int filmstripGripSize;
- public final int filmstripBarSize;
-
- // These are width values.
- public final int filmstripGripWidth;
-
- public static synchronized PhotoPage get(Context context) {
- if (sInstance == null) {
- sInstance = new PhotoPage(context);
- }
- return sInstance;
- }
-
- public PhotoPage(Context context) {
- Resources r = context.getResources();
- filmstripTopMargin = r.getDimensionPixelSize(R.dimen.filmstrip_top_margin);
- filmstripMidMargin = r.getDimensionPixelSize(R.dimen.filmstrip_mid_margin);
- filmstripBottomMargin = r.getDimensionPixelSize(R.dimen.filmstrip_bottom_margin);
- filmstripThumbSize = r.getDimensionPixelSize(R.dimen.filmstrip_thumb_size);
- filmstripContentSize = r.getDimensionPixelSize(R.dimen.filmstrip_content_size);
- filmstripGripSize = r.getDimensionPixelSize(R.dimen.filmstrip_grip_size);
- filmstripBarSize = r.getDimensionPixelSize(R.dimen.filmstrip_bar_size);
- filmstripGripWidth = r.getDimensionPixelSize(R.dimen.filmstrip_grip_width);
- }
- }
}
diff --git a/src/com/android/gallery3d/app/ControllerOverlay.java b/src/com/android/gallery3d/app/ControllerOverlay.java
index 847d9e6..0347213 100644
--- a/src/com/android/gallery3d/app/ControllerOverlay.java
+++ b/src/com/android/gallery3d/app/ControllerOverlay.java
@@ -51,10 +51,5 @@
void showErrorMessage(String message);
- void hide();
-
void setTimes(int currentTime, int totalTime);
-
- void resetTime();
-
}
diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java
index 997f1fd..7e1572f 100644
--- a/src/com/android/gallery3d/app/CropImage.java
+++ b/src/com/android/gallery3d/app/CropImage.java
@@ -38,6 +38,7 @@
import android.os.Message;
import android.provider.MediaStore;
import android.provider.MediaStore.Images;
+import android.util.FloatMath;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
@@ -554,8 +555,7 @@
}
if (outputX * outputY > MAX_PIXEL_COUNT) {
- float scale = (float) Math.sqrt(
- (double) MAX_PIXEL_COUNT / outputX / outputY);
+ float scale = FloatMath.sqrt((float) MAX_PIXEL_COUNT / outputX / outputY);
Log.w(TAG, "scale down the cropped image: " + scale);
outputX = Math.round(scale * outputX);
outputY = Math.round(scale * outputY);
@@ -708,7 +708,7 @@
BitmapRegionDecoder regionDecoder) {
if (regionDecoder == null) {
- Toast.makeText(this, "fail to load image", Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show();
finish();
return;
}
@@ -734,7 +734,7 @@
private void onBitmapAvailable(Bitmap bitmap) {
if (bitmap == null) {
- Toast.makeText(this, "fail to load image", Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show();
finish();
return;
}
diff --git a/src/com/android/gallery3d/app/DialogPicker.java b/src/com/android/gallery3d/app/DialogPicker.java
index 8a57824..7ca86e5 100644
--- a/src/com/android/gallery3d/app/DialogPicker.java
+++ b/src/com/android/gallery3d/app/DialogPicker.java
@@ -16,12 +16,11 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.R;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.content.Intent;
import android.os.Bundle;
+import com.android.gallery3d.util.GalleryUtils;
+
public class DialogPicker extends PickerActivity {
@Override
diff --git a/src/com/android/gallery3d/app/EyePosition.java b/src/com/android/gallery3d/app/EyePosition.java
index 7b4495a..89e0846 100644
--- a/src/com/android/gallery3d/app/EyePosition.java
+++ b/src/com/android/gallery3d/app/EyePosition.java
@@ -16,19 +16,20 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.SystemClock;
+import android.util.FloatMath;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.GalleryUtils;
+
public class EyePosition {
private static final String TAG = "EyePosition";
@@ -41,9 +42,9 @@
private static final int GYROSCOPE_SETTLE_DOWN = 15;
private static final float GYROSCOPE_RESTORE_FACTOR = 0.995f;
- private static final double USER_ANGEL = Math.toRadians(10);
- private static final float USER_ANGEL_COS = (float) Math.cos(USER_ANGEL);
- private static final float USER_ANGEL_SIN = (float) Math.sin(USER_ANGEL);
+ private static final float USER_ANGEL = (float) Math.toRadians(10);
+ private static final float USER_ANGEL_COS = FloatMath.cos(USER_ANGEL);
+ private static final float USER_ANGEL_SIN = FloatMath.sin(USER_ANGEL);
private static final float MAX_VIEW_RANGE = (float) 0.5;
private static final int NOT_STARTED = -1;
@@ -126,8 +127,8 @@
float ty = -1 + t * y;
float tz = t * z;
- float length = (float) Math.sqrt(tx * tx + ty * ty + tz * tz);
- float glength = (float) Math.sqrt(temp);
+ float length = FloatMath.sqrt(tx * tx + ty * ty + tz * tz);
+ float glength = FloatMath.sqrt(temp);
mX = Utils.clamp((x * USER_ANGEL_COS / glength
+ tx * USER_ANGEL_SIN / length) * mUserDistance,
@@ -135,7 +136,7 @@
mY = -Utils.clamp((y * USER_ANGEL_COS / glength
+ ty * USER_ANGEL_SIN / length) * mUserDistance,
-mLimit, mLimit);
- mZ = (float) -Math.sqrt(
+ mZ = -FloatMath.sqrt(
mUserDistance * mUserDistance - mX * mX - mY * mY);
mListener.onEyePositionChanged(mX, mY, mZ);
}
@@ -173,7 +174,7 @@
mY = Utils.clamp((float) (mY + y * t / Math.hypot(mZ, mY)),
-mLimit, mLimit) * GYROSCOPE_RESTORE_FACTOR;
- mZ = (float) -Math.sqrt(
+ mZ = -FloatMath.sqrt(
mUserDistance * mUserDistance - mX * mX - mY * mY);
mListener.onEyePositionChanged(mX, mY, mZ);
}
diff --git a/src/com/android/gallery3d/app/FilterUtils.java b/src/com/android/gallery3d/app/FilterUtils.java
index 9b8ea2d..c15457e 100644
--- a/src/com/android/gallery3d/app/FilterUtils.java
+++ b/src/com/android/gallery3d/app/FilterUtils.java
@@ -214,11 +214,6 @@
return "/cluster/{" + base + "}/" + kind;
}
- // Change the topmost filter to the specified type.
- public static String switchFilterPath(String base, int filterType) {
- return newFilterPath(removeOneFilterFromPath(base), filterType);
- }
-
// Change the topmost clustering to the specified type.
public static String switchClusterPath(String base, int clusterType) {
return newClusterPath(removeOneClusterFromPath(base), clusterType);
@@ -258,39 +253,4 @@
}
return sb.toString();
}
-
- // Remove the topmost filter (if any) from the path.
- private static String removeOneFilterFromPath(String base) {
- boolean[] done = new boolean[1];
- return removeOneFilterFromPath(base, done);
- }
-
- private static String removeOneFilterFromPath(String base, boolean[] done) {
- if (done[0]) return base;
-
- String[] segments = Path.split(base);
- if (segments[0].equals("filter") && segments[1].equals("mediatype")) {
- done[0] = true;
- return Path.splitSequence(segments[3])[0];
- }
-
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < segments.length; i++) {
- sb.append("/");
- if (segments[i].startsWith("{")) {
- sb.append("{");
- String[] sets = Path.splitSequence(segments[i]);
- for (int j = 0; j < sets.length; j++) {
- if (j > 0) {
- sb.append(",");
- }
- sb.append(removeOneFilterFromPath(sets[j], done));
- }
- sb.append("}");
- } else {
- sb.append(segments[i]);
- }
- }
- return sb.toString();
- }
}
diff --git a/src/com/android/gallery3d/app/Gallery.java b/src/com/android/gallery3d/app/Gallery.java
index 253af2b..c8fbd53 100644
--- a/src/com/android/gallery3d/app/Gallery.java
+++ b/src/com/android/gallery3d/app/Gallery.java
@@ -17,12 +17,15 @@
package com.android.gallery3d.app;
import android.app.Dialog;
+import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
+import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.OpenableColumns;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
@@ -49,7 +52,6 @@
public static final String KEY_MEDIA_TYPES = "mediaTypes";
private static final String TAG = "Gallery";
- private GalleryActionBar mActionBar;
private Dialog mVersionCheckDialog;
@Override
@@ -60,7 +62,6 @@
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);
- mActionBar = new GalleryActionBar(this);
if (savedInstanceState != null) {
getStateManager().restoreFromState(savedInstanceState);
@@ -115,7 +116,6 @@
data.putInt(KEY_TYPE_BITS, typeBits);
data.putString(AlbumSetPage.KEY_MEDIA_PATH,
getDataManager().getTopSetPath(typeBits));
- getStateManager().setLaunchGalleryOnTop(true);
getStateManager().startState(AlbumSetPage.class, data);
}
@@ -134,7 +134,6 @@
private void startViewAction(Intent intent) {
Boolean slideshow = intent.getBooleanExtra(EXTRA_SLIDESHOW, false);
- getStateManager().setLaunchGalleryOnTop(true);
if (slideshow) {
getActionBar().hide();
DataManager manager = getDataManager();
@@ -165,7 +164,6 @@
data.putInt(KEY_TYPE_BITS, typeBits);
data.putString(AlbumSetPage.KEY_MEDIA_PATH,
getDataManager().getTopSetPath(typeBits));
- getStateManager().setLaunchGalleryOnTop(true);
getStateManager().startState(AlbumSetPage.class, data);
} else if (contentType.startsWith(
ContentResolver.CURSOR_DIR_BASE_TYPE)) {
@@ -183,6 +181,8 @@
if (mediaSet != null) {
if (mediaSet.isLeafAlbum()) {
data.putString(AlbumPage.KEY_MEDIA_PATH, setPath.toString());
+ data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
+ dm.getTopSetPath(DataManager.INCLUDE_ALL));
getStateManager().startState(AlbumPage.class, data);
} else {
data.putString(AlbumSetPage.KEY_MEDIA_PATH, setPath.toString());
@@ -196,11 +196,31 @@
Path albumPath = dm.getDefaultSetOf(itemPath);
// TODO: Make this parameter public so other activities can reference it.
boolean singleItemOnly = intent.getBooleanExtra("SingleItemOnly", false);
- if (!singleItemOnly && albumPath != null) {
- data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
- albumPath.toString());
+ if (!singleItemOnly && (albumPath != null)) {
+ data.putString(PhotoPage.KEY_MEDIA_SET_PATH, albumPath.toString());
}
data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, itemPath.toString());
+
+ // Displays the filename as title, reading the filename from the interface:
+ // {@link android.provider.OpenableColumns#DISPLAY_NAME}.
+ AsyncQueryHandler queryHandler = new AsyncQueryHandler(getContentResolver()) {
+ @Override
+ protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+ try {
+ if ((cursor != null) && cursor.moveToFirst()) {
+ String displayName = cursor.getString(0);
+
+ // Just show empty title if other apps don't set DISPLAY_NAME
+ setTitle((displayName == null) ? "" : displayName);
+ }
+ } finally {
+ Utils.closeSilently(cursor);
+ }
+ }
+ };
+ queryHandler.startQuery(0, null, uri, new String[] {OpenableColumns.DISPLAY_NAME},
+ null, null, null);
+
getStateManager().startState(PhotoPage.class, data);
}
}
@@ -213,41 +233,6 @@
}
@Override
- public boolean onOptionsItemSelected(MenuItem item) {
- GLRoot root = getGLRoot();
- root.lockRenderThread();
- try {
- return getStateManager().itemSelected(item);
- } finally {
- root.unlockRenderThread();
- }
- }
-
- @Override
- public void onBackPressed() {
- // send the back event to the top sub-state
- GLRoot root = getGLRoot();
- root.lockRenderThread();
- try {
- getStateManager().onBackPressed();
- } finally {
- root.unlockRenderThread();
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- GLRoot root = getGLRoot();
- root.lockRenderThread();
- try {
- getStateManager().destroy();
- } finally {
- root.unlockRenderThread();
- }
- }
-
- @Override
protected void onResume() {
Utils.assertTrue(getStateManager().getStateCount() > 0);
super.onResume();
@@ -265,11 +250,6 @@
}
@Override
- public GalleryActionBar getGalleryActionBar() {
- return mActionBar;
- }
-
- @Override
public void onCancel(DialogInterface dialog) {
if (dialog == mVersionCheckDialog) {
mVersionCheckDialog = null;
diff --git a/src/com/android/gallery3d/app/GalleryActionBar.java b/src/com/android/gallery3d/app/GalleryActionBar.java
index 717f16c..c99cd60 100644
--- a/src/com/android/gallery3d/app/GalleryActionBar.java
+++ b/src/com/android/gallery3d/app/GalleryActionBar.java
@@ -16,11 +16,10 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.R;
-
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.ActionBar.OnMenuVisibilityListener;
import android.content.Context;
import android.content.DialogInterface;
import android.view.LayoutInflater;
@@ -32,11 +31,23 @@
import android.widget.ShareActionProvider;
import android.widget.TextView;
+import com.android.gallery3d.R;
+
import java.util.ArrayList;
public class GalleryActionBar implements ActionBar.OnNavigationListener {
private static final String TAG = "GalleryActionBar";
+ private ClusterRunner mClusterRunner;
+ private CharSequence[] mTitles;
+ private ArrayList<Integer> mActions;
+ private Context mContext;
+ private LayoutInflater mInflater;
+ private GalleryActivity mActivity;
+ private ActionBar mActionBar;
+ private int mCurrentIndex;
+ private ClusterAdapter mAdapter = new ClusterAdapter();
+
public interface ClusterRunner {
public void doCluster(int id);
}
@@ -103,15 +114,23 @@
}
}
- private ClusterRunner mClusterRunner;
- private CharSequence[] mTitles;
- private ArrayList<Integer> mActions;
- private Context mContext;
- private LayoutInflater mInflater;
- private GalleryActivity mActivity;
- private ActionBar mActionBar;
- private int mCurrentIndex;
- private ClusterAdapter mAdapter = new ClusterAdapter();
+ public static String getClusterByTypeString(Context context, int type) {
+ for (ActionItem item : sClusterItems) {
+ if (item.action == type) {
+ return context.getString(item.clusterBy);
+ }
+ }
+ return null;
+ }
+
+ public static ShareActionProvider initializeShareActionProvider(Menu menu) {
+ MenuItem item = menu.findItem(R.id.action_share);
+ ShareActionProvider shareActionProvider = null;
+ if (item != null) {
+ shareActionProvider = (ShareActionProvider) item.getActionProvider();
+ }
+ return shareActionProvider;
+ }
public GalleryActionBar(GalleryActivity activity) {
mActionBar = ((Activity) activity).getActionBar();
@@ -121,11 +140,6 @@
mCurrentIndex = 0;
}
- public static int getHeight(Activity activity) {
- ActionBar actionBar = activity.getActionBar();
- return actionBar != null ? actionBar.getHeight() : 0;
- }
-
private void createDialogData() {
ArrayList<CharSequence> titles = new ArrayList<CharSequence>();
mActions = new ArrayList<Integer>();
@@ -139,6 +153,10 @@
titles.toArray(mTitles);
}
+ public int getHeight() {
+ return mActionBar != null ? mActionBar.getHeight() : 0;
+ }
+
public void setClusterItemEnabled(int id, boolean enabled) {
for (ActionItem item : sClusterItems) {
if (item.action == id) {
@@ -161,37 +179,27 @@
return sClusterItems[mCurrentIndex].action;
}
- public static String getClusterByTypeString(Context context, int type) {
- for (ActionItem item : sClusterItems) {
- if (item.action == type) {
- return context.getString(item.clusterBy);
+ public void enableClusterMenu(int action, ClusterRunner runner) {
+ if (mActionBar != null) {
+ // Don't set cluster runner until action bar is ready.
+ mClusterRunner = null;
+ mActionBar.setListNavigationCallbacks(mAdapter, this);
+ mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ setSelectedAction(action);
+ mClusterRunner = runner;
+ }
+ }
+
+ // The only use case not to hideMenu in this method is to ensure
+ // all elements disappear at the same time when exiting gallery.
+ // hideMenu should always be true in all other cases.
+ public void disableClusterMenu(boolean hideMenu) {
+ if (mActionBar != null) {
+ mClusterRunner = null;
+ if (hideMenu) {
+ mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
}
}
- return null;
- }
-
- public static ShareActionProvider initializeShareActionProvider(Menu menu) {
- MenuItem item = menu.findItem(R.id.action_share);
- ShareActionProvider shareActionProvider = null;
- if (item != null) {
- shareActionProvider = (ShareActionProvider) item.getActionProvider();
- }
- return shareActionProvider;
- }
-
- public void showClusterMenu(int action, ClusterRunner runner) {
- Log.v(TAG, "showClusterMenu: runner=" + runner);
- // Don't set cluster runner until action bar is ready.
- mClusterRunner = null;
- mActionBar.setListNavigationCallbacks(mAdapter, this);
- mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
- setSelectedAction(action);
- mClusterRunner = runner;
- }
-
- public void hideClusterMenu() {
- mClusterRunner = null;
- mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
}
public void showClusterDialog(final ClusterRunner clusterRunner) {
@@ -205,6 +213,16 @@
}).create().show();
}
+ public void setDisplayOptions(boolean displayHomeAsUp, boolean showTitle) {
+ if (mActionBar != null) {
+ int options = (displayHomeAsUp ? ActionBar.DISPLAY_HOME_AS_UP : 0) |
+ (showTitle ? ActionBar.DISPLAY_SHOW_TITLE : 0);
+ mActionBar.setDisplayOptions(options,
+ ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE);
+ mActionBar.setHomeButtonEnabled(displayHomeAsUp);
+ }
+ }
+
public void setTitle(String title) {
if (mActionBar != null) mActionBar.setTitle(title);
}
@@ -217,15 +235,25 @@
if (mActionBar != null) mActionBar.setSubtitle(title);
}
- public void setNavigationMode(int mode) {
- if (mActionBar != null) mActionBar.setNavigationMode(mode);
+ public void show() {
+ if (mActionBar != null) mActionBar.show();
}
- public int getHeight() {
- return mActionBar == null ? 0 : mActionBar.getHeight();
+ public void hide() {
+ if (mActionBar != null) mActionBar.hide();
+ }
+
+ public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
+ if (mActionBar != null) mActionBar.addOnMenuVisibilityListener(listener);
+ }
+
+ public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
+ if (mActionBar != null) mActionBar.removeOnMenuVisibilityListener(listener);
}
public boolean setSelectedAction(int type) {
+ if (mActionBar == null) return false;
+
for (int i = 0, n = sClusterItems.length; i < n; i++) {
ActionItem item = sClusterItems[i];
if (item.action == type) {
diff --git a/src/com/android/gallery3d/app/GalleryActivity.java b/src/com/android/gallery3d/app/GalleryActivity.java
index 02f2f72..f41811b 100644
--- a/src/com/android/gallery3d/app/GalleryActivity.java
+++ b/src/com/android/gallery3d/app/GalleryActivity.java
@@ -17,12 +17,9 @@
package com.android.gallery3d.app;
import com.android.gallery3d.ui.GLRoot;
-import com.android.gallery3d.ui.PositionRepository;
public interface GalleryActivity extends GalleryContext {
public StateManager getStateManager();
public GLRoot getGLRoot();
- public PositionRepository getPositionRepository();
- public GalleryApp getGalleryApplication();
public GalleryActionBar getGalleryActionBar();
}
diff --git a/src/com/android/gallery3d/app/GalleryApp.java b/src/com/android/gallery3d/app/GalleryApp.java
index b3a305e..a2d7494 100644
--- a/src/com/android/gallery3d/app/GalleryApp.java
+++ b/src/com/android/gallery3d/app/GalleryApp.java
@@ -16,16 +16,16 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.DownloadCache;
-import com.android.gallery3d.data.ImageCacheService;
-import com.android.gallery3d.util.ThreadPool;
-
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.os.Looper;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.DownloadCache;
+import com.android.gallery3d.data.ImageCacheService;
+import com.android.gallery3d.util.ThreadPool;
+
public interface GalleryApp {
public DataManager getDataManager();
public ImageCacheService getImageCacheService();
diff --git a/src/com/android/gallery3d/app/GalleryAppImpl.java b/src/com/android/gallery3d/app/GalleryAppImpl.java
index 8d25ebf..852926b 100644
--- a/src/com/android/gallery3d/app/GalleryAppImpl.java
+++ b/src/com/android/gallery3d/app/GalleryAppImpl.java
@@ -16,6 +16,9 @@
package com.android.gallery3d.app;
+import android.app.Application;
+import android.content.Context;
+
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.DownloadCache;
import com.android.gallery3d.data.ImageCacheService;
@@ -24,9 +27,6 @@
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.ThreadPool;
-import android.app.Application;
-import android.content.Context;
-
import java.io.File;
public class GalleryAppImpl extends Application implements GalleryApp {
@@ -35,6 +35,7 @@
private static final long DOWNLOAD_CAPACITY = 64 * 1024 * 1024; // 64M
private ImageCacheService mImageCacheService;
+ private Object mLock = new Object();
private DataManager mDataManager;
private ThreadPool mThreadPool;
private DownloadCache mDownloadCache;
@@ -42,6 +43,7 @@
@Override
public void onCreate() {
super.onCreate();
+ com.android.camera.Util.initialize(this);
GalleryUtils.initialize(this);
WidgetUtils.initialize(this);
PicasaSource.initialize(this);
@@ -59,11 +61,14 @@
return mDataManager;
}
- public synchronized ImageCacheService getImageCacheService() {
- if (mImageCacheService == null) {
- mImageCacheService = new ImageCacheService(getAndroidContext());
+ public ImageCacheService getImageCacheService() {
+ // This method may block on file I/O so a dedicated lock is needed here.
+ synchronized (mLock) {
+ if (mImageCacheService == null) {
+ mImageCacheService = new ImageCacheService(getAndroidContext());
+ }
+ return mImageCacheService;
}
- return mImageCacheService;
}
public synchronized ThreadPool getThreadPool() {
diff --git a/src/com/android/gallery3d/app/GalleryContext.java b/src/com/android/gallery3d/app/GalleryContext.java
index 022b4a7..06f4fe4 100644
--- a/src/com/android/gallery3d/app/GalleryContext.java
+++ b/src/com/android/gallery3d/app/GalleryContext.java
@@ -16,23 +16,19 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.ImageCacheService;
-import com.android.gallery3d.util.ThreadPool;
-
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.os.Looper;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.util.ThreadPool;
+
public interface GalleryContext {
- public ImageCacheService getImageCacheService();
public DataManager getDataManager();
public Context getAndroidContext();
public Looper getMainLooper();
public Resources getResources();
- public ContentResolver getContentResolver();
public ThreadPool getThreadPool();
}
diff --git a/src/com/android/gallery3d/app/ManageCachePage.java b/src/com/android/gallery3d/app/ManageCachePage.java
index 27f92e4..f97958e 100644
--- a/src/com/android/gallery3d/app/ManageCachePage.java
+++ b/src/com/android/gallery3d/app/ManageCachePage.java
@@ -16,29 +16,7 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.R;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.ui.AlbumSetView;
-import com.android.gallery3d.ui.CacheStorageUsageInfo;
-import com.android.gallery3d.ui.GLCanvas;
-import com.android.gallery3d.ui.GLView;
-import com.android.gallery3d.ui.ManageCacheDrawer;
-import com.android.gallery3d.ui.MenuExecutor;
-import com.android.gallery3d.ui.SelectionDrawer;
-import com.android.gallery3d.ui.SelectionManager;
-import com.android.gallery3d.ui.SlotView;
-import com.android.gallery3d.ui.StaticBackground;
-import com.android.gallery3d.ui.SynchronizedHandler;
-import com.android.gallery3d.util.Future;
-import com.android.gallery3d.util.GalleryUtils;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
import android.app.Activity;
-import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
@@ -52,6 +30,25 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.ui.CacheStorageUsageInfo;
+import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.GLView;
+import com.android.gallery3d.ui.ManageCacheDrawer;
+import com.android.gallery3d.ui.MenuExecutor;
+import com.android.gallery3d.ui.SelectionManager;
+import com.android.gallery3d.ui.SlotView;
+import com.android.gallery3d.ui.SynchronizedHandler;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
import java.util.ArrayList;
public class ManageCachePage extends ActivityState implements
@@ -61,21 +58,17 @@
private static final String TAG = "ManageCachePage";
- private static final float USER_DISTANCE_METER = 0.3f;
private static final int DATA_CACHE_SIZE = 256;
private static final int MSG_REFRESH_STORAGE_INFO = 1;
private static final int MSG_REQUEST_LAYOUT = 2;
private static final int PROGRESS_BAR_MAX = 10000;
- private StaticBackground mStaticBackground;
- private AlbumSetView mAlbumSetView;
-
+ private SlotView mSlotView;
private MediaSet mMediaSet;
protected SelectionManager mSelectionManager;
- protected SelectionDrawer mSelectionDrawer;
- private AlbumSetDataAdapter mAlbumSetDataAdapter;
- private float mUserDistance; // in pixel
+ protected ManageCacheDrawer mSelectionDrawer;
+ private AlbumSetDataLoader mAlbumSetDataAdapter;
private EyePosition mEyePosition;
@@ -96,6 +89,11 @@
private float mMatrix[] = new float[16];
@Override
+ protected void renderBackground(GLCanvas view) {
+ view.clearBuffer();
+ }
+
+ @Override
protected void onLayout(
boolean changed, int left, int top, int right, int bottom) {
// Hack: our layout depends on other components on the screen.
@@ -107,10 +105,9 @@
}
mLayoutReady = false;
- mStaticBackground.layout(0, 0, right - left, bottom - top);
mEyePosition.resetPosition();
Activity activity = (Activity) mActivity;
- int slotViewTop = GalleryActionBar.getHeight(activity);
+ int slotViewTop = mActivity.getGalleryActionBar().getHeight();
int slotViewBottom = bottom - top;
View footer = activity.findViewById(R.id.footer);
@@ -120,7 +117,7 @@
slotViewBottom = location[1];
}
- mAlbumSetView.layout(0, slotViewTop, right - left, slotViewBottom);
+ mSlotView.layout(0, slotViewTop, right - left, slotViewBottom);
}
@Override
@@ -134,6 +131,7 @@
}
};
+ @Override
public void onEyePositionChanged(float x, float y, float z) {
mRootPane.lockRendering();
mX = x;
@@ -144,15 +142,11 @@
}
private void onDown(int index) {
- MediaSet set = mAlbumSetDataAdapter.getMediaSet(index);
- Path path = (set == null) ? null : set.getPath();
- mSelectionManager.setPressedPath(path);
- mAlbumSetView.invalidate();
+ mSelectionDrawer.setPressedIndex(index);
}
private void onUp() {
- mSelectionManager.setPressedPath(null);
- mAlbumSetView.invalidate();
+ mSelectionDrawer.setPressedIndex(-1);
}
public void onSingleTapUp(int slotIndex) {
@@ -188,7 +182,7 @@
refreshCacheStorageInfo();
mSelectionManager.toggle(path);
- mAlbumSetView.invalidate();
+ mSlotView.invalidate();
}
@Override
@@ -230,7 +224,7 @@
public void onPause() {
super.onPause();
mAlbumSetDataAdapter.pause();
- mAlbumSetView.pause();
+ mSelectionDrawer.pause();
mEyePosition.pause();
if (mUpdateStorageInfo != null) {
@@ -260,7 +254,7 @@
super.onResume();
setContentPane(mRootPane);
mAlbumSetDataAdapter.resume();
- mAlbumSetView.resume();
+ mSelectionDrawer.resume();
mEyePosition.resume();
mUpdateStorageInfo = mActivity.getThreadPool().submit(mUpdateStorageInfoJob);
FrameLayout layout = (FrameLayout) ((Activity) mActivity).findViewById(R.id.footer);
@@ -269,7 +263,6 @@
}
private void initializeData(Bundle data) {
- mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
String mediaPath = data.getString(ManageCachePage.KEY_MEDIA_PATH);
mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
mSelectionManager.setSourceMediaSet(mMediaSet);
@@ -278,9 +271,9 @@
mSelectionManager.setAutoLeaveSelectionMode(false);
mSelectionManager.enterSelectionMode();
- mAlbumSetDataAdapter = new AlbumSetDataAdapter(
+ mAlbumSetDataAdapter = new AlbumSetDataLoader(
mActivity, mMediaSet, DATA_CACHE_SIZE);
- mAlbumSetView.setModel(mAlbumSetDataAdapter);
+ mSelectionDrawer.setModel(mAlbumSetDataAdapter);
}
private void initializeViews() {
@@ -288,22 +281,20 @@
mSelectionManager = new SelectionManager(mActivity, true);
mSelectionManager.setSelectionListener(this);
- mStaticBackground = new StaticBackground(activity);
- mRootPane.addComponent(mStaticBackground);
Config.ManageCachePage config = Config.ManageCachePage.get(activity);
- mSelectionDrawer = new ManageCacheDrawer((Context) mActivity,
- mSelectionManager, config.cachePinSize, config.cachePinMargin);
- mAlbumSetView = new AlbumSetView(mActivity, mSelectionDrawer,
- config.slotViewSpec, config.labelSpec);
- mAlbumSetView.setListener(new SlotView.SimpleListener() {
+ mSlotView = new SlotView(mActivity, config.slotViewSpec);
+ mSelectionDrawer = new ManageCacheDrawer(mActivity, mSelectionManager, mSlotView,
+ config.labelSpec, config.cachePinSize, config.cachePinMargin);
+ mSlotView.setSlotRenderer(mSelectionDrawer);
+ mSlotView.setListener(new SlotView.SimpleListener() {
@Override
public void onDown(int index) {
ManageCachePage.this.onDown(index);
}
@Override
- public void onUp() {
+ public void onUp(boolean followedByLongPress) {
ManageCachePage.this.onUp();
}
@@ -312,7 +303,7 @@
ManageCachePage.this.onSingleTapUp(slotIndex);
}
});
- mRootPane.addComponent(mAlbumSetView);
+ mRootPane.addComponent(mSlotView);
initializeFooterViews();
}
@@ -324,24 +315,28 @@
mFooterContent = inflater.inflate(R.layout.manage_offline_bar, null);
mFooterContent.findViewById(R.id.done).setOnClickListener(this);
- mStaticBackground.setImage(R.drawable.background, R.drawable.background_portrait);
refreshCacheStorageInfo();
}
@Override
public void onClick(View view) {
Utils.assertTrue(view.getId() == R.id.done);
+ GLRoot root = mActivity.getGLRoot();
+ root.lockRenderThread();
+ try {
+ ArrayList<Path> ids = mSelectionManager.getSelected(false);
+ if (ids.size() == 0) {
+ onBackPressed();
+ return;
+ }
+ showToast();
- ArrayList<Path> ids = mSelectionManager.getSelected(false);
- if (ids.size() == 0) {
- onBackPressed();
- return;
+ MenuExecutor menuExecutor = new MenuExecutor(mActivity, mSelectionManager);
+ menuExecutor.startAction(R.id.action_toggle_full_caching,
+ R.string.process_caching_requests, this);
+ } finally {
+ root.unlockRenderThread();
}
- showToast();
-
- MenuExecutor menuExecutor = new MenuExecutor(mActivity, mSelectionManager);
- menuExecutor.startAction(R.id.action_toggle_full_caching,
- R.string.process_caching_requests, this);
}
private void showToast() {
@@ -388,17 +383,20 @@
}
}
+ @Override
public void onProgressComplete(int result) {
onBackPressed();
}
+ @Override
public void onProgressUpdate(int index) {
}
+ @Override
public void onSelectionModeChange(int mode) {
}
+ @Override
public void onSelectionChange(Path path, boolean selected) {
}
-
}
diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java
index 099e9f5..288eeb0 100644
--- a/src/com/android/gallery3d/app/MovieActivity.java
+++ b/src/com/android/gallery3d/app/MovieActivity.java
@@ -18,14 +18,17 @@
import android.app.ActionBar;
import android.app.Activity;
+import android.content.AsyncQueryHandler;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
-import android.provider.MediaStore.Video.VideoColumns;
+import android.provider.OpenableColumns;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -35,13 +38,19 @@
import android.widget.ShareActionProvider;
import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
/**
* This activity plays a video from a specified URI.
+ *
+ * The client of this activity can pass a logo bitmap in the intent (KEY_LOGO_BITMAP)
+ * to set the action bar logo so the playback process looks more seamlessly integrated with
+ * the original activity.
*/
public class MovieActivity extends Activity {
@SuppressWarnings("unused")
private static final String TAG = "MovieActivity";
+ private static final String KEY_LOGO_BITMAP = "logo-bitmap";
private MoviePlayer mPlayer;
private boolean mFinishOnCompletion;
@@ -55,7 +64,7 @@
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
setContentView(R.layout.movie_view);
- View rootView = findViewById(R.id.root);
+ View rootView = findViewById(R.id.movie_view_root);
Intent intent = getIntent();
initializeActionBar(intent);
mFinishOnCompletion = intent.getBooleanExtra(
@@ -85,26 +94,44 @@
}
private void initializeActionBar(Intent intent) {
- ActionBar actionBar = getActionBar();
+ mUri = intent.getData();
+ final ActionBar actionBar = getActionBar();
+ Bitmap logo = intent.getParcelableExtra(KEY_LOGO_BITMAP);
+ if (logo != null) {
+ actionBar.setLogo(new BitmapDrawable(getResources(), logo));
+ }
actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP,
ActionBar.DISPLAY_HOME_AS_UP);
+
String title = intent.getStringExtra(Intent.EXTRA_TITLE);
- mUri = intent.getData();
- if (title == null) {
- Cursor cursor = null;
- try {
- cursor = getContentResolver().query(mUri,
- new String[] {VideoColumns.TITLE}, null, null, null);
- if (cursor != null && cursor.moveToNext()) {
- title = cursor.getString(0);
+ if (title != null) {
+ actionBar.setTitle(title);
+ } else {
+ // Displays the filename as title, reading the filename from the
+ // interface: {@link android.provider.OpenableColumns#DISPLAY_NAME}.
+ AsyncQueryHandler queryHandler =
+ new AsyncQueryHandler(getContentResolver()) {
+ @Override
+ protected void onQueryComplete(int token, Object cookie,
+ Cursor cursor) {
+ try {
+ if ((cursor != null) && cursor.moveToFirst()) {
+ String displayName = cursor.getString(0);
+
+ // Just show empty title if other apps don't set
+ // DISPLAY_NAME
+ actionBar.setTitle((displayName == null) ? "" :
+ displayName);
+ }
+ } finally {
+ Utils.closeSilently(cursor);
+ }
}
- } catch (Throwable t) {
- Log.w(TAG, "cannot get title from: " + intent.getDataString(), t);
- } finally {
- if (cursor != null) cursor.close();
- }
+ };
+ queryHandler.startQuery(0, null, mUri,
+ new String[] {OpenableColumns.DISPLAY_NAME}, null, null,
+ null);
}
- if (title != null) actionBar.setTitle(title);
}
@Override
diff --git a/src/com/android/gallery3d/app/MovieControllerOverlay.java b/src/com/android/gallery3d/app/MovieControllerOverlay.java
index 752213e..92c3049 100644
--- a/src/com/android/gallery3d/app/MovieControllerOverlay.java
+++ b/src/com/android/gallery3d/app/MovieControllerOverlay.java
@@ -16,9 +16,6 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.R;
-
import android.content.Context;
import android.os.Handler;
import android.view.Gravity;
@@ -31,7 +28,6 @@
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
-import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
@@ -39,6 +35,8 @@
import android.widget.RelativeLayout;
import android.widget.TextView;
+import com.android.gallery3d.R;
+
/**
* The playback controller for the Movie Player.
*/
@@ -103,6 +101,9 @@
ProgressBar spinner = new ProgressBar(context);
spinner.setIndeterminate(true);
loadingView.addView(spinner, wrapContent);
+ TextView loadingText = createOverlayTextView(context);
+ loadingText.setText(R.string.loading_video);
+ loadingView.addView(loadingText, wrapContent);
addView(loadingView, wrapContent);
playPauseReplayView = new ImageView(context);
@@ -114,10 +115,7 @@
playPauseReplayView.setOnClickListener(this);
addView(playPauseReplayView, wrapContent);
- errorView = new TextView(context);
- errorView.setGravity(Gravity.CENTER);
- errorView.setBackgroundColor(0xCC000000);
- errorView.setTextColor(0xFFFFFFFF);
+ errorView = createOverlayTextView(context);
addView(errorView, matchParent);
handler = new Handler();
@@ -136,6 +134,14 @@
hide();
}
+ private TextView createOverlayTextView(Context context) {
+ TextView view = new TextView(context);
+ view.setGravity(Gravity.CENTER);
+ view.setTextColor(0xFFFFFFFF);
+ view.setPadding(0, 15, 0, 15);
+ return view;
+ }
+
public void setListener(Listener listener) {
this.listener = listener;
}
@@ -171,15 +177,11 @@
public void showErrorMessage(String message) {
state = State.ERROR;
int padding = (int) (getMeasuredWidth() * ERROR_MESSAGE_RELATIVE_PADDING);
- errorView.setPadding(padding, 10, padding, 10);
+ errorView.setPadding(padding, errorView.getPaddingTop(), padding, errorView.getPaddingBottom());
errorView.setText(message);
showMainView(errorView);
}
- public void resetTime() {
- timeBar.resetTime();
- }
-
public void setTimes(int currentTime, int totalTime) {
timeBar.setTime(currentTime, totalTime);
}
@@ -331,9 +333,6 @@
cx - bw / 2, playbackButtonsCenterline - bh / 2, cx + bw / 2,
playbackButtonsCenterline + bh / 2);
- // Space available on each side of the error message for the next and previous buttons
- int errorMessagePadding = (int) (w * ERROR_MESSAGE_RELATIVE_PADDING);
-
if (mainView != null) {
layoutCenteredView(mainView, l, t, r, b);
}
@@ -386,5 +385,4 @@
maybeStartHiding();
listener.onSeekEnd(time);
}
-
}
diff --git a/src/com/android/gallery3d/app/PackagesMonitor.java b/src/com/android/gallery3d/app/PackagesMonitor.java
index dcd9d04..e4bb8ee 100644
--- a/src/com/android/gallery3d/app/PackagesMonitor.java
+++ b/src/com/android/gallery3d/app/PackagesMonitor.java
@@ -16,14 +16,14 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.picasasource.PicasaSource;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
+import com.android.gallery3d.picasasource.PicasaSource;
+
public class PackagesMonitor extends BroadcastReceiver {
public static final String KEY_PACKAGES_VERSION = "packages-version";
@@ -33,7 +33,24 @@
}
@Override
- public void onReceive(Context context, Intent intent) {
+ public void onReceive(final Context context, final Intent intent) {
+ final PendingResult result = goAsync();
+ new Thread("GalleryPackagesMonitorAsync") {
+ @Override
+ public void run() {
+ try {
+ onReceiveAsync(context, intent);
+ } catch (Throwable t) {
+ Log.e("PackagesMonitor", "onReceiveAsync", t);
+ } finally {
+ result.finish();
+ }
+ }
+ }.start();
+ }
+
+ // Runs in a background thread.
+ private void onReceiveAsync(Context context, Intent intent) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
int version = prefs.getInt(KEY_PACKAGES_VERSION, 1);
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index d7d1168..096e781 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -16,6 +16,11 @@
package com.android.gallery3d.app;
+import android.graphics.Bitmap;
+import android.graphics.BitmapRegionDecoder;
+import android.os.Handler;
+import android.os.Message;
+
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.ContentListener;
@@ -24,8 +29,9 @@
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;
+import com.android.gallery3d.ui.BitmapScreenNail;
import com.android.gallery3d.ui.PhotoView;
-import com.android.gallery3d.ui.PhotoView.ImageData;
+import com.android.gallery3d.ui.ScreenNail;
import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.ui.TileImageViewAdapter;
import com.android.gallery3d.util.Future;
@@ -34,11 +40,6 @@
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
-import android.graphics.Bitmap;
-import android.graphics.BitmapRegionDecoder;
-import android.os.Handler;
-import android.os.Message;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -54,16 +55,16 @@
private static final int MSG_LOAD_START = 1;
private static final int MSG_LOAD_FINISH = 2;
private static final int MSG_RUN_OBJECT = 3;
+ private static final int MSG_UPDATE_IMAGE_REQUESTS = 4;
private static final int MIN_LOAD_COUNT = 8;
private static final int DATA_CACHE_SIZE = 32;
- private static final int IMAGE_CACHE_SIZE = 5;
+ private static final int SCREEN_NAIL_MAX = PhotoView.SCREEN_NAIL_MAX;
+ private static final int IMAGE_CACHE_SIZE = 2 * SCREEN_NAIL_MAX + 1;
private static final int BIT_SCREEN_NAIL = 1;
private static final int BIT_FULL_IMAGE = 2;
- private static final long VERSION_OUT_OF_RANGE = MediaObject.nextVersionNumber();
-
// sImageFetchSeq is the fetching sequence for images.
// We want to fetch the current screennail first (offset = 0), the next
// screennail (offset = +1), then the previous screennail (offset = -1) etc.
@@ -128,9 +129,9 @@
private int mCurrentIndex;
// mChanges keeps the version number (of MediaItem) about the previous,
- // current, and next image. If the version number changes, we invalidate
- // the model. This is used after a database reload or mCurrentIndex changes.
- private final long mChanges[] = new long[3];
+ // current, and next image. If the version number changes, we notify the
+ // view. This is used after a database reload or mCurrentIndex changes.
+ private final long mChanges[] = new long[IMAGE_CACHE_SIZE];
private final Handler mMainHandler;
private final ThreadPool mThreadPool;
@@ -143,9 +144,9 @@
private int mSize = 0;
private Path mItemPath;
private boolean mIsActive;
+ private boolean mNeedFullImage;
public interface DataListener extends LoadingListener {
- public void onPhotoAvailable(long version, boolean fullImage);
public void onPhotoChanged(int index, Path item);
}
@@ -164,6 +165,7 @@
mItemPath = Utils.checkNotNull(itemPath);
mCurrentIndex = indexHint;
mThreadPool = activity.getThreadPool();
+ mNeedFullImage = true;
Arrays.fill(mChanges, MediaObject.INVALID_DATA_VERSION);
@@ -183,6 +185,10 @@
if (mDataListener != null) mDataListener.onLoadingFinished();
return;
}
+ case MSG_UPDATE_IMAGE_REQUESTS: {
+ updateImageRequests();
+ return;
+ }
default: throw new AssertionError();
}
}
@@ -192,7 +198,7 @@
}
private long getVersion(int index) {
- if (index < 0 || index >= mSize) return VERSION_OUT_OF_RANGE;
+ if (index < 0 || index >= mSize) return MediaObject.INVALID_DATA_VERSION;
if (index >= mContentStart && index < mContentEnd) {
MediaItem item = mData[index % DATA_CACHE_SIZE];
if (item != null) return item.getDataVersion();
@@ -200,43 +206,40 @@
return MediaObject.INVALID_DATA_VERSION;
}
- private void fireModelInvalidated() {
- for (int i = -1; i <= 1; ++i) {
- long current = getVersion(mCurrentIndex + i);
- long change = mChanges[i + 1];
- if (current != change) {
- mPhotoView.notifyImageInvalidated(i);
- mChanges[i + 1] = current;
- }
+ private void fireDataChange() {
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
+ mChanges[i + SCREEN_NAIL_MAX] = getVersion(mCurrentIndex + i);
}
+ mPhotoView.notifyDataChange(mChanges, -mCurrentIndex,
+ mSize - 1 - mCurrentIndex);
}
public void setDataListener(DataListener listener) {
mDataListener = listener;
}
- private void updateScreenNail(long version, Future<Bitmap> future) {
+ private void updateScreenNail(long version, Future<ScreenNail> future) {
ImageEntry entry = mImageCache.get(version);
+ ScreenNail screenNail = future.get();
+
if (entry == null || entry.screenNailTask != future) {
- Bitmap screenNail = future.get();
if (screenNail != null) screenNail.recycle();
return;
}
entry.screenNailTask = null;
- entry.screenNail = future.get();
+ Utils.assertTrue(entry.screenNail == null);
+ entry.screenNail = screenNail;
- if (entry.screenNail == null) {
+ if (screenNail == null) {
entry.failToLoad = true;
- } else {
- if (mDataListener != null) {
- mDataListener.onPhotoAvailable(version, false);
- }
- for (int i = -1; i <=1; ++i) {
- if (version == getVersion(mCurrentIndex + i)) {
- if (i == 0) updateTileProvider(entry);
- mPhotoView.notifyImageInvalidated(i);
- }
+ }
+
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
+ if (version == getVersion(mCurrentIndex + i)) {
+ if (i == 0) updateTileProvider(entry);
+ mPhotoView.notifyImageChange(i);
+ break;
}
}
updateImageRequests();
@@ -253,12 +256,9 @@
entry.fullImageTask = null;
entry.fullImage = future.get();
if (entry.fullImage != null) {
- if (mDataListener != null) {
- mDataListener.onPhotoAvailable(version, true);
- }
if (version == getVersion(mCurrentIndex)) {
updateTileProvider(entry);
- mPhotoView.notifyImageInvalidated(0);
+ mPhotoView.notifyImageChange(0);
}
}
updateImageRequests();
@@ -273,7 +273,7 @@
mReloadTask = new ReloadTask();
mReloadTask.start();
- mPhotoView.notifyModelInvalidated();
+ fireDataChange();
}
public void pause() {
@@ -287,30 +287,28 @@
for (ImageEntry entry : mImageCache.values()) {
if (entry.fullImageTask != null) entry.fullImageTask.cancel();
if (entry.screenNailTask != null) entry.screenNailTask.cancel();
+ if (entry.screenNail != null) entry.screenNail.recycle();
}
mImageCache.clear();
mTileProvider.clear();
}
- private ImageData getImage(int index) {
+ private ScreenNail getImage(int index) {
if (index < 0 || index >= mSize || !mIsActive) return null;
Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
ImageEntry entry = mImageCache.get(getVersion(index));
- Bitmap screennail = entry == null ? null : entry.screenNail;
- if (screennail != null) {
- return new ImageData(screennail, entry.rotation);
- } else {
- return new ImageData(null, 0);
+ return entry == null ? null : entry.screenNail;
+ }
+
+ private MediaItem getItem(int index) {
+ if (index < 0 || index >= mSize || !mIsActive) return null;
+ Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
+
+ if (index >= mContentStart && index < mContentEnd) {
+ return mData[index % DATA_CACHE_SIZE];
}
- }
-
- public ImageData getPreviousImage() {
- return getImage(mCurrentIndex - 1);
- }
-
- public ImageData getNextImage() {
- return getImage(mCurrentIndex + 1);
+ return null;
}
private void updateCurrentIndex(int index) {
@@ -323,29 +321,55 @@
updateImageCache();
updateImageRequests();
updateTileProvider();
- mPhotoView.notifyOnNewImage();
if (mDataListener != null) {
mDataListener.onPhotoChanged(index, mItemPath);
}
- fireModelInvalidated();
+
+ fireDataChange();
}
+ @Override
public void next() {
updateCurrentIndex(mCurrentIndex + 1);
}
+ @Override
public void previous() {
updateCurrentIndex(mCurrentIndex - 1);
}
- public void jumpTo(int index) {
- if (mCurrentIndex == index) return;
- updateCurrentIndex(index);
+ @Override
+ public ScreenNail getScreenNail(int offset) {
+ return getImage(mCurrentIndex + offset);
}
- public Bitmap getBackupImage() {
- return mTileProvider.getBackupImage();
+ @Override
+ public void getImageSize(int offset, PhotoView.Size size) {
+ MediaItem item = getItem(mCurrentIndex + offset);
+ if (item == null) {
+ size.width = 0;
+ size.height = 0;
+ } else {
+ size.width = item.getWidth();
+ size.height = item.getHeight();
+ }
+ }
+
+ @Override
+ public int getImageRotation(int offset) {
+ MediaItem item = getItem(mCurrentIndex + offset);
+ return (item == null) ? 0 : item.getFullImageRotation();
+ }
+
+ @Override
+ public void setNeedFullImage(boolean enabled) {
+ mNeedFullImage = enabled;
+ mMainHandler.sendEmptyMessage(MSG_UPDATE_IMAGE_REQUESTS);
+ }
+
+ public ScreenNail getScreenNail() {
+ return mTileProvider.getScreenNail();
}
public int getImageHeight() {
@@ -356,11 +380,6 @@
return mTileProvider.getImageWidth();
}
- public int getImageRotation() {
- ImageEntry entry = mImageCache.get(getVersion(mCurrentIndex));
- return entry == null ? 0 : entry.rotation;
- }
-
public int getLevelCount() {
return mTileProvider.getLevelCount();
}
@@ -392,7 +411,7 @@
mCurrentIndex = indexHint;
updateSlidingWindow();
updateImageCache();
- fireModelInvalidated();
+ fireDataChange();
// We need to reload content if the path doesn't match.
MediaItem item = getCurrentMediaItem();
@@ -411,17 +430,17 @@
}
private void updateTileProvider(ImageEntry entry) {
- Bitmap screenNail = entry.screenNail;
+ ScreenNail screenNail = entry.screenNail;
BitmapRegionDecoder fullImage = entry.fullImage;
if (screenNail != null) {
if (fullImage != null) {
- mTileProvider.setBackupImage(screenNail,
+ mTileProvider.setScreenNail(screenNail,
fullImage.getWidth(), fullImage.getHeight());
mTileProvider.setRegionDecoder(fullImage);
} else {
int width = screenNail.getWidth();
int height = screenNail.getHeight();
- mTileProvider.setBackupImage(screenNail, width, height);
+ mTileProvider.setScreenNail(screenNail, width, height);
}
} else {
mTileProvider.clear();
@@ -472,6 +491,7 @@
for (int i = 0; i < sImageFetchSeq.length; i++) {
int offset = sImageFetchSeq[i].indexOffset;
int bit = sImageFetchSeq[i].imageBit;
+ if (bit == BIT_FULL_IMAGE && !mNeedFullImage) continue;
task = startTaskIfNeeded(currentIndex + offset, bit);
if (task != null) break;
}
@@ -491,7 +511,7 @@
}
}
- private static class ScreenNailJob implements Job<Bitmap> {
+ private static class ScreenNailJob implements Job<ScreenNail> {
private MediaItem mItem;
public ScreenNailJob(MediaItem item) {
@@ -499,14 +519,19 @@
}
@Override
- public Bitmap run(JobContext jc) {
+ public ScreenNail run(JobContext jc) {
+ // We try to get a ScreenNail first, if it fails, we fallback to get
+ // a Bitmap and then wrap it in a BitmapScreenNail instead.
+ ScreenNail s = mItem.getScreenNail();
+ if (s != null) return s;
+
Bitmap bitmap = mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc);
if (jc.isCancelled()) return null;
if (bitmap != null) {
bitmap = BitmapUtils.rotateBitmap(bitmap,
mItem.getRotation() - mItem.getFullImageRotation(), true);
}
- return bitmap;
+ return new BitmapScreenNail(bitmap);
}
}
@@ -580,6 +605,7 @@
ImageEntry entry = mImageCache.remove(version);
if (entry.fullImageTask != null) entry.fullImageTask.cancel();
if (entry.screenNailTask != null) entry.screenNailTask.cancel();
+ if (entry.screenNail != null) entry.screenNail.recycle();
}
}
@@ -606,16 +632,16 @@
}
private class ScreenNailListener
- implements Runnable, FutureListener<Bitmap> {
+ implements Runnable, FutureListener<ScreenNail> {
private final long mVersion;
- private Future<Bitmap> mFuture;
+ private Future<ScreenNail> mFuture;
public ScreenNailListener(long version) {
mVersion = version;
}
@Override
- public void onFutureDone(Future<Bitmap> future) {
+ public void onFutureDone(Future<ScreenNail> future) {
mFuture = future;
mMainHandler.sendMessage(
mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
@@ -631,8 +657,8 @@
public int requestedBits = 0;
public int rotation;
public BitmapRegionDecoder fullImage;
- public Bitmap screenNail;
- public Future<Bitmap> screenNailTask;
+ public ScreenNail screenNail;
+ public Future<ScreenNail> screenNailTask;
public Future<BitmapRegionDecoder> fullImageTask;
public boolean failToLoad = false;
}
@@ -738,7 +764,7 @@
updateImageCache();
updateTileProvider();
updateImageRequests();
- fireModelInvalidated();
+ fireDataChange();
return null;
}
@@ -746,12 +772,8 @@
if (mSize == 0) return;
if (mCurrentIndex >= mSize) {
mCurrentIndex = mSize - 1;
- mPhotoView.notifyOnNewImage();
- mPhotoView.startSlideInAnimation(PhotoView.TRANS_SLIDE_IN_LEFT);
- } else {
- mPhotoView.notifyOnNewImage();
- mPhotoView.startSlideInAnimation(PhotoView.TRANS_SLIDE_IN_RIGHT);
}
+ fireDataChange();
}
}
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index ed67b08..6617ce6 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -16,13 +16,17 @@
package com.android.gallery3d.app;
-import android.app.ActionBar;
import android.app.ActionBar.OnMenuVisibilityListener;
import android.app.Activity;
import android.content.ActivityNotFoundException;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Rect;
import android.net.Uri;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -31,6 +35,7 @@
import android.view.MenuItem;
import android.view.View;
import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ShareActionProvider;
import android.widget.Toast;
@@ -43,26 +48,25 @@
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.MtpDevice;
import com.android.gallery3d.data.Path;
+import com.android.gallery3d.data.SnailSource;
import com.android.gallery3d.picasasource.PicasaSource;
import com.android.gallery3d.ui.DetailsHelper;
import com.android.gallery3d.ui.DetailsHelper.CloseListener;
import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
-import com.android.gallery3d.ui.FilmStripView;
import com.android.gallery3d.ui.GLCanvas;
import com.android.gallery3d.ui.GLView;
import com.android.gallery3d.ui.ImportCompleteListener;
import com.android.gallery3d.ui.MenuExecutor;
+import com.android.gallery3d.ui.ScreenNail;
+import com.android.gallery3d.ui.ScreenNailHolder;
import com.android.gallery3d.ui.PhotoView;
-import com.android.gallery3d.ui.PositionRepository;
-import com.android.gallery3d.ui.PositionRepository.Position;
import com.android.gallery3d.ui.SelectionManager;
import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.ui.UserInteractionListener;
import com.android.gallery3d.util.GalleryUtils;
public class PhotoPage extends ActivityState
- implements PhotoView.PhotoTapListener, FilmStripView.Listener,
- UserInteractionListener {
+ implements PhotoView.PhotoTapListener, UserInteractionListener {
private static final String TAG = "PhotoPage";
private static final int MSG_HIDE_BARS = 1;
@@ -76,13 +80,14 @@
public static final String KEY_MEDIA_SET_PATH = "media-set-path";
public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
public static final String KEY_INDEX_HINT = "index-hint";
+ public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect";
+ public static final String KEY_SCREENNAIL_HOLDER = "screennail-holder";
private GalleryApp mApplication;
private SelectionManager mSelectionManager;
private PhotoView mPhotoView;
private PhotoPage.Model mModel;
- private FilmStripView mFilmStripView;
private DetailsHelper mDetailsHelper;
private boolean mShowDetails;
private Path mPendingSharePath;
@@ -96,14 +101,20 @@
private int mCurrentIndex = 0;
private Handler mHandler;
private boolean mShowBars = true;
- private ActionBar mActionBar;
+ private GalleryActionBar mActionBar;
private MyMenuVisibilityListener mMenuVisibilityListener;
+ private PageTapListener mPageTapListener;
private boolean mIsMenuVisible;
private boolean mIsInteracting;
private MediaItem mCurrentPhoto = null;
private MenuExecutor mMenuExecutor;
private boolean mIsActive;
private ShareActionProvider mShareActionProvider;
+ private String mSetPathString;
+ private ScreenNailHolder mScreenNailHolder;
+ private ScreenNail mScreenNail;
+
+ private NfcAdapter mNfcAdapter;
public static interface Model extends PhotoView.Model {
public void resume();
@@ -121,6 +132,15 @@
}
}
+ public interface PageTapListener {
+ // Return true if the tap is consumed.
+ public boolean onSingleTapUp(int x, int y);
+ }
+
+ public void setPageTapListener(PageTapListener listener) {
+ mPageTapListener = listener;
+ }
+
private final GLView mRootPane = new GLView() {
@Override
@@ -132,42 +152,15 @@
protected void onLayout(
boolean changed, int left, int top, int right, int bottom) {
mPhotoView.layout(0, 0, right - left, bottom - top);
- PositionRepository.getInstance(mActivity).setOffset(0, 0);
- int filmStripHeight = 0;
- if (mFilmStripView != null) {
- mFilmStripView.measure(
- MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
- MeasureSpec.UNSPECIFIED);
- filmStripHeight = mFilmStripView.getMeasuredHeight();
- mFilmStripView.layout(0, bottom - top - filmStripHeight,
- right - left, bottom - top);
- }
if (mShowDetails) {
- mDetailsHelper.layout(left, GalleryActionBar.getHeight((Activity) mActivity),
- right, bottom);
+ mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
}
}
};
- private void initFilmStripView() {
- Config.PhotoPage config = Config.PhotoPage.get((Context) mActivity);
- mFilmStripView = new FilmStripView(mActivity, mMediaSet,
- config.filmstripTopMargin, config.filmstripMidMargin, config.filmstripBottomMargin,
- config.filmstripContentSize, config.filmstripThumbSize, config.filmstripBarSize,
- config.filmstripGripSize, config.filmstripGripWidth);
- mRootPane.addComponent(mFilmStripView);
- mFilmStripView.setListener(this);
- mFilmStripView.setUserInteractionListener(this);
- mFilmStripView.setFocusIndex(mCurrentIndex);
- mFilmStripView.setStartIndex(mCurrentIndex);
- mRootPane.requestLayout();
- if (mIsActive) mFilmStripView.resume();
- if (!mShowBars) mFilmStripView.setVisibility(GLView.INVISIBLE);
- }
-
@Override
public void onCreate(Bundle data, Bundle restoreState) {
- mActionBar = ((Activity) mActivity).getActionBar();
+ mActionBar = mActivity.getGalleryActionBar();
mSelectionManager = new SelectionManager(mActivity, false);
mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
@@ -176,16 +169,35 @@
mRootPane.addComponent(mPhotoView);
mApplication = (GalleryApp)((Activity) mActivity).getApplication();
- String setPathString = data.getString(KEY_MEDIA_SET_PATH);
+ mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext());
Path itemPath = Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH));
- if (setPathString != null) {
- mMediaSet = mActivity.getDataManager().getMediaSet(setPathString);
+ if (mSetPathString != null) {
+ mScreenNailHolder =
+ (ScreenNailHolder) data.getParcelable(KEY_SCREENNAIL_HOLDER);
+ if (mScreenNailHolder != null) {
+ mScreenNail = mScreenNailHolder.attach();
+
+ // Get the ScreenNail from ScreenNailHolder and register it.
+ int id = SnailSource.registerScreenNail(mScreenNail);
+ Path screenNailSetPath = SnailSource.getSetPath(id);
+ Path screenNailItemPath = SnailSource.getItemPath(id);
+
+ // Combine the original MediaSet with the one for CameraScreenNail.
+ mSetPathString = "/combo/item/{" + screenNailSetPath +
+ "," + mSetPathString + "}";
+
+ // Start from the screen nail.
+ itemPath = screenNailItemPath;
+ }
+
+ mMediaSet = mActivity.getDataManager().getMediaSet(mSetPathString);
mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
mMediaSet = (MediaSet)
- mActivity.getDataManager().getMediaObject(setPathString);
+ mActivity.getDataManager().getMediaObject(mSetPathString);
if (mMediaSet == null) {
- Log.w(TAG, "failed to restore " + setPathString);
+ Log.w(TAG, "failed to restore " + mSetPathString);
}
PhotoDataAdapter pda = new PhotoDataAdapter(
mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex);
@@ -199,7 +211,6 @@
@Override
public void onPhotoChanged(int index, Path item) {
- if (mFilmStripView != null) mFilmStripView.setFocusIndex(index);
mCurrentIndex = index;
mResultIntent.putExtra(KEY_INDEX_HINT, index);
if (item != null) {
@@ -227,11 +238,6 @@
public void onLoadingStarted() {
GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
}
-
- @Override
- public void onPhotoAvailable(long version, boolean fullImage) {
- if (mFilmStripView == null) initFilmStripView();
- }
});
} else {
// Get default media set by the URI
@@ -255,8 +261,10 @@
}
};
- // start the opening animation
- mPhotoView.setOpenedItem(itemPath);
+ // start the opening animation only if it's not restored.
+ if (restoreState == null) {
+ mPhotoView.setOpenAnimationRect((Rect) data.getParcelable(KEY_OPEN_ANIMATION_RECT));
+ }
}
private void updateShareURI(Path path) {
@@ -267,6 +275,10 @@
intent.setType(MenuExecutor.getMimeType(type));
intent.putExtra(Intent.EXTRA_STREAM, manager.getContentUri(path));
mShareActionProvider.setShareIntent(intent);
+ if (mNfcAdapter != null) {
+ mNfcAdapter.setBeamPushUris(new Uri[]{manager.getContentUri(path)},
+ (Activity)mActivity);
+ }
mPendingSharePath = null;
} else {
// This happens when ActionBar is not created yet.
@@ -274,29 +286,31 @@
}
}
- private void setTitle(String title) {
- if (title == null) return;
- boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean(
- R.bool.show_action_bar_title);
- if (showTitle)
- mActionBar.setTitle(title);
- else
- mActionBar.setTitle("");
- }
-
private void updateCurrentPhoto(MediaItem photo) {
if (mCurrentPhoto == photo) return;
mCurrentPhoto = photo;
if (mCurrentPhoto == null) return;
updateMenuOperations();
+ updateTitle();
if (mShowDetails) {
mDetailsHelper.reloadDetails(mModel.getCurrentIndex());
}
- setTitle(photo.getName());
mPhotoView.showVideoPlayIcon(
photo.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO);
- updateShareURI(photo.getPath());
+ if ((photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
+ updateShareURI(photo.getPath());
+ }
+ }
+
+ private void updateTitle() {
+ if (mCurrentPhoto == null) return;
+ boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean(
+ R.bool.show_action_bar_title);
+ if (showTitle && mCurrentPhoto.getName() != null)
+ mActionBar.setTitle(mCurrentPhoto.getName());
+ else
+ mActionBar.setTitle("");
}
private void updateMenuOperations() {
@@ -334,9 +348,6 @@
WindowManager.LayoutParams params = ((Activity) mActivity).getWindow().getAttributes();
params.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
((Activity) mActivity).getWindow().setAttributes(params);
- if (mFilmStripView != null) {
- mFilmStripView.show();
- }
}
private void hideBars() {
@@ -346,9 +357,6 @@
WindowManager.LayoutParams params = ((Activity) mActivity).getWindow().getAttributes();
params.systemUiVisibility = View. SYSTEM_UI_FLAG_LOW_PROFILE;
((Activity) mActivity).getWindow().setAttributes(params);
- if (mFilmStripView != null) {
- mFilmStripView.hide();
- }
}
private void refreshHidingMessage() {
@@ -395,22 +403,18 @@
protected void onBackPressed() {
if (mShowDetails) {
hideDetails();
- } else {
- PositionRepository repository = PositionRepository.getInstance(mActivity);
- repository.clear();
- if (mCurrentPhoto != null) {
- Position position = new Position();
- position.x = mRootPane.getWidth() / 2;
- position.y = mRootPane.getHeight() / 2;
- position.z = -1000;
- repository.putPosition(
- Long.valueOf(System.identityHashCode(mCurrentPhoto.getPath())),
- position);
- }
+ } else if (!switchWithCaptureAnimation(-1)) {
super.onBackPressed();
}
}
+ // Switch to the previous or next picture using the capture animation.
+ // The offset is -1 to switch to the previous picture, 1 to switch to
+ // the next picture.
+ public boolean switchWithCaptureAnimation(int offset) {
+ return mPhotoView.switchWithCaptureAnimation(offset);
+ }
+
@Override
protected boolean onCreateActionBar(Menu menu) {
MenuInflater inflater = ((Activity) mActivity).getMenuInflater();
@@ -420,6 +424,7 @@
mMenu = menu;
mShowBars = true;
updateMenuOperations();
+ updateTitle();
return true;
}
@@ -437,7 +442,24 @@
DataManager manager = mActivity.getDataManager();
int action = item.getItemId();
+ boolean needsConfirm = false;
switch (action) {
+ case android.R.id.home: {
+ if (mSetPathString != null) {
+ if (mActivity.getStateManager().getStateCount() > 1) {
+ onBackPressed();
+ } else {
+ // We're in view mode so set up the stacks on our own.
+ Bundle data = new Bundle(getData());
+ data.putString(AlbumPage.KEY_MEDIA_PATH, mSetPathString);
+ data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
+ mActivity.getDataManager().getTopSetPath(
+ DataManager.INCLUDE_ALL));
+ mActivity.getStateManager().switchState(this, AlbumPage.class, data);
+ }
+ }
+ return true;
+ }
case R.id.action_slideshow: {
Bundle data = new Bundle();
data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
@@ -466,20 +488,21 @@
}
return true;
}
+ case R.id.action_delete:
+ needsConfirm = true;
case R.id.action_setas:
- case R.id.action_confirm_delete:
case R.id.action_rotate_ccw:
case R.id.action_rotate_cw:
case R.id.action_show_on_map:
case R.id.action_edit:
mSelectionManager.deSelectAll();
mSelectionManager.toggle(path);
- mMenuExecutor.onMenuClicked(item, null);
+ mMenuExecutor.onMenuClicked(item, needsConfirm, null);
return true;
case R.id.action_import:
mSelectionManager.deSelectAll();
mSelectionManager.toggle(path);
- mMenuExecutor.onMenuClicked(item,
+ mMenuExecutor.onMenuClicked(item, needsConfirm,
new ImportCompleteListener(mActivity));
return true;
default :
@@ -507,6 +530,10 @@
}
public void onSingleTapUp(int x, int y) {
+ if (mPageTapListener != null) {
+ if (mPageTapListener.onSingleTapUp(x, y)) return;
+ }
+
MediaItem item = mModel.getCurrentMediaItem();
if (item == null) {
// item is not ready, ignore
@@ -544,12 +571,6 @@
}
}
- // Called by FileStripView.
- // Returns false if it cannot jump to the specified index at this time.
- public boolean onSlotSelected(int slotIndex) {
- return mPhotoView.jumpTo(slotIndex);
- }
-
@Override
protected void onStateResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
@@ -586,14 +607,12 @@
public void onPause() {
super.onPause();
mIsActive = false;
- if (mFilmStripView != null) {
- mFilmStripView.pause();
- }
DetailsHelper.pause();
mPhotoView.pause();
mModel.pause();
mHandler.removeMessages(MSG_HIDE_BARS);
mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
+
mMenuExecutor.pause();
}
@@ -602,18 +621,30 @@
super.onResume();
mIsActive = true;
setContentPane(mRootPane);
+
mModel.resume();
mPhotoView.resume();
- if (mFilmStripView != null) {
- mFilmStripView.resume();
- }
if (mMenuVisibilityListener == null) {
mMenuVisibilityListener = new MyMenuVisibilityListener();
}
+ mActionBar.setDisplayOptions(mSetPathString != null, true);
mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
+
onUserInteraction();
}
+ @Override
+ protected void onDestroy() {
+ if (mScreenNailHolder != null) {
+ // Unregister the ScreenNail and notify mScreenNailHolder.
+ SnailSource.unregisterScreenNail(mScreenNail);
+ mScreenNailHolder.detach();
+ mScreenNailHolder = null;
+ mScreenNail = null;
+ }
+ super.onDestroy();
+ }
+
private class MyDetailsSource implements DetailsSource {
private int mIndex;
@@ -638,4 +669,5 @@
return mIndex;
}
}
+
}
diff --git a/src/com/android/gallery3d/app/PickerActivity.java b/src/com/android/gallery3d/app/PickerActivity.java
index 944192d..f5b2cbd 100644
--- a/src/com/android/gallery3d/app/PickerActivity.java
+++ b/src/com/android/gallery3d/app/PickerActivity.java
@@ -16,10 +16,6 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.R;
-import com.android.gallery3d.ui.GLRoot;
-import com.android.gallery3d.ui.GLRootView;
-
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
@@ -28,6 +24,10 @@
import android.view.View.OnClickListener;
import android.view.Window;
+import com.android.gallery3d.R;
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.GLRootView;
+
public class PickerActivity extends AbstractGalleryActivity
implements OnClickListener {
@@ -74,19 +74,7 @@
finish();
return true;
}
- return false;
- }
-
- @Override
- public void onBackPressed() {
- // send the back event to the top sub-state
- GLRoot root = getGLRoot();
- root.lockRenderThread();
- try {
- getStateManager().getTopState().onBackPressed();
- } finally {
- root.unlockRenderThread();
- }
+ return super.onOptionsItemSelected(item);
}
@Override
diff --git a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
index 0c54ada..47f6acb 100644
--- a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
@@ -16,18 +16,6 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.ui.PhotoView;
-import com.android.gallery3d.ui.PhotoView.ImageData;
-import com.android.gallery3d.ui.SynchronizedHandler;
-import com.android.gallery3d.ui.TileImageViewAdapter;
-import com.android.gallery3d.util.Future;
-import com.android.gallery3d.util.FutureListener;
-import com.android.gallery3d.util.ThreadPool;
-
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
@@ -35,6 +23,18 @@
import android.os.Handler;
import android.os.Message;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.ui.PhotoView;
+import com.android.gallery3d.ui.ScreenNail;
+import com.android.gallery3d.ui.SynchronizedHandler;
+import com.android.gallery3d.ui.TileImageViewAdapter;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.ThreadPool;
+
public class SinglePhotoDataAdapter extends TileImageViewAdapter
implements PhotoPage.Model {
@@ -109,16 +109,12 @@
return false;
}
- public int getImageRotation() {
- return mItem.getRotation();
- }
-
private void onDecodeLargeComplete(ImageBundle bundle) {
try {
- setBackupImage(bundle.backupImage,
+ setScreenNail(bundle.backupImage,
bundle.decoder.getWidth(), bundle.decoder.getHeight());
setRegionDecoder(bundle.decoder);
- mPhotoView.notifyImageInvalidated(0);
+ mPhotoView.notifyImageChange(0);
} catch (Throwable t) {
Log.w(TAG, "fail to decode large", t);
}
@@ -128,9 +124,8 @@
try {
Bitmap backup = future.get();
if (backup == null) return;
- setBackupImage(backup, backup.getWidth(), backup.getHeight());
- mPhotoView.notifyOnNewImage();
- mPhotoView.notifyImageInvalidated(0); // the current image
+ setScreenNail(backup, backup.getWidth(), backup.getHeight());
+ mPhotoView.notifyImageChange(0);
} catch (Throwable t) {
Log.w(TAG, "fail to decode thumb", t);
}
@@ -158,26 +153,43 @@
}
}
- public ImageData getNextImage() {
- return null;
- }
-
- public ImageData getPreviousImage() {
- return null;
- }
-
+ @Override
public void next() {
throw new UnsupportedOperationException();
}
+ @Override
public void previous() {
throw new UnsupportedOperationException();
}
- public void jumpTo(int index) {
- throw new UnsupportedOperationException();
+ @Override
+ public void getImageSize(int offset, PhotoView.Size size) {
+ if (offset == 0) {
+ size.width = mItem.getWidth();
+ size.height = mItem.getHeight();
+ } else {
+ size.width = 0;
+ size.height = 0;
+ }
}
+ @Override
+ public int getImageRotation(int offset) {
+ return (offset == 0) ? mItem.getFullImageRotation() : 0;
+ }
+
+ @Override
+ public ScreenNail getScreenNail(int offset) {
+ return (offset == 0) ? getScreenNail() : null;
+ }
+
+ @Override
+ public void setNeedFullImage(boolean enabled) {
+ // currently not necessary.
+ }
+
+
public MediaItem getCurrentMediaItem() {
return mItem;
}
diff --git a/src/com/android/gallery3d/app/SlideshowDream.java b/src/com/android/gallery3d/app/SlideshowDream.java
index 80f7fe0..c249467 100644
--- a/src/com/android/gallery3d/app/SlideshowDream.java
+++ b/src/com/android/gallery3d/app/SlideshowDream.java
@@ -1,16 +1,10 @@
package com.android.gallery3d.app;
-import android.app.Activity;
import android.content.Intent;
-import android.graphics.Canvas;
-import android.net.Uri;
import android.os.Bundle;
-import android.os.Environment;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.ViewFlipper;
+import android.support.v13.dreams.BasicDream;
-public class SlideshowDream extends Activity {
+public class SlideshowDream extends BasicDream {
@Override
public void onCreate(Bundle bndl) {
super.onCreate(bndl);
diff --git a/src/com/android/gallery3d/app/SlideshowPage.java b/src/com/android/gallery3d/app/SlideshowPage.java
index 8697629..5aa3985 100644
--- a/src/com/android/gallery3d/app/SlideshowPage.java
+++ b/src/com/android/gallery3d/app/SlideshowPage.java
@@ -17,7 +17,6 @@
package com.android.gallery3d.app;
import android.app.Activity;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
diff --git a/src/com/android/gallery3d/app/StateManager.java b/src/com/android/gallery3d/app/StateManager.java
index 556a06a..5866be9 100644
--- a/src/com/android/gallery3d/app/StateManager.java
+++ b/src/com/android/gallery3d/app/StateManager.java
@@ -16,8 +16,6 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.common.Utils;
-
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
@@ -26,6 +24,8 @@
import android.view.Menu;
import android.view.MenuItem;
+import com.android.gallery3d.common.Utils;
+
import java.util.Stack;
public class StateManager {
@@ -37,12 +37,10 @@
private static final String KEY_DATA = "data";
private static final String KEY_STATE = "bundle";
private static final String KEY_CLASS = "class";
- private static final String KEY_LAUNCH_GALLERY_ON_TOP = "launch-gallery-on-top";
private GalleryActivity mContext;
private Stack<StateEntry> mStack = new Stack<StateEntry>();
private ActivityState.ResultEntry mResult;
- private boolean mLaunchGalleryOnTop = false;
public StateManager(GalleryActivity context) {
mContext = context;
@@ -68,10 +66,6 @@
if (mIsResumed) state.resume();
}
- public void setLaunchGalleryOnTop(boolean enabled) {
- mLaunchGalleryOnTop = enabled;
- }
-
public void startStateForResult(Class<? extends ActivityState> klass,
int requestCode, Bundle data) {
Log.v(TAG, "startStateForResult " + klass + ", " + requestCode);
@@ -135,18 +129,12 @@
public boolean itemSelected(MenuItem item) {
if (!mStack.isEmpty()) {
+ if (getTopState().onItemSelected(item)) return true;
if (item.getItemId() == android.R.id.home) {
if (mStack.size() > 1) {
getTopState().onBackPressed();
- } else if (mLaunchGalleryOnTop) {
- Activity activity = (Activity) mContext;
- Intent intent = new Intent(activity, Gallery.class)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ((Activity) mContext).startActivity(intent);
}
return true;
- } else {
- return getTopState().onItemSelected(item);
}
}
return false;
@@ -159,7 +147,7 @@
}
void finishState(ActivityState state) {
- Log.v(TAG, "finishState " + state.getClass());
+ Log.v(TAG, "finishState " + state);
if (state != mStack.peek().activityState) {
if (state.isDestroyed()) {
Log.d(TAG, "The state is already destroyed");
@@ -235,7 +223,6 @@
@SuppressWarnings("unchecked")
public void restoreFromState(Bundle inState) {
Log.v(TAG, "restoreFromState");
- mLaunchGalleryOnTop = inState.getBoolean(KEY_LAUNCH_GALLERY_ON_TOP, false);
Parcelable list[] = inState.getParcelableArray(KEY_MAIN);
for (Parcelable parcelable : list) {
Bundle bundle = (Bundle) parcelable;
@@ -261,7 +248,6 @@
public void saveState(Bundle outState) {
Log.v(TAG, "saveState");
- outState.putBoolean(KEY_LAUNCH_GALLERY_ON_TOP, mLaunchGalleryOnTop);
Parcelable list[] = new Parcelable[mStack.size()];
int i = 0;
for (StateEntry entry : mStack) {
diff --git a/src/com/android/gallery3d/app/TimeBar.java b/src/com/android/gallery3d/app/TimeBar.java
index 0ac8efb..1f5bfd9 100644
--- a/src/com/android/gallery3d/app/TimeBar.java
+++ b/src/com/android/gallery3d/app/TimeBar.java
@@ -16,9 +16,6 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.R;
-
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -29,6 +26,9 @@
import android.view.MotionEvent;
import android.view.View;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
+
/**
* The time bar view, which includes the current and total time, the progress bar,
* and the scrubber.
diff --git a/src/com/android/gallery3d/data/BitmapPool.java b/src/com/android/gallery3d/data/BitmapPool.java
new file mode 100644
index 0000000..0fbd84e
--- /dev/null
+++ b/src/com/android/gallery3d/data/BitmapPool.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.data;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.ui.Log;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+
+public class BitmapPool {
+ private static final String TAG = "BitmapPool";
+
+ private final ArrayList<Bitmap> mPool;
+ private final int mPoolLimit;
+
+ // mOneSize is true if the pool can only cache Bitmap with one size.
+ private final boolean mOneSize;
+ private final int mWidth, mHeight; // only used if mOneSize is true
+
+ // Construct a BitmapPool which caches bitmap with the specified size.
+ public BitmapPool(int width, int height, int poolLimit) {
+ mWidth = width;
+ mHeight = height;
+ mPoolLimit = poolLimit;
+ mPool = new ArrayList<Bitmap>(poolLimit);
+ mOneSize = true;
+ }
+
+ // Construct a BitmapPool which caches bitmap with any size;
+ public BitmapPool(int poolLimit) {
+ mWidth = -1;
+ mHeight = -1;
+ mPoolLimit = poolLimit;
+ mPool = new ArrayList<Bitmap>(poolLimit);
+ mOneSize = false;
+ }
+
+ // Get a Bitmap from the pool.
+ public synchronized Bitmap getBitmap() {
+ Utils.assertTrue(mOneSize);
+ int size = mPool.size();
+ return size > 0 ? mPool.remove(size - 1) : null;
+ }
+
+ // Get a Bitmap from the pool with the specified size.
+ public synchronized Bitmap getBitmap(int width, int height) {
+ Utils.assertTrue(!mOneSize);
+ for (int i = mPool.size() - 1; i >= 0; i--) {
+ Bitmap b = mPool.get(i);
+ if (b.getWidth() == width && b.getHeight() == height) {
+ return mPool.remove(i);
+ }
+ }
+ return null;
+ }
+
+ // Put a Bitmap into the pool, if the Bitmap has a proper size. Otherwise
+ // the Bitmap will be recycled. If the pool is full, an old Bitmap will be
+ // recycled.
+ public void recycle(Bitmap bitmap) {
+ if (bitmap == null) return;
+ if (mOneSize && ((bitmap.getWidth() != mWidth) ||
+ (bitmap.getHeight() != mHeight))) {
+ bitmap.recycle();
+ return;
+ }
+ synchronized (this) {
+ if (mPool.size() >= mPoolLimit) mPool.remove(0);
+ mPool.add(bitmap);
+ }
+ }
+
+ public synchronized void clear() {
+ mPool.clear();
+ }
+
+ private Bitmap findCachedBitmap(JobContext jc,
+ byte[] data, int offset, int length, Options options) {
+ if (mOneSize) return getBitmap();
+ DecodeUtils.decodeBounds(jc, data, offset, length, options);
+ return getBitmap(options.outWidth, options.outHeight);
+ }
+
+ private Bitmap findCachedBitmap(JobContext jc,
+ FileDescriptor fileDescriptor, Options options) {
+ if (mOneSize) return getBitmap();
+ DecodeUtils.decodeBounds(jc, fileDescriptor, options);
+ return getBitmap(options.outWidth, options.outHeight);
+ }
+
+ public Bitmap decode(JobContext jc,
+ byte[] data, int offset, int length, BitmapFactory.Options options) {
+ if (options == null) options = new BitmapFactory.Options();
+ if (options.inSampleSize < 1) options.inSampleSize = 1;
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ options.inBitmap = (options.inSampleSize == 1)
+ ? findCachedBitmap(jc, data, offset, length, options) : null;
+ try {
+ Bitmap bitmap = DecodeUtils.decode(jc, data, offset, length, options);
+ if (options.inBitmap != null && options.inBitmap != bitmap) {
+ recycle(options.inBitmap);
+ options.inBitmap = null;
+ }
+ return bitmap;
+ } catch (IllegalArgumentException e) {
+ if (options.inBitmap == null) throw e;
+
+ Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
+ recycle(options.inBitmap);
+ options.inBitmap = null;
+ return DecodeUtils.decode(jc, data, offset, length, options);
+ }
+ }
+
+ // This is the same as the method above except the source data comes
+ // from a file descriptor instead of a byte array.
+ public Bitmap decode(JobContext jc,
+ FileDescriptor fileDescriptor, Options options) {
+ if (options == null) options = new BitmapFactory.Options();
+ if (options.inSampleSize < 1) options.inSampleSize = 1;
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ options.inBitmap = (options.inSampleSize == 1)
+ ? findCachedBitmap(jc, fileDescriptor, options) : null;
+ try {
+ Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
+ if (options.inBitmap != null&& options.inBitmap != bitmap) {
+ recycle(options.inBitmap);
+ options.inBitmap = null;
+ }
+ return bitmap;
+ } catch (IllegalArgumentException e) {
+ if (options.inBitmap == null) throw e;
+
+ Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
+ recycle(options.inBitmap);
+ options.inBitmap = null;
+ return DecodeUtils.decode(jc, fileDescriptor, options);
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/data/BytesBufferPool.java b/src/com/android/gallery3d/data/BytesBufferPool.java
new file mode 100644
index 0000000..d2da323
--- /dev/null
+++ b/src/com/android/gallery3d/data/BytesBufferPool.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.data;
+
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class BytesBufferPool {
+
+ private static final int READ_STEP = 4096;
+
+ public static class BytesBuffer {
+ public byte[] data;
+ public int offset;
+ public int length;
+
+ private BytesBuffer(int capacity) {
+ this.data = new byte[capacity];
+ }
+
+ // an helper function to read content from FileDescriptor
+ public void readFrom(JobContext jc, FileDescriptor fd) throws IOException {
+ FileInputStream fis = new FileInputStream(fd);
+ length = 0;
+ try {
+ int capacity = data.length;
+ while (true) {
+ int step = Math.min(READ_STEP, capacity - length);
+ int rc = fis.read(data, length, step);
+ if (rc < 0 || jc.isCancelled()) return;
+ length += rc;
+
+ if (length == capacity) {
+ byte[] newData = new byte[data.length * 2];
+ System.arraycopy(data, 0, newData, 0, data.length);
+ data = newData;
+ capacity = data.length;
+ }
+ }
+ } finally {
+ fis.close();
+ }
+ }
+ }
+
+ private final int mPoolSize;
+ private final int mBufferSize;
+ private final ArrayList<BytesBuffer> mList;
+
+ public BytesBufferPool(int poolSize, int bufferSize) {
+ mList = new ArrayList<BytesBuffer>(poolSize);
+ mPoolSize = poolSize;
+ mBufferSize = bufferSize;
+ }
+
+ public synchronized BytesBuffer get() {
+ int n = mList.size();
+ return n > 0 ? mList.remove(n - 1) : new BytesBuffer(mBufferSize);
+ }
+
+ public synchronized void recycle(BytesBuffer buffer) {
+ if (buffer.data.length != mBufferSize) return;
+ if (mList.size() < mPoolSize) {
+ buffer.offset = 0;
+ buffer.length = 0;
+ mList.add(buffer);
+ }
+ }
+
+ public synchronized void clear() {
+ mList.clear();
+ }
+}
diff --git a/src/com/android/gallery3d/data/ChangeNotifier.java b/src/com/android/gallery3d/data/ChangeNotifier.java
index e1e601d..35be2dc 100644
--- a/src/com/android/gallery3d/data/ChangeNotifier.java
+++ b/src/com/android/gallery3d/data/ChangeNotifier.java
@@ -16,10 +16,10 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-
import android.net.Uri;
+import com.android.gallery3d.app.GalleryApp;
+
import java.util.concurrent.atomic.AtomicBoolean;
// This handles change notification for media sets.
@@ -38,14 +38,11 @@
return mContentDirty.compareAndSet(true, false);
}
+ // For debugging only.
public void fakeChange() {
onChange(false);
}
- public void clearDirty() {
- mContentDirty.set(false);
- }
-
protected void onChange(boolean selfChange) {
if (mContentDirty.compareAndSet(false, true)) {
mMediaSet.notifyContentChanged();
diff --git a/src/com/android/gallery3d/data/ClusterAlbumSet.java b/src/com/android/gallery3d/data/ClusterAlbumSet.java
index 16501c2..863e94a 100644
--- a/src/com/android/gallery3d/data/ClusterAlbumSet.java
+++ b/src/com/android/gallery3d/data/ClusterAlbumSet.java
@@ -16,11 +16,11 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-
import android.content.Context;
import android.net.Uri;
+import com.android.gallery3d.app.GalleryApp;
+
import java.util.ArrayList;
import java.util.HashSet;
diff --git a/src/com/android/gallery3d/data/ComboAlbum.java b/src/com/android/gallery3d/data/ComboAlbum.java
index 6d22311..73c8ef3 100644
--- a/src/com/android/gallery3d/data/ComboAlbum.java
+++ b/src/com/android/gallery3d/data/ComboAlbum.java
@@ -16,7 +16,6 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.util.Future;
import java.util.ArrayList;
diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java
index f7dac5e..1da3b76 100644
--- a/src/com/android/gallery3d/data/DataManager.java
+++ b/src/com/android/gallery3d/data/DataManager.java
@@ -16,16 +16,16 @@
package com.android.gallery3d.data;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.MediaSet.ItemConsumer;
import com.android.gallery3d.data.MediaSource.PathId;
import com.android.gallery3d.picasasource.PicasaSource;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
@@ -115,6 +115,7 @@
addSource(new ClusterSource(mApplication));
addSource(new FilterSource(mApplication));
addSource(new UriSource(mApplication));
+ addSource(new SnailSource(mApplication));
if (mActiveCount > 0) {
for (MediaSource source : mSourceMap.values()) {
@@ -145,10 +146,6 @@
return path.getObject();
}
- public MediaSet peekMediaSet(Path path) {
- return (MediaSet) path.getObject();
- }
-
public MediaObject getMediaObject(Path path) {
MediaObject obj = path.getObject();
if (obj != null) return obj;
@@ -159,11 +156,16 @@
return null;
}
- MediaObject object = source.createMediaObject(path);
- if (object == null) {
- Log.w(TAG, "cannot create media object: " + path);
+ try {
+ MediaObject object = source.createMediaObject(path);
+ if (object == null) {
+ Log.w(TAG, "cannot create media object: " + path);
+ }
+ return object;
+ } catch (Throwable t) {
+ Log.w(TAG, "exception in creating media object: " + path, t);
+ return null;
}
- return object;
}
public MediaObject getMediaObject(String s) {
@@ -240,14 +242,6 @@
return getMediaObject(path).getMediaType();
}
- public MediaDetails getDetails(Path path) {
- return getMediaObject(path).getDetails();
- }
-
- public void cache(Path path, int flag) {
- getMediaObject(path).cache(flag);
- }
-
public Path findPathByUri(Uri uri) {
if (uri == null) return null;
for (MediaSource source : mSourceMap.values()) {
diff --git a/src/com/android/gallery3d/data/DataSourceType.java b/src/com/android/gallery3d/data/DataSourceType.java
new file mode 100644
index 0000000..2761188
--- /dev/null
+++ b/src/com/android/gallery3d/data/DataSourceType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.data;
+
+import com.android.gallery3d.util.MediaSetUtils;
+
+public final class DataSourceType {
+ public static final int TYPE_NOT_CATEGORIZED = 0;
+ public static final int TYPE_LOCAL = 1;
+ public static final int TYPE_PICASA = 2;
+ public static final int TYPE_MTP = 3;
+ public static final int TYPE_CAMERA = 4;
+
+ private static final Path PICASA_ROOT = Path.fromString("/picasa");
+ private static final Path LOCAL_ROOT = Path.fromString("/local");
+ private static final Path MTP_ROOT = Path.fromString("/mtp");
+
+ public static int identifySourceType(MediaSet set) {
+ if (set == null) {
+ return TYPE_NOT_CATEGORIZED;
+ }
+
+ Path path = set.getPath();
+ if (MediaSetUtils.isCameraSource(path)) return TYPE_CAMERA;
+
+ Path prefix = path.getPrefixPath();
+
+ if (prefix == PICASA_ROOT) return TYPE_PICASA;
+ if (prefix == MTP_ROOT) return TYPE_MTP;
+ if (prefix == LOCAL_ROOT) return TYPE_LOCAL;
+
+ return TYPE_NOT_CATEGORIZED;
+ }
+}
diff --git a/src/com/android/gallery3d/data/DecodeUtils.java b/src/com/android/gallery3d/data/DecodeUtils.java
index b205576..e51dc3f 100644
--- a/src/com/android/gallery3d/data/DecodeUtils.java
+++ b/src/com/android/gallery3d/data/DecodeUtils.java
@@ -16,20 +16,17 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.ThreadPool.CancelListener;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
-import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.BitmapRegionDecoder;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
+import android.util.FloatMath;
+
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.ThreadPool.CancelListener;
+import com.android.gallery3d.util.ThreadPool.JobContext;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -40,35 +37,38 @@
private static class DecodeCanceller implements CancelListener {
Options mOptions;
+
public DecodeCanceller(Options options) {
mOptions = options;
}
+
+ @Override
public void onCancel() {
mOptions.requestCancelDecode();
}
}
- public static Bitmap requestDecode(JobContext jc, final String filePath,
- Options options) {
- if (options == null) options = new Options();
- jc.setCancelListener(new DecodeCanceller(options));
- return ensureGLCompatibleBitmap(
- BitmapFactory.decodeFile(filePath, options));
- }
-
- public static Bitmap requestDecode(JobContext jc, FileDescriptor fd, Options options) {
+ public static Bitmap decode(JobContext jc, FileDescriptor fd, Options options) {
if (options == null) options = new Options();
jc.setCancelListener(new DecodeCanceller(options));
return ensureGLCompatibleBitmap(
BitmapFactory.decodeFileDescriptor(fd, null, options));
}
- public static Bitmap requestDecode(JobContext jc, byte[] bytes,
+ public static void decodeBounds(JobContext jc, FileDescriptor fd,
Options options) {
- return requestDecode(jc, bytes, 0, bytes.length, options);
+ Utils.assertTrue(options != null);
+ options.inJustDecodeBounds = true;
+ jc.setCancelListener(new DecodeCanceller(options));
+ BitmapFactory.decodeFileDescriptor(fd, null, options);
+ options.inJustDecodeBounds = false;
}
- public static Bitmap requestDecode(JobContext jc, byte[] bytes, int offset,
+ public static Bitmap decode(JobContext jc, byte[] bytes, Options options) {
+ return decode(jc, bytes, 0, bytes.length, options);
+ }
+
+ public static Bitmap decode(JobContext jc, byte[] bytes, int offset,
int length, Options options) {
if (options == null) options = new Options();
jc.setCancelListener(new DecodeCanceller(options));
@@ -76,13 +76,22 @@
BitmapFactory.decodeByteArray(bytes, offset, length, options));
}
- public static Bitmap requestDecode(JobContext jc, final String filePath,
- Options options, int targetSize) {
+ public static void decodeBounds(JobContext jc, byte[] bytes, int offset,
+ int length, Options options) {
+ Utils.assertTrue(options != null);
+ options.inJustDecodeBounds = true;
+ jc.setCancelListener(new DecodeCanceller(options));
+ BitmapFactory.decodeByteArray(bytes, offset, length, options);
+ options.inJustDecodeBounds = false;
+ }
+
+ public static Bitmap decodeThumbnail(
+ JobContext jc, String filePath, Options options, int targetSize, int type) {
FileInputStream fis = null;
try {
fis = new FileInputStream(filePath);
FileDescriptor fd = fis.getFD();
- return requestDecode(jc, fd, options, targetSize);
+ return decodeThumbnail(jc, fd, options, targetSize, type);
} catch (Exception ex) {
Log.w(TAG, ex);
return null;
@@ -91,8 +100,8 @@
}
}
- public static Bitmap requestDecode(JobContext jc, FileDescriptor fd,
- Options options, int targetSize) {
+ public static Bitmap decodeThumbnail(
+ JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) {
if (options == null) options = new Options();
jc.setCancelListener(new DecodeCanceller(options));
@@ -100,14 +109,40 @@
BitmapFactory.decodeFileDescriptor(fd, null, options);
if (jc.isCancelled()) return null;
- options.inSampleSize = BitmapUtils.computeSampleSizeLarger(
- options.outWidth, options.outHeight, targetSize);
+ int w = options.outWidth;
+ int h = options.outHeight;
+
+ if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
+ // We center-crop the original image as it's micro thumbnail. In this case,
+ // we want to make sure the shorter side >= "targetSize".
+ float scale = (float) targetSize / Math.min(w, h);
+ options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+
+ // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding
+ // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here.
+ final int MAX_PIXEL_COUNT = 640000; // 400 x 1600
+ if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) {
+ options.inSampleSize = BitmapUtils.computeSampleSize(
+ FloatMath.sqrt((float) MAX_PIXEL_COUNT / (w * h)));
+ }
+ } else {
+ // For screen nail, we only want to keep the longer side >= targetSize.
+ float scale = (float) targetSize / Math.max(w, h);
+ options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+ }
+
options.inJustDecodeBounds = false;
Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
- // We need to resize down if the decoder does not support inSampleSize.
- // (For example, GIF images.)
- result = BitmapUtils.resizeDownIfTooBig(result, targetSize, true);
+ if (result == null) return null;
+
+ // We need to resize down if the decoder does not support inSampleSize
+ // (For example, GIF images)
+ float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL
+ ? Math.min(result.getWidth(), result.getHeight())
+ : Math.max(result.getWidth(), result.getHeight()));
+
+ if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true);
return ensureGLCompatibleBitmap(result);
}
@@ -118,7 +153,7 @@
* Note: The returned image may be resized down. However, both width and height must be
* larger than the <code>targetSize</code>.
*/
- public static Bitmap requestDecodeIfBigEnough(JobContext jc, byte[] data,
+ public static Bitmap decodeIfBigEnough(JobContext jc, byte[] data,
Options options, int targetSize) {
if (options == null) options = new Options();
jc.setCancelListener(new DecodeCanceller(options));
@@ -136,14 +171,6 @@
BitmapFactory.decodeByteArray(data, 0, data.length, options));
}
- public static Bitmap requestDecode(JobContext jc,
- FileDescriptor fileDescriptor, Rect paddings, Options options) {
- if (options == null) options = new Options();
- jc.setCancelListener(new DecodeCanceller(options));
- return ensureGLCompatibleBitmap(BitmapFactory.decodeFileDescriptor
- (fileDescriptor, paddings, options));
- }
-
// TODO: This function should not be called directly from
// DecodeUtils.requestDecode(...), since we don't have the knowledge
// if the bitmap will be uploaded to GL.
@@ -154,7 +181,7 @@
return newBitmap;
}
- public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
+ public static BitmapRegionDecoder createBitmapRegionDecoder(
JobContext jc, byte[] bytes, int offset, int length,
boolean shareable) {
if (offset < 0 || length <= 0 || offset + length > bytes.length) {
@@ -172,7 +199,7 @@
}
}
- public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
+ public static BitmapRegionDecoder createBitmapRegionDecoder(
JobContext jc, String filePath, boolean shareable) {
try {
return BitmapRegionDecoder.newInstance(filePath, shareable);
@@ -182,7 +209,7 @@
}
}
- public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
+ public static BitmapRegionDecoder createBitmapRegionDecoder(
JobContext jc, FileDescriptor fd, boolean shareable) {
try {
return BitmapRegionDecoder.newInstance(fd, shareable);
@@ -192,7 +219,7 @@
}
}
- public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
+ public static BitmapRegionDecoder createBitmapRegionDecoder(
JobContext jc, InputStream is, boolean shareable) {
try {
return BitmapRegionDecoder.newInstance(is, shareable);
@@ -203,20 +230,4 @@
return null;
}
}
-
- public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
- JobContext jc, Uri uri, ContentResolver resolver,
- boolean shareable) {
- ParcelFileDescriptor pfd = null;
- try {
- pfd = resolver.openFileDescriptor(uri, "r");
- return BitmapRegionDecoder.newInstance(
- pfd.getFileDescriptor(), shareable);
- } catch (Throwable t) {
- Log.w(TAG, t);
- return null;
- } finally {
- Utils.closeSilently(pfd);
- }
- }
}
diff --git a/src/com/android/gallery3d/data/DownloadCache.java b/src/com/android/gallery3d/data/DownloadCache.java
index 30ba668..7505dac 100644
--- a/src/com/android/gallery3d/data/DownloadCache.java
+++ b/src/com/android/gallery3d/data/DownloadCache.java
@@ -16,6 +16,12 @@
package com.android.gallery3d.data;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.common.LruCache;
import com.android.gallery3d.common.Utils;
@@ -27,17 +33,10 @@
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-
import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.WeakHashMap;
public class DownloadCache {
private static final String TAG = "DownloadCache";
@@ -78,7 +77,6 @@
private long mTotalBytes = 0;
private boolean mInitialized = false;
- private WeakHashMap<Object, Entry> mAssociateMap = new WeakHashMap<Object, Entry>();
public DownloadCache(GalleryApp application, File root, long capacity) {
mRoot = Utils.checkNotNull(root);
@@ -113,31 +111,6 @@
return null;
}
- public Entry lookup(URL url) {
- if (!mInitialized) initialize();
- String stringUrl = url.toString();
-
- // First find in the entry-pool
- synchronized (mEntryMap) {
- Entry entry = mEntryMap.get(stringUrl);
- if (entry != null) {
- updateLastAccess(entry.mId);
- return entry;
- }
- }
-
- // Then, find it in database
- TaskProxy proxy = new TaskProxy();
- synchronized (mTaskMap) {
- Entry entry = findEntryInDatabase(stringUrl);
- if (entry != null) {
- updateLastAccess(entry.mId);
- return entry;
- }
- }
- return null;
- }
-
public Entry download(JobContext jc, URL url) {
if (!mInitialized) initialize();
@@ -282,10 +255,6 @@
mId = id;
this.cacheFile = Utils.checkNotNull(cacheFile);
}
-
- public void associateWith(Object object) {
- mAssociateMap.put(Utils.checkNotNull(object), this);
- }
}
private class DownloadTask implements Job<File>, FutureListener<File> {
diff --git a/src/com/android/gallery3d/data/DownloadUtils.java b/src/com/android/gallery3d/data/DownloadUtils.java
index 9632db9..9e35c45 100644
--- a/src/com/android/gallery3d/data/DownloadUtils.java
+++ b/src/com/android/gallery3d/data/DownloadUtils.java
@@ -20,7 +20,6 @@
import com.android.gallery3d.util.ThreadPool.CancelListener;
import com.android.gallery3d.util.ThreadPool.JobContext;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -44,22 +43,6 @@
}
}
- public static byte[] requestDownload(JobContext jc, URL url) {
- ByteArrayOutputStream baos = null;
- try {
- baos = new ByteArrayOutputStream();
- if (!download(jc, url, baos)) {
- return null;
- }
- return baos.toByteArray();
- } catch (Throwable t) {
- Log.w(TAG, t);
- return null;
- } finally {
- Utils.closeSilently(baos);
- }
- }
-
public static void dump(JobContext jc, InputStream is, OutputStream os)
throws IOException {
byte buffer[] = new byte[4096];
diff --git a/src/com/android/gallery3d/data/Exif.java b/src/com/android/gallery3d/data/Exif.java
index 6f314df..ba5862a 100644
--- a/src/com/android/gallery3d/data/Exif.java
+++ b/src/com/android/gallery3d/data/Exif.java
@@ -18,8 +18,8 @@
import android.util.Log;
-import java.io.InputStream;
import java.io.IOException;
+import java.io.InputStream;
public class Exif {
private static final String TAG = "CameraExif";
diff --git a/src/com/android/gallery3d/data/Face.java b/src/com/android/gallery3d/data/Face.java
index c5fd131..29af27f 100644
--- a/src/com/android/gallery3d/data/Face.java
+++ b/src/com/android/gallery3d/data/Face.java
@@ -45,22 +45,10 @@
return mPosition;
}
- public int getWidth() {
- return mPosition.right - mPosition.left;
- }
-
- public int getHeight() {
- return mPosition.bottom - mPosition.top;
- }
-
public String getName() {
return mName;
}
- public String getPersonId() {
- return mPersonId;
- }
-
@Override
public boolean equals(Object obj) {
if (obj instanceof Face) {
diff --git a/src/com/android/gallery3d/data/ImageCacheRequest.java b/src/com/android/gallery3d/data/ImageCacheRequest.java
index 104ff48..81660c9 100644
--- a/src/com/android/gallery3d/data/ImageCacheRequest.java
+++ b/src/com/android/gallery3d/data/ImageCacheRequest.java
@@ -16,15 +16,15 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.data.ImageCacheService.ImageData;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.data.BytesBufferPool.BytesBuffer;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
abstract class ImageCacheRequest implements Job<Bitmap> {
private static final String TAG = "ImageCacheRequest";
@@ -41,48 +41,56 @@
mTargetSize = targetSize;
}
+ @Override
public Bitmap run(JobContext jc) {
String debugTag = mPath + "," +
((mType == MediaItem.TYPE_THUMBNAIL) ? "THUMB" :
(mType == MediaItem.TYPE_MICROTHUMBNAIL) ? "MICROTHUMB" : "?");
ImageCacheService cacheService = mApplication.getImageCacheService();
- ImageData data = cacheService.getImageData(mPath, mType);
+ BytesBuffer buffer = MediaItem.getBytesBufferPool().get();
+ try {
+ boolean found = cacheService.getImageData(mPath, mType, buffer);
+ if (jc.isCancelled()) return null;
+ if (found) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ Bitmap bitmap;
+ if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
+ bitmap = MediaItem.getMicroThumbPool().decode(jc,
+ buffer.data, buffer.offset, buffer.length, options);
+ } else {
+ bitmap = MediaItem.getThumbPool().decode(jc,
+ buffer.data, buffer.offset, buffer.length, options);
+ }
+ if (bitmap == null && !jc.isCancelled()) {
+ Log.w(TAG, "decode cached failed " + debugTag);
+ }
+ return bitmap;
+ }
+ } finally {
+ MediaItem.getBytesBufferPool().recycle(buffer);
+ }
+ Bitmap bitmap = onDecodeOriginal(jc, mType);
if (jc.isCancelled()) return null;
- if (data != null) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- Bitmap bitmap = DecodeUtils.requestDecode(jc, data.mData,
- data.mOffset, data.mData.length - data.mOffset, options);
- if (bitmap == null && !jc.isCancelled()) {
- Log.w(TAG, "decode cached failed " + debugTag);
- }
- return bitmap;
- } else {
- Bitmap bitmap = onDecodeOriginal(jc, mType);
- if (jc.isCancelled()) return null;
-
- if (bitmap == null) {
- Log.w(TAG, "decode orig failed " + debugTag);
- return null;
- }
-
- if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
- bitmap = BitmapUtils.resizeDownAndCropCenter(bitmap,
- mTargetSize, true);
- } else {
- bitmap = BitmapUtils.resizeDownBySideLength(bitmap,
- mTargetSize, true);
- }
- if (jc.isCancelled()) return null;
-
- byte[] array = BitmapUtils.compressBitmap(bitmap);
- if (jc.isCancelled()) return null;
-
- cacheService.putImageData(mPath, mType, array);
- return bitmap;
+ if (bitmap == null) {
+ Log.w(TAG, "decode orig failed " + debugTag);
+ return null;
}
+
+ if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
+ bitmap = BitmapUtils.resizeAndCropCenter(bitmap, mTargetSize, true);
+ } else {
+ bitmap = BitmapUtils.resizeDownBySideLength(bitmap, mTargetSize, true);
+ }
+ if (jc.isCancelled()) return null;
+
+ byte[] array = BitmapUtils.compressToBytes(bitmap);
+ if (jc.isCancelled()) return null;
+
+ cacheService.putImageData(mPath, mType, array);
+ return bitmap;
}
public abstract Bitmap onDecodeOriginal(JobContext jc, int targetSize);
diff --git a/src/com/android/gallery3d/data/ImageCacheService.java b/src/com/android/gallery3d/data/ImageCacheService.java
index 3adce13..0e79313 100644
--- a/src/com/android/gallery3d/data/ImageCacheService.java
+++ b/src/com/android/gallery3d/data/ImageCacheService.java
@@ -16,13 +16,15 @@
package com.android.gallery3d.data;
+import android.content.Context;
+
import com.android.gallery3d.common.BlobCache;
+import com.android.gallery3d.common.BlobCache.LookupRequest;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.BytesBufferPool.BytesBuffer;
import com.android.gallery3d.util.CacheManager;
import com.android.gallery3d.util.GalleryUtils;
-import android.content.Context;
-
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -33,7 +35,7 @@
private static final String IMAGE_CACHE_FILE = "imgcache";
private static final int IMAGE_CACHE_MAX_ENTRIES = 5000;
private static final int IMAGE_CACHE_MAX_BYTES = 200 * 1024 * 1024;
- private static final int IMAGE_CACHE_VERSION = 3;
+ private static final int IMAGE_CACHE_VERSION = 4;
private BlobCache mCache;
@@ -43,32 +45,35 @@
IMAGE_CACHE_VERSION);
}
- public static class ImageData {
- public ImageData(byte[] data, int offset) {
- mData = data;
- mOffset = offset;
- }
- public byte[] mData;
- public int mOffset;
- }
-
- public ImageData getImageData(Path path, int type) {
+ /**
+ * Gets the cached image data for the given <code>path</code> and <code>type</code>.
+ *
+ * The image data will be stored in <code>buffer.data</code>, started from
+ * <code>buffer.offset</code> for <code>buffer.length</code> bytes. If the
+ * buffer.data is not big enough, a new byte array will be allocated and returned.
+ *
+ * @return true if the image data is found; false if not found.
+ */
+ public boolean getImageData(Path path, int type, BytesBuffer buffer) {
byte[] key = makeKey(path, type);
long cacheKey = Utils.crc64Long(key);
try {
- byte[] value = null;
+ LookupRequest request = new LookupRequest();
+ request.key = cacheKey;
+ request.buffer = buffer.data;
synchronized (mCache) {
- value = mCache.lookup(cacheKey);
+ if (!mCache.lookup(request)) return false;
}
- if (value == null) return null;
- if (isSameKey(key, value)) {
- int offset = key.length;
- return new ImageData(value, offset);
+ if (isSameKey(key, request.buffer)) {
+ buffer.data = request.buffer;
+ buffer.offset = key.length;
+ buffer.length = request.length - buffer.offset;
+ return true;
}
} catch (IOException ex) {
// ignore.
}
- return null;
+ return false;
}
public void putImageData(Path path, int type, byte[] value) {
diff --git a/src/com/android/gallery3d/data/LocalAlbum.java b/src/com/android/gallery3d/data/LocalAlbum.java
index 5bd4398..117dbb6 100644
--- a/src/com/android/gallery3d/data/LocalAlbum.java
+++ b/src/com/android/gallery3d/data/LocalAlbum.java
@@ -16,18 +16,22 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.content.ContentResolver;
+import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
+import android.provider.MediaStore;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.Video;
import android.provider.MediaStore.Video.VideoColumns;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.MediaSetUtils;
+
import java.util.ArrayList;
// LocalAlbumSet lists all media items in one bucket on local storage.
@@ -45,7 +49,7 @@
private final GalleryApp mApplication;
private final ContentResolver mResolver;
private final int mBucketId;
- private final String mBucketName;
+ private final String mName;
private final boolean mIsImage;
private final ChangeNotifier mNotifier;
private final Path mItemPath;
@@ -57,7 +61,7 @@
mApplication = application;
mResolver = application.getContentResolver();
mBucketId = bucketId;
- mBucketName = name;
+ mName = getLocalizedName(application.getResources(), bucketId, name);
mIsImage = isImage;
if (isImage) {
@@ -87,6 +91,19 @@
}
@Override
+ public Uri getContentUri() {
+ if (mIsImage) {
+ return MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()
+ .appendQueryParameter(LocalSource.KEY_BUCKET_ID,
+ String.valueOf(mBucketId)).build();
+ } else {
+ return MediaStore.Video.Media.EXTERNAL_CONTENT_URI.buildUpon()
+ .appendQueryParameter(LocalSource.KEY_BUCKET_ID,
+ String.valueOf(mBucketId)).build();
+ }
+ }
+
+ @Override
public ArrayList<MediaItem> getMediaItem(int start, int count) {
DataManager dataManager = mApplication.getDataManager();
Uri uri = mBaseUri.buildUpon()
@@ -221,7 +238,7 @@
@Override
public String getName() {
- return mBucketName;
+ return mName;
}
@Override
@@ -249,4 +266,19 @@
public boolean isLeafAlbum() {
return true;
}
+
+ private static String getLocalizedName(Resources res, int bucketId,
+ String name) {
+ if (bucketId == MediaSetUtils.CAMERA_BUCKET_ID) {
+ return res.getString(R.string.folder_camera);
+ } else if (bucketId == MediaSetUtils.DOWNLOAD_BUCKET_ID) {
+ return res.getString(R.string.folder_download);
+ } else if (bucketId == MediaSetUtils.IMPORTED_BUCKET_ID) {
+ return res.getString(R.string.folder_imported);
+ } else if (bucketId == MediaSetUtils.SNAPSHOT_BUCKET_ID) {
+ return res.getString(R.string.folder_screenshot);
+ } else {
+ return name;
+ }
+ }
}
diff --git a/src/com/android/gallery3d/data/LocalAlbumSet.java b/src/com/android/gallery3d/data/LocalAlbumSet.java
index a8c1aa7..dbb5189 100644
--- a/src/com/android/gallery3d/data/LocalAlbumSet.java
+++ b/src/com/android/gallery3d/data/LocalAlbumSet.java
@@ -16,29 +16,33 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.GalleryUtils;
-import com.android.gallery3d.util.MediaSetUtils;
-
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Handler;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.Files.FileColumns;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.Video;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.MediaSetUtils;
+import com.android.gallery3d.util.ThreadPool;
+import com.android.gallery3d.util.ThreadPool.JobContext;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Comparator;
-import java.util.HashSet;
// LocalAlbumSet lists all image or video albums in the local storage.
// The path should be "/local/image", "local/video" or "/local/all"
-public class LocalAlbumSet extends MediaSet {
+public class LocalAlbumSet extends MediaSet
+ implements FutureListener<ArrayList<MediaSet>> {
public static final Path PATH_ALL = Path.fromString("/local/all");
public static final Path PATH_IMAGE = Path.fromString("/local/image");
public static final Path PATH_VIDEO = Path.fromString("/local/video");
@@ -95,10 +99,15 @@
private final ChangeNotifier mNotifierImage;
private final ChangeNotifier mNotifierVideo;
private final String mName;
+ private final Handler mHandler;
+
+ private Future<ArrayList<MediaSet>> mLoadTask;
+ private ArrayList<MediaSet> mLoadBuffer;
public LocalAlbumSet(Path path, GalleryApp application) {
super(path, nextVersionNumber());
mApplication = application;
+ mHandler = new Handler(application.getMainLooper());
mType = getTypeFromPath(path);
mNotifierImage = new ChangeNotifier(this, mWatchUriImage, application);
mNotifierVideo = new ChangeNotifier(this, mWatchUriVideo, application);
@@ -111,10 +120,7 @@
if (name.length < 2) {
throw new IllegalArgumentException(path.toString());
}
- if ("all".equals(name[1])) return MEDIA_TYPE_ALL;
- if ("image".equals(name[1])) return MEDIA_TYPE_IMAGE;
- if ("video".equals(name[1])) return MEDIA_TYPE_VIDEO;
- throw new IllegalArgumentException(path.toString());
+ return getTypeFromString(name[1]);
}
@Override
@@ -132,7 +138,16 @@
return mName;
}
- private BucketEntry[] loadBucketEntries(Cursor cursor) {
+ private BucketEntry[] loadBucketEntries(JobContext jc) {
+ Uri uri = mBaseUri;
+
+ Log.v("DebugLoadingTime", "start quering media provider");
+ Cursor cursor = mApplication.getContentResolver().query(
+ uri, PROJECTION_BUCKET, BUCKET_GROUP_BY, null, BUCKET_ORDER_BY);
+ if (cursor == null) {
+ Log.w(TAG, "cannot open local database: " + uri);
+ return new BucketEntry[0];
+ }
ArrayList<BucketEntry> buffer = new ArrayList<BucketEntry>();
int typeBits = 0;
if ((mType & MEDIA_TYPE_IMAGE) != 0) {
@@ -151,7 +166,9 @@
buffer.add(entry);
}
}
+ if (jc.isCancelled()) return null;
}
+ Log.v("DebugLoadingTime", "got " + buffer.size() + " buckets");
} finally {
cursor.close();
}
@@ -166,62 +183,59 @@
return -1;
}
- @SuppressWarnings("unchecked")
- protected ArrayList<MediaSet> loadSubMediaSets() {
- // Note: it will be faster if we only select media_type and bucket_id.
- // need to test the performance if that is worth
+ private class AlbumsLoader implements ThreadPool.Job<ArrayList<MediaSet>> {
- Uri uri = mBaseUri;
- GalleryUtils.assertNotInRenderThread();
- Cursor cursor = mApplication.getContentResolver().query(
- uri, PROJECTION_BUCKET, BUCKET_GROUP_BY, null, BUCKET_ORDER_BY);
- if (cursor == null) {
- Log.w(TAG, "cannot open local database: " + uri);
- return new ArrayList<MediaSet>();
- }
- BucketEntry[] entries = loadBucketEntries(cursor);
- int offset = 0;
+ @Override
+ @SuppressWarnings("unchecked")
+ public ArrayList<MediaSet> run(JobContext jc) {
+ // Note: it will be faster if we only select media_type and bucket_id.
+ // need to test the performance if that is worth
+ BucketEntry[] entries = loadBucketEntries(jc);
- // Move camera and download bucket to the front, while keeping the
- // order of others.
- int index = findBucket(entries, MediaSetUtils.CAMERA_BUCKET_ID);
- if (index != -1) {
- circularShiftRight(entries, offset++, index);
- }
- index = findBucket(entries, MediaSetUtils.DOWNLOAD_BUCKET_ID);
- if (index != -1) {
- circularShiftRight(entries, offset++, index);
- }
+ if (jc.isCancelled()) return null;
- ArrayList<MediaSet> albums = new ArrayList<MediaSet>();
- DataManager dataManager = mApplication.getDataManager();
- for (BucketEntry entry : entries) {
- albums.add(getLocalAlbum(dataManager,
- mType, mPath, entry.bucketId, entry.bucketName));
+ int offset = 0;
+ // Move camera and download bucket to the front, while keeping the
+ // order of others.
+ int index = findBucket(entries, MediaSetUtils.CAMERA_BUCKET_ID);
+ if (index != -1) {
+ circularShiftRight(entries, offset++, index);
+ }
+ index = findBucket(entries, MediaSetUtils.DOWNLOAD_BUCKET_ID);
+ if (index != -1) {
+ circularShiftRight(entries, offset++, index);
+ }
+
+ ArrayList<MediaSet> albums = new ArrayList<MediaSet>();
+ DataManager dataManager = mApplication.getDataManager();
+ for (BucketEntry entry : entries) {
+ MediaSet album = getLocalAlbum(dataManager,
+ mType, mPath, entry.bucketId, entry.bucketName);
+ albums.add(album);
+ }
+ return albums;
}
- for (int i = 0, n = albums.size(); i < n; ++i) {
- albums.get(i).reload();
- }
- return albums;
}
private MediaSet getLocalAlbum(
DataManager manager, int type, Path parent, int id, String name) {
- Path path = parent.getChild(id);
- MediaObject object = manager.peekMediaObject(path);
- if (object != null) return (MediaSet) object;
- switch (type) {
- case MEDIA_TYPE_IMAGE:
- return new LocalAlbum(path, mApplication, id, true, name);
- case MEDIA_TYPE_VIDEO:
- return new LocalAlbum(path, mApplication, id, false, name);
- case MEDIA_TYPE_ALL:
- Comparator<MediaItem> comp = DataManager.sDateTakenComparator;
- return new LocalMergeAlbum(path, comp, new MediaSet[] {
- getLocalAlbum(manager, MEDIA_TYPE_IMAGE, PATH_IMAGE, id, name),
- getLocalAlbum(manager, MEDIA_TYPE_VIDEO, PATH_VIDEO, id, name)});
+ synchronized (DataManager.LOCK) {
+ Path path = parent.getChild(id);
+ MediaObject object = manager.peekMediaObject(path);
+ if (object != null) return (MediaSet) object;
+ switch (type) {
+ case MEDIA_TYPE_IMAGE:
+ return new LocalAlbum(path, mApplication, id, true, name);
+ case MEDIA_TYPE_VIDEO:
+ return new LocalAlbum(path, mApplication, id, false, name);
+ case MEDIA_TYPE_ALL:
+ Comparator<MediaItem> comp = DataManager.sDateTakenComparator;
+ return new LocalMergeAlbum(path, comp, new MediaSet[] {
+ getLocalAlbum(manager, MEDIA_TYPE_IMAGE, PATH_IMAGE, id, name),
+ getLocalAlbum(manager, MEDIA_TYPE_VIDEO, PATH_VIDEO, id, name)}, id);
+ }
+ throw new IllegalArgumentException(String.valueOf(type));
}
- throw new IllegalArgumentException(String.valueOf(type));
}
public static String getBucketName(ContentResolver resolver, int bucketId) {
@@ -247,15 +261,39 @@
}
@Override
- public long reload() {
+ // synchronized on this function for
+ // 1. Prevent calling reload() concurrently.
+ // 2. Prevent calling onFutureDone() and reload() concurrently
+ public synchronized long reload() {
// "|" is used instead of "||" because we want to clear both flags.
if (mNotifierImage.isDirty() | mNotifierVideo.isDirty()) {
+ if (mLoadTask != null) mLoadTask.cancel();
+ mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this);
+ }
+ if (mLoadBuffer != null) {
+ mAlbums = mLoadBuffer;
+ mLoadBuffer = null;
+ for (MediaSet album : mAlbums) {
+ album.reload();
+ }
mDataVersion = nextVersionNumber();
- mAlbums = loadSubMediaSets();
}
return mDataVersion;
}
+ @Override
+ public synchronized void onFutureDone(Future<ArrayList<MediaSet>> future) {
+ if (mLoadTask != future) return; // ignore, wait for the latest task
+ mLoadBuffer = future.get();
+ if (mLoadBuffer == null) mLoadBuffer = new ArrayList<MediaSet>();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyContentChanged();
+ }
+ });
+ }
+
// For debug only. Fake there is a ContentObserver.onChange() event.
void fakeChange() {
mNotifierImage.fakeChange();
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index fa3ece3..4f2797e 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -16,13 +16,6 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.util.GalleryUtils;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-import com.android.gallery3d.util.UpdateHelper;
-
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
@@ -35,6 +28,13 @@
import android.provider.MediaStore.Images.ImageColumns;
import android.util.Log;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+import com.android.gallery3d.util.UpdateHelper;
+
import java.io.File;
import java.io.IOException;
@@ -159,14 +159,15 @@
LocalImageRequest(GalleryApp application, Path path, int type,
String localFilePath) {
- super(application, path, type, getTargetSize(type));
+ super(application, path, type, MediaItem.getTargetSize(type));
mLocalFilePath = localFilePath;
}
@Override
- public Bitmap onDecodeOriginal(JobContext jc, int type) {
+ public Bitmap onDecodeOriginal(JobContext jc, final int type) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ int targetSize = MediaItem.getTargetSize(type);
// try to decode from JPEG EXIF
if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
@@ -181,25 +182,13 @@
Log.w(TAG, "fail to get exif thumb", t);
}
if (thumbData != null) {
- Bitmap bitmap = DecodeUtils.requestDecodeIfBigEnough(
- jc, thumbData, options, getTargetSize(type));
+ Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
+ jc, thumbData, options, targetSize);
if (bitmap != null) return bitmap;
}
}
- return DecodeUtils.requestDecode(
- jc, mLocalFilePath, options, getTargetSize(type));
- }
- }
- static int getTargetSize(int type) {
- switch (type) {
- case TYPE_THUMBNAIL:
- return THUMBNAIL_TARGET_SIZE;
- case TYPE_MICROTHUMBNAIL:
- return MICROTHUMBNAIL_TARGET_SIZE;
- default:
- throw new RuntimeException(
- "should only request thumb/microthumb from cache");
+ return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type);
}
}
@@ -217,8 +206,7 @@
}
public BitmapRegionDecoder run(JobContext jc) {
- return DecodeUtils.requestCreateBitmapRegionDecoder(
- jc, mLocalFilePath, false);
+ return DecodeUtils.createBitmapRegionDecoder(jc, mLocalFilePath, false);
}
}
diff --git a/src/com/android/gallery3d/data/LocalMediaItem.java b/src/com/android/gallery3d/data/LocalMediaItem.java
index a76fedf..2749ebe 100644
--- a/src/com/android/gallery3d/data/LocalMediaItem.java
+++ b/src/com/android/gallery3d/data/LocalMediaItem.java
@@ -16,10 +16,10 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.database.Cursor;
+import com.android.gallery3d.util.GalleryUtils;
+
import java.text.DateFormat;
import java.util.Date;
diff --git a/src/com/android/gallery3d/data/LocalMergeAlbum.java b/src/com/android/gallery3d/data/LocalMergeAlbum.java
index bb796d5..1e34e78 100644
--- a/src/com/android/gallery3d/data/LocalMergeAlbum.java
+++ b/src/com/android/gallery3d/data/LocalMergeAlbum.java
@@ -16,6 +16,9 @@
package com.android.gallery3d.data;
+import android.net.Uri;
+import android.provider.MediaStore;
+
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Comparator;
@@ -38,16 +41,18 @@
private String mName;
private FetchCache[] mFetcher;
private int mSupportedOperation;
+ private int mBucketId;
// mIndex maps global position to the position of each underlying media sets.
private TreeMap<Integer, int[]> mIndex = new TreeMap<Integer, int[]>();
public LocalMergeAlbum(
- Path path, Comparator<MediaItem> comparator, MediaSet[] sources) {
+ Path path, Comparator<MediaItem> comparator, MediaSet[] sources, int bucketId) {
super(path, INVALID_DATA_VERSION);
mComparator = comparator;
mSources = sources;
mName = sources.length == 0 ? "" : sources[0].getName();
+ mBucketId = bucketId;
for (MediaSet set : mSources) {
set.addContentListener(this);
}
@@ -76,6 +81,12 @@
}
@Override
+ public Uri getContentUri() {
+ return MediaStore.Files.getContentUri("external").buildUpon().appendQueryParameter(
+ LocalSource.KEY_BUCKET_ID, String.valueOf(mBucketId)).build();
+ }
+
+ @Override
public String getName() {
return mName;
}
diff --git a/src/com/android/gallery3d/data/LocalSource.java b/src/com/android/gallery3d/data/LocalSource.java
index 9bb561b..19b2fec 100644
--- a/src/com/android/gallery3d/data/LocalSource.java
+++ b/src/com/android/gallery3d/data/LocalSource.java
@@ -75,6 +75,8 @@
"external/images/media", LOCAL_IMAGE_ALBUM);
mUriMatcher.addURI(MediaStore.AUTHORITY,
"external/video/media", LOCAL_VIDEO_ALBUM);
+ mUriMatcher.addURI(MediaStore.AUTHORITY,
+ "external/file", LOCAL_ALL_ALBUM);
}
@Override
@@ -98,7 +100,7 @@
LocalAlbumSet.PATH_VIDEO.getChild(bucketId));
Comparator<MediaItem> comp = DataManager.sDateTakenComparator;
return new LocalMergeAlbum(
- path, comp, new MediaSet[] {imageSet, videoSet});
+ path, comp, new MediaSet[] {imageSet, videoSet}, bucketId);
}
case LOCAL_IMAGE_ITEM:
return new LocalImage(path, mApplication, mMatcher.getIntVar(0));
@@ -122,6 +124,7 @@
}
// The media type bit passed by the intent
+ private static final int MEDIA_TYPE_ALL = 0;
private static final int MEDIA_TYPE_IMAGE = 1;
private static final int MEDIA_TYPE_VIDEO = 4;
@@ -165,6 +168,9 @@
case LOCAL_VIDEO_ALBUM: {
return getAlbumPath(uri, MEDIA_TYPE_VIDEO);
}
+ case LOCAL_ALL_ALBUM: {
+ return getAlbumPath(uri, MEDIA_TYPE_ALL);
+ }
}
} catch (NumberFormatException e) {
Log.w(TAG, "uri: " + uri.toString(), e);
diff --git a/src/com/android/gallery3d/data/LocalVideo.java b/src/com/android/gallery3d/data/LocalVideo.java
index ca379c3..0ba59f5 100644
--- a/src/com/android/gallery3d/data/LocalVideo.java
+++ b/src/com/android/gallery3d/data/LocalVideo.java
@@ -16,26 +16,20 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.util.UpdateHelper;
-import com.android.gallery3d.util.GalleryUtils;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.net.Uri;
import android.provider.MediaStore.Video;
import android.provider.MediaStore.Video.VideoColumns;
-import java.io.File;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+import com.android.gallery3d.util.UpdateHelper;
// LocalVideo represents a video in the local storage.
public class LocalVideo extends LocalMediaItem {
@@ -73,7 +67,6 @@
};
private final GalleryApp mApplication;
- private static Bitmap sOverlay;
public int durationInSec;
@@ -148,7 +141,7 @@
LocalVideoRequest(GalleryApp application, Path path, int type,
String localFilePath) {
- super(application, path, type, LocalImage.getTargetSize(type));
+ super(application, path, type, MediaItem.getTargetSize(type));
mLocalFilePath = localFilePath;
}
diff --git a/src/com/android/gallery3d/data/LocationClustering.java b/src/com/android/gallery3d/data/LocationClustering.java
index 788060c..b36bd63 100644
--- a/src/com/android/gallery3d/data/LocationClustering.java
+++ b/src/com/android/gallery3d/data/LocationClustering.java
@@ -16,15 +16,16 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.R;
-import com.android.gallery3d.util.ReverseGeocoder;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
+import android.util.FloatMath;
import android.widget.Toast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.ReverseGeocoder;
+
import java.util.ArrayList;
class LocationClustering extends Clustering {
@@ -294,7 +295,7 @@
}
// step 5: calculate the final score
- float score = totalDistance * (float) Math.sqrt(realK);
+ float score = totalDistance * FloatMath.sqrt(realK);
if (score < bestScore) {
bestScore = score;
diff --git a/src/com/android/gallery3d/data/MediaDetails.java b/src/com/android/gallery3d/data/MediaDetails.java
index 1b56ac4..9663dd0 100644
--- a/src/com/android/gallery3d/data/MediaDetails.java
+++ b/src/com/android/gallery3d/data/MediaDetails.java
@@ -16,15 +16,15 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.R;
-
import android.media.ExifInterface;
+import com.android.gallery3d.R;
+
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.TreeMap;
import java.util.Map.Entry;
+import java.util.TreeMap;
public class MediaDetails implements Iterable<Entry<Integer, Object>> {
@SuppressWarnings("unused")
@@ -73,22 +73,6 @@
public boolean isFlashFired() {
return (mState & FLASH_FIRED_MASK) != 0;
}
-
- public int getFlashReturn() {
- return (mState & FLASH_RETURN_MASK) >> 1;
- }
-
- public int getFlashMode() {
- return (mState & FLASH_MODE_MASK) >> 3;
- }
-
- public boolean isFlashPresent() {
- return (mState & FLASH_FUNCTION_MASK) != 0;
- }
-
- public boolean isRedEyeModePresent() {
- return (mState & FLASH_RED_EYE_MASK) != 0;
- }
}
public void addDetail(int index, Object value) {
diff --git a/src/com/android/gallery3d/data/MediaItem.java b/src/com/android/gallery3d/data/MediaItem.java
index 1361232..f0f1af4 100644
--- a/src/com/android/gallery3d/data/MediaItem.java
+++ b/src/com/android/gallery3d/data/MediaItem.java
@@ -16,11 +16,12 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.util.ThreadPool.Job;
-
import android.graphics.Bitmap;
import android.graphics.BitmapRegionDecoder;
+import com.android.gallery3d.ui.ScreenNail;
+import com.android.gallery3d.util.ThreadPool.Job;
+
// MediaItem represents an image or a video item.
public abstract class MediaItem extends MediaObject {
// NOTE: These type numbers are stored in the image cache, so it should not
@@ -38,6 +39,15 @@
public static final String MIME_TYPE_JPEG = "image/jpeg";
+ private static final int BYTESBUFFE_POOL_SIZE = 4;
+ private static final int BYTESBUFFER_SIZE = 200 * 1024;
+
+ private static final BitmapPool sMicroThumbPool = new BitmapPool(
+ MICROTHUMBNAIL_TARGET_SIZE, MICROTHUMBNAIL_TARGET_SIZE, 16);
+ private static final BitmapPool sThumbPool = new BitmapPool(4);
+ private static final BytesBufferPool sMicroThumbBufferPool =
+ new BytesBufferPool(BYTESBUFFE_POOL_SIZE, BYTESBUFFER_SIZE);
+
// TODO: fix default value for latlng and change this.
public static final double INVALID_LATLNG = 0f;
@@ -89,4 +99,34 @@
// Returns 0, 0 if the information is not available.
public abstract int getWidth();
public abstract int getHeight();
+
+ // This is an alternative for requestImage() in PhotoPage. If this
+ // is implemented, you don't need to implement requestImage().
+ public ScreenNail getScreenNail() {
+ return null;
+ }
+
+ public static int getTargetSize(int type) {
+ switch (type) {
+ case TYPE_THUMBNAIL:
+ return THUMBNAIL_TARGET_SIZE;
+ case TYPE_MICROTHUMBNAIL:
+ return MICROTHUMBNAIL_TARGET_SIZE;
+ default:
+ throw new RuntimeException(
+ "should only request thumb/microthumb from cache");
+ }
+ }
+
+ public static BitmapPool getMicroThumbPool() {
+ return sMicroThumbPool;
+ }
+
+ public static BitmapPool getThumbPool() {
+ return sThumbPool;
+ }
+
+ public static BytesBufferPool getBytesBufferPool() {
+ return sMicroThumbBufferPool;
+ }
}
diff --git a/src/com/android/gallery3d/data/MediaObject.java b/src/com/android/gallery3d/data/MediaObject.java
index d0f1672..f78aa7a 100644
--- a/src/com/android/gallery3d/data/MediaObject.java
+++ b/src/com/android/gallery3d/data/MediaObject.java
@@ -44,6 +44,10 @@
public static final int MEDIA_TYPE_VIDEO = 4;
public static final int MEDIA_TYPE_ALL = MEDIA_TYPE_IMAGE | MEDIA_TYPE_VIDEO;
+ public static final String MEDIA_TYPE_IMAGE_STRING = "image";
+ public static final String MEDIA_TYPE_VIDEO_STRING = "video";
+ public static final String MEDIA_TYPE_ALL_STRING = "all";
+
// These are flags for cache() and return values for getCacheFlag():
public static final int CACHE_FLAG_NO = 0;
public static final int CACHE_FLAG_SCREENNAIL = 1;
@@ -127,4 +131,20 @@
public static synchronized long nextVersionNumber() {
return ++MediaObject.sVersionSerial;
}
+
+ public static int getTypeFromString(String s) {
+ if (MEDIA_TYPE_ALL_STRING.equals(s)) return MediaObject.MEDIA_TYPE_ALL;
+ if (MEDIA_TYPE_IMAGE_STRING.equals(s)) return MediaObject.MEDIA_TYPE_IMAGE;
+ if (MEDIA_TYPE_VIDEO_STRING.equals(s)) return MediaObject.MEDIA_TYPE_VIDEO;
+ throw new IllegalArgumentException(s);
+ }
+
+ public static String getTypeString(int type) {
+ switch (type) {
+ case MEDIA_TYPE_IMAGE: return MEDIA_TYPE_IMAGE_STRING;
+ case MEDIA_TYPE_VIDEO: return MEDIA_TYPE_VIDEO_STRING;
+ case MEDIA_TYPE_ALL: return MEDIA_TYPE_ALL_STRING;
+ }
+ throw new IllegalArgumentException();
+ }
}
diff --git a/src/com/android/gallery3d/data/MediaSource.java b/src/com/android/gallery3d/data/MediaSource.java
index ae98e0f..07479a7 100644
--- a/src/com/android/gallery3d/data/MediaSource.java
+++ b/src/com/android/gallery3d/data/MediaSource.java
@@ -16,10 +16,10 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.data.MediaSet.ItemConsumer;
-
import android.net.Uri;
+import com.android.gallery3d.data.MediaSet.ItemConsumer;
+
import java.util.ArrayList;
public abstract class MediaSource {
diff --git a/src/com/android/gallery3d/data/MtpClient.java b/src/com/android/gallery3d/data/MtpClient.java
index 6991c16..2d58df2 100644
--- a/src/com/android/gallery3d/data/MtpClient.java
+++ b/src/com/android/gallery3d/data/MtpClient.java
@@ -27,13 +27,10 @@
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.mtp.MtpDevice;
-import android.mtp.MtpDeviceInfo;
import android.mtp.MtpObjectInfo;
import android.mtp.MtpStorageInfo;
-import android.os.ParcelFileDescriptor;
import android.util.Log;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
diff --git a/src/com/android/gallery3d/data/MtpContext.java b/src/com/android/gallery3d/data/MtpContext.java
index 6528494..ee5cf7e 100644
--- a/src/com/android/gallery3d/data/MtpContext.java
+++ b/src/com/android/gallery3d/data/MtpContext.java
@@ -1,10 +1,6 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.R;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.content.Context;
-import android.hardware.usb.UsbDevice;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.mtp.MtpObjectInfo;
@@ -13,6 +9,9 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.util.GalleryUtils;
+
import java.io.File;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/gallery3d/data/MtpDevice.java b/src/com/android/gallery3d/data/MtpDevice.java
index e654583..f43ae2b 100644
--- a/src/com/android/gallery3d/data/MtpDevice.java
+++ b/src/com/android/gallery3d/data/MtpDevice.java
@@ -16,8 +16,6 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-
import android.hardware.usb.UsbDevice;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
@@ -25,6 +23,8 @@
import android.net.Uri;
import android.util.Log;
+import com.android.gallery3d.app.GalleryApp;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/src/com/android/gallery3d/data/MtpDeviceSet.java b/src/com/android/gallery3d/data/MtpDeviceSet.java
index 6521623..6dcb0d2 100644
--- a/src/com/android/gallery3d/data/MtpDeviceSet.java
+++ b/src/com/android/gallery3d/data/MtpDeviceSet.java
@@ -16,27 +16,37 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.util.MediaSetUtils;
-
import android.mtp.MtpDeviceInfo;
import android.net.Uri;
+import android.os.Handler;
import android.util.Log;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.MediaSetUtils;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// MtpDeviceSet -- MtpDevice -- MtpImage
-public class MtpDeviceSet extends MediaSet {
+public class MtpDeviceSet extends MediaSet
+ implements FutureListener<ArrayList<MediaSet>> {
private static final String TAG = "MtpDeviceSet";
private GalleryApp mApplication;
- private final ArrayList<MediaSet> mDeviceSet = new ArrayList<MediaSet>();
private final ChangeNotifier mNotifier;
private final MtpContext mMtpContext;
private final String mName;
+ private final Handler mHandler;
+
+ private Future<ArrayList<MediaSet>> mLoadTask;
+ private ArrayList<MediaSet> mDeviceSet = new ArrayList<MediaSet>();
+ private ArrayList<MediaSet> mLoadBuffer;
public MtpDeviceSet(Path path, GalleryApp application, MtpContext mtpContext) {
super(path, nextVersionNumber());
@@ -44,28 +54,32 @@
mNotifier = new ChangeNotifier(this, Uri.parse("mtp://"), application);
mMtpContext = mtpContext;
mName = application.getResources().getString(R.string.set_label_mtp_devices);
+ mHandler = new Handler(mApplication.getMainLooper());
}
- private void loadDevices() {
- DataManager dataManager = mApplication.getDataManager();
- // Enumerate all devices
- mDeviceSet.clear();
- List<android.mtp.MtpDevice> devices = mMtpContext.getMtpClient().getDeviceList();
- Log.v(TAG, "loadDevices: " + devices + ", size=" + devices.size());
- for (android.mtp.MtpDevice mtpDevice : devices) {
- int deviceId = mtpDevice.getDeviceId();
- Path childPath = mPath.getChild(deviceId);
- MtpDevice device = (MtpDevice) dataManager.peekMediaObject(childPath);
- if (device == null) {
- device = new MtpDevice(childPath, mApplication, deviceId, mMtpContext);
- }
- Log.d(TAG, "add device " + device);
- mDeviceSet.add(device);
- }
+ private class DevicesLoader implements Job<ArrayList<MediaSet>> {
+ @Override
+ public ArrayList<MediaSet> run(JobContext jc) {
+ DataManager dataManager = mApplication.getDataManager();
+ ArrayList<MediaSet> result = new ArrayList<MediaSet>();
- Collections.sort(mDeviceSet, MediaSetUtils.NAME_COMPARATOR);
- for (int i = 0, n = mDeviceSet.size(); i < n; i++) {
- mDeviceSet.get(i).reload();
+ // Enumerate all devices
+ List<android.mtp.MtpDevice> devices = mMtpContext.getMtpClient().getDeviceList();
+ Log.v(TAG, "loadDevices: " + devices + ", size=" + devices.size());
+ for (android.mtp.MtpDevice mtpDevice : devices) {
+ synchronized (DataManager.LOCK) {
+ int deviceId = mtpDevice.getDeviceId();
+ Path childPath = mPath.getChild(deviceId);
+ MtpDevice device = (MtpDevice) dataManager.peekMediaObject(childPath);
+ if (device == null) {
+ device = new MtpDevice(childPath, mApplication, deviceId, mMtpContext);
+ }
+ Log.d(TAG, "add device " + device);
+ result.add(device);
+ }
+ }
+ Collections.sort(result, MediaSetUtils.NAME_COMPARATOR);
+ return result;
}
}
@@ -99,11 +113,33 @@
}
@Override
- public long reload() {
+ public synchronized long reload() {
if (mNotifier.isDirty()) {
+ if (mLoadTask != null) mLoadTask.cancel();
+ mLoadTask = mApplication.getThreadPool().submit(new DevicesLoader(), this);
+ }
+ if (mLoadBuffer != null) {
+ mDeviceSet = mLoadBuffer;
+ mLoadBuffer = null;
+ for (MediaSet device : mDeviceSet) {
+ device.reload();
+ }
mDataVersion = nextVersionNumber();
- loadDevices();
}
return mDataVersion;
}
+
+ @Override
+ public synchronized void onFutureDone(Future<ArrayList<MediaSet>> future) {
+ if (future != mLoadTask) return;
+ mLoadBuffer = future.get();
+ if (mLoadBuffer == null) mLoadBuffer = new ArrayList<MediaSet>();
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ notifyContentChanged();
+ }
+ });
+ }
}
diff --git a/src/com/android/gallery3d/data/MtpImage.java b/src/com/android/gallery3d/data/MtpImage.java
index 211b2f2..96b4d9f 100644
--- a/src/com/android/gallery3d/data/MtpImage.java
+++ b/src/com/android/gallery3d/data/MtpImage.java
@@ -16,12 +16,6 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.provider.GalleryProvider;
-import com.android.gallery3d.util.ThreadPool;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapRegionDecoder;
@@ -30,6 +24,12 @@
import android.net.Uri;
import android.util.Log;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.provider.GalleryProvider;
+import com.android.gallery3d.util.ThreadPool;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
import java.text.DateFormat;
import java.util.Date;
@@ -84,7 +84,7 @@
Log.w(TAG, "decoding thumbnail failed");
return null;
}
- return DecodeUtils.requestDecode(jc, thumbnail, null);
+ return DecodeUtils.decode(jc, thumbnail, null);
}
};
}
@@ -95,7 +95,7 @@
public BitmapRegionDecoder run(JobContext jc) {
byte[] bytes = mMtpContext.getMtpClient().getObject(
UsbDevice.getDeviceName(mDeviceId), mObjectId, mObjectSize);
- return DecodeUtils.requestCreateBitmapRegionDecoder(
+ return DecodeUtils.createBitmapRegionDecoder(
jc, bytes, 0, bytes.length, false);
}
};
diff --git a/src/com/android/gallery3d/data/Path.java b/src/com/android/gallery3d/data/Path.java
index 3de1c7c..dfb9131 100644
--- a/src/com/android/gallery3d/data/Path.java
+++ b/src/com/android/gallery3d/data/Path.java
@@ -175,13 +175,20 @@
}
public String getPrefix() {
+ if (this == sRoot) return "";
+ return getPrefixPath().mSegment;
+ }
+
+ public Path getPrefixPath() {
synchronized (Path.class) {
Path current = this;
- if (current == sRoot) return "";
+ if (current == sRoot) {
+ throw new IllegalStateException();
+ }
while (current.mParent != sRoot) {
current = current.mParent;
}
- return current.mSegment;
+ return current;
}
}
@@ -190,15 +197,6 @@
return mSegment;
}
- public String getSuffix(int level) {
- // We don't need lock because mSegment and mParent are final.
- Path p = this;
- while (level-- != 0) {
- p = p.mParent;
- }
- return p.mSegment;
- }
-
// Below are for testing/debugging only
static void clearAll() {
synchronized (Path.class) {
diff --git a/src/com/android/gallery3d/data/SizeClustering.java b/src/com/android/gallery3d/data/SizeClustering.java
index 7e24b33..2dbe49d 100644
--- a/src/com/android/gallery3d/data/SizeClustering.java
+++ b/src/com/android/gallery3d/data/SizeClustering.java
@@ -16,11 +16,11 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.R;
-
import android.content.Context;
import android.content.res.Resources;
+import com.android.gallery3d.R;
+
import java.util.ArrayList;
public class SizeClustering extends Clustering {
diff --git a/src/com/android/gallery3d/data/SnailAlbum.java b/src/com/android/gallery3d/data/SnailAlbum.java
new file mode 100644
index 0000000..39467bb
--- /dev/null
+++ b/src/com/android/gallery3d/data/SnailAlbum.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.data;
+
+import java.util.ArrayList;
+
+// This is a simple MediaSet which contains only one MediaItem -- a SnailItem.
+public class SnailAlbum extends MediaSet {
+
+ private MediaItem mItem;
+
+ public SnailAlbum(Path path, MediaItem item) {
+ super(path, nextVersionNumber());
+ mItem = item;
+ }
+
+ @Override
+ public int getMediaItemCount() {
+ return 1;
+ }
+
+ @Override
+ public ArrayList<MediaItem> getMediaItem(int start, int count) {
+ ArrayList<MediaItem> result = new ArrayList<MediaItem>();
+
+ // If [start, start+count) contains the index 0, return the item.
+ if (start <= 0 && start + count > 0) {
+ result.add(mItem);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean isLeafAlbum() {
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "SnailAlbum";
+ }
+
+ @Override
+ public long reload() {
+ return mDataVersion;
+ }
+}
diff --git a/src/com/android/gallery3d/data/SnailItem.java b/src/com/android/gallery3d/data/SnailItem.java
new file mode 100644
index 0000000..2836a19
--- /dev/null
+++ b/src/com/android/gallery3d/data/SnailItem.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.data;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapRegionDecoder;
+
+import com.android.gallery3d.ui.ScreenNail;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+// SnailItem is a MediaItem which can provide a ScreenNail. This is
+// used so we can show an foreign component (like an
+// android.view.View) instead of a Bitmap.
+public class SnailItem extends MediaItem {
+ private ScreenNail mScreenNail;
+
+ public SnailItem(Path path, ScreenNail screenNail) {
+ super(path, nextVersionNumber());
+ mScreenNail = screenNail;
+ }
+
+ @Override
+ public Job<Bitmap> requestImage(int type) {
+ // nothing to return
+ return new Job<Bitmap>() {
+ public Bitmap run(JobContext jc) {
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public Job<BitmapRegionDecoder> requestLargeImage() {
+ // nothing to return
+ return new Job<BitmapRegionDecoder>() {
+ public BitmapRegionDecoder run(JobContext jc) {
+ return null;
+ }
+ };
+ }
+
+ // We do not provide requestImage or requestLargeImage, instead we
+ // provide a ScreenNail.
+ @Override
+ public ScreenNail getScreenNail() {
+ return mScreenNail;
+ }
+
+ @Override
+ public String getMimeType() {
+ return "";
+ }
+
+ // Returns width and height of the media item.
+ // Returns 0, 0 if the information is not available.
+ @Override
+ public int getWidth() {
+ return 0;
+ }
+
+ @Override
+ public int getHeight() {
+ return 0;
+ }
+}
diff --git a/src/com/android/gallery3d/data/SnailSource.java b/src/com/android/gallery3d/data/SnailSource.java
new file mode 100644
index 0000000..e74a8bb
--- /dev/null
+++ b/src/com/android/gallery3d/data/SnailSource.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.gallery3d.data;
+
+import android.util.SparseArray;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.ui.ScreenNail;
+
+public class SnailSource extends MediaSource {
+ private static final String TAG = "SnailSource";
+ private static final int SNAIL_ALBUM = 0;
+ private static final int SNAIL_ITEM = 1;
+
+ private GalleryApp mApplication;
+ private PathMatcher mMatcher;
+ private static int sNextId;
+ private static SparseArray<ScreenNail> sRegistry = new SparseArray<ScreenNail>();
+
+ public SnailSource(GalleryApp application) {
+ super("snail");
+ mApplication = application;
+ mMatcher = new PathMatcher();
+ mMatcher.add("/snail/set/*", SNAIL_ALBUM);
+ mMatcher.add("/snail/item/*", SNAIL_ITEM);
+ }
+
+ // The only path we accept is "/snail/set/id" and "/snail/item/id"
+ @Override
+ public MediaObject createMediaObject(Path path) {
+ DataManager dataManager = mApplication.getDataManager();
+ switch (mMatcher.match(path)) {
+ case SNAIL_ALBUM:
+ String itemPath = "/snail/item/" + mMatcher.getVar(0);
+ MediaItem item =
+ (MediaItem) dataManager.getMediaObject(itemPath);
+ return new SnailAlbum(path, item);
+ case SNAIL_ITEM: {
+ int id = mMatcher.getIntVar(0);
+ return new SnailItem(path, lookupScreenNail(id));
+ }
+ }
+ return null;
+ }
+
+ // Registers a ScreenNail and returns the id of it. You can obtain the Path
+ // of the MediaItem associated with the ScreenNail by getItemPath(), and the
+ // Path of the MediaSet containing that MediaItem by getSetPath().
+ public static synchronized int registerScreenNail(ScreenNail s) {
+ int id = sNextId++;
+ sRegistry.put(id, s);
+ return id;
+ }
+
+ public static Path getSetPath(int id) {
+ return Path.fromString("/snail/set").getChild(id);
+ }
+
+ public static Path getItemPath(int id) {
+ return Path.fromString("/snail/item").getChild(id);
+ }
+
+ public static synchronized void unregisterScreenNail(ScreenNail s) {
+ int index = sRegistry.indexOfValue(s);
+ sRegistry.removeAt(index);
+ }
+
+ private static synchronized ScreenNail lookupScreenNail(int id) {
+ return sRegistry.get(id);
+ }
+}
diff --git a/src/com/android/gallery3d/data/TagClustering.java b/src/com/android/gallery3d/data/TagClustering.java
index c873051..849f885 100644
--- a/src/com/android/gallery3d/data/TagClustering.java
+++ b/src/com/android/gallery3d/data/TagClustering.java
@@ -16,10 +16,10 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.R;
-
import android.content.Context;
+import com.android.gallery3d.R;
+
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
diff --git a/src/com/android/gallery3d/data/TimeClustering.java b/src/com/android/gallery3d/data/TimeClustering.java
index 1ccf14c..9a0ae1d 100644
--- a/src/com/android/gallery3d/data/TimeClustering.java
+++ b/src/com/android/gallery3d/data/TimeClustering.java
@@ -16,13 +16,13 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.content.Context;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.GalleryUtils;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
diff --git a/src/com/android/gallery3d/data/UriImage.java b/src/com/android/gallery3d/data/UriImage.java
index 8f91cc0..05850bb 100644
--- a/src/com/android/gallery3d/data/UriImage.java
+++ b/src/com/android/gallery3d/data/UriImage.java
@@ -16,13 +16,6 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.ThreadPool.CancelListener;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
@@ -32,6 +25,13 @@
import android.os.ParcelFileDescriptor;
import android.webkit.MimeTypeMap;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.ThreadPool.CancelListener;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -180,7 +180,7 @@
private class RegionDecoderJob implements Job<BitmapRegionDecoder> {
public BitmapRegionDecoder run(JobContext jc) {
if (!prepareInputFile(jc)) return null;
- BitmapRegionDecoder decoder = DecodeUtils.requestCreateBitmapRegionDecoder(
+ BitmapRegionDecoder decoder = DecodeUtils.createBitmapRegionDecoder(
jc, mFileDescriptor.getFileDescriptor(), false);
mWidth = decoder.getWidth();
mHeight = decoder.getHeight();
@@ -195,25 +195,24 @@
mType = type;
}
+ @Override
public Bitmap run(JobContext jc) {
if (!prepareInputFile(jc)) return null;
- int targetSize = LocalImage.getTargetSize(mType);
+ int targetSize = MediaItem.getTargetSize(mType);
Options options = new Options();
options.inPreferredConfig = Config.ARGB_8888;
- Bitmap bitmap = DecodeUtils.requestDecode(jc,
- mFileDescriptor.getFileDescriptor(), options, targetSize);
+ Bitmap bitmap = DecodeUtils.decodeThumbnail(jc,
+ mFileDescriptor.getFileDescriptor(), options, targetSize, mType);
+
if (jc.isCancelled() || bitmap == null) {
return null;
}
if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
- bitmap = BitmapUtils.resizeDownAndCropCenter(bitmap,
- targetSize, true);
+ bitmap = BitmapUtils.resizeAndCropCenter(bitmap, targetSize, true);
} else {
- bitmap = BitmapUtils.resizeDownBySideLength(bitmap,
- targetSize, true);
+ bitmap = BitmapUtils.resizeDownBySideLength(bitmap, targetSize, true);
}
-
return bitmap;
}
}
diff --git a/src/com/android/gallery3d/data/UriSource.java b/src/com/android/gallery3d/data/UriSource.java
index ac62b93..ac53dd5 100644
--- a/src/com/android/gallery3d/data/UriSource.java
+++ b/src/com/android/gallery3d/data/UriSource.java
@@ -16,10 +16,10 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-
import android.net.Uri;
+import com.android.gallery3d.app.GalleryApp;
+
import java.net.URLDecoder;
import java.net.URLEncoder;
diff --git a/src/com/android/gallery3d/gadget/LocalPhotoSource.java b/src/com/android/gallery3d/gadget/LocalPhotoSource.java
index ad77de5..6ec41f6 100644
--- a/src/com/android/gallery3d/gadget/LocalPhotoSource.java
+++ b/src/com/android/gallery3d/gadget/LocalPhotoSource.java
@@ -16,14 +16,6 @@
package com.android.gallery3d.gadget;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.ContentListener;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -34,6 +26,14 @@
import android.os.Handler;
import android.provider.MediaStore.Images.Media;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.util.GalleryUtils;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
diff --git a/src/com/android/gallery3d/gadget/MediaSetSource.java b/src/com/android/gallery3d/gadget/MediaSetSource.java
index c1687e0..786092b 100644
--- a/src/com/android/gallery3d/gadget/MediaSetSource.java
+++ b/src/com/android/gallery3d/gadget/MediaSetSource.java
@@ -16,16 +16,16 @@
package com.android.gallery3d.gadget;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Binder;
+
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.ContentListener;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.Binder;
-
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
index 814ede2..c18652d 100644
--- a/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
+++ b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
@@ -16,9 +16,6 @@
package com.android.gallery3d.gadget;
-import com.android.gallery3d.R;
-import com.android.gallery3d.gadget.WidgetDatabaseHelper.Entry;
-
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
@@ -30,6 +27,9 @@
import android.util.Log;
import android.widget.RemoteViews;
+import com.android.gallery3d.R;
+import com.android.gallery3d.gadget.WidgetDatabaseHelper.Entry;
+
public class PhotoAppWidgetProvider extends AppWidgetProvider {
private static final String TAG = "WidgetProvider";
diff --git a/src/com/android/gallery3d/gadget/WidgetClickHandler.java b/src/com/android/gallery3d/gadget/WidgetClickHandler.java
index 075644d..36575e4 100644
--- a/src/com/android/gallery3d/gadget/WidgetClickHandler.java
+++ b/src/com/android/gallery3d/gadget/WidgetClickHandler.java
@@ -16,9 +16,6 @@
package com.android.gallery3d.gadget;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.Gallery;
-
import android.app.Activity;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
@@ -27,6 +24,9 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.Gallery;
+
public class WidgetClickHandler extends Activity {
private static final String TAG = "PhotoAppWidgetClickHandler";
diff --git a/src/com/android/gallery3d/gadget/WidgetConfigure.java b/src/com/android/gallery3d/gadget/WidgetConfigure.java
index a871e24..5717657 100644
--- a/src/com/android/gallery3d/gadget/WidgetConfigure.java
+++ b/src/com/android/gallery3d/gadget/WidgetConfigure.java
@@ -16,11 +16,6 @@
package com.android.gallery3d.gadget;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.AlbumPicker;
-import com.android.gallery3d.app.CropImage;
-import com.android.gallery3d.app.DialogPicker;
-
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
@@ -30,6 +25,11 @@
import android.os.Bundle;
import android.widget.RemoteViews;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.AlbumPicker;
+import com.android.gallery3d.app.CropImage;
+import com.android.gallery3d.app.DialogPicker;
+
public class WidgetConfigure extends Activity {
@SuppressWarnings("unused")
private static final String TAG = "WidgetConfigure";
diff --git a/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java
index 1648784..b8ef7a7 100644
--- a/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java
+++ b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java
@@ -16,8 +16,6 @@
package com.android.gallery3d.gadget;
-import com.android.gallery3d.common.Utils;
-
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -28,6 +26,8 @@
import android.net.Uri;
import android.util.Log;
+import com.android.gallery3d.common.Utils;
+
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
diff --git a/src/com/android/gallery3d/gadget/WidgetService.java b/src/com/android/gallery3d/gadget/WidgetService.java
index a61831c..eba7403 100644
--- a/src/com/android/gallery3d/gadget/WidgetService.java
+++ b/src/com/android/gallery3d/gadget/WidgetService.java
@@ -16,13 +16,6 @@
package com.android.gallery3d.gadget;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.data.ContentListener;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.data.Path;
-
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -30,6 +23,13 @@
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
+
public class WidgetService extends RemoteViewsService {
@SuppressWarnings("unused")
diff --git a/src/com/android/gallery3d/gadget/WidgetSource.java b/src/com/android/gallery3d/gadget/WidgetSource.java
index 8b8eb79..92874c7 100644
--- a/src/com/android/gallery3d/gadget/WidgetSource.java
+++ b/src/com/android/gallery3d/gadget/WidgetSource.java
@@ -16,11 +16,11 @@
package com.android.gallery3d.gadget;
-import com.android.gallery3d.data.ContentListener;
-
import android.graphics.Bitmap;
import android.net.Uri;
+import com.android.gallery3d.data.ContentListener;
+
public interface WidgetSource {
public int size();
public Bitmap getImage(int index);
diff --git a/src/com/android/gallery3d/gadget/WidgetTypeChooser.java b/src/com/android/gallery3d/gadget/WidgetTypeChooser.java
index c4bca60..1694f1c 100644
--- a/src/com/android/gallery3d/gadget/WidgetTypeChooser.java
+++ b/src/com/android/gallery3d/gadget/WidgetTypeChooser.java
@@ -16,8 +16,6 @@
package com.android.gallery3d.gadget;
-import com.android.gallery3d.R;
-
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -27,6 +25,8 @@
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
+import com.android.gallery3d.R;
+
public class WidgetTypeChooser extends Activity {
private OnCheckedChangeListener mListener = new OnCheckedChangeListener() {
diff --git a/src/com/android/gallery3d/gadget/WidgetUtils.java b/src/com/android/gallery3d/gadget/WidgetUtils.java
index b194c7d..c20c186 100644
--- a/src/com/android/gallery3d/gadget/WidgetUtils.java
+++ b/src/com/android/gallery3d/gadget/WidgetUtils.java
@@ -16,18 +16,18 @@
package com.android.gallery3d.gadget;
-import com.android.gallery3d.R;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.util.ThreadPool;
-
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Bitmap.Config;
import android.util.Log;
+import com.android.gallery3d.R;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.util.ThreadPool;
+
public class WidgetUtils {
private static final String TAG = "WidgetUtils";
diff --git a/src/com/android/gallery3d/photoeditor/BitmapUtils.java b/src/com/android/gallery3d/photoeditor/BitmapUtils.java
index a9fa758..9b41143 100644
--- a/src/com/android/gallery3d/photoeditor/BitmapUtils.java
+++ b/src/com/android/gallery3d/photoeditor/BitmapUtils.java
@@ -21,11 +21,8 @@
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
import android.graphics.Matrix;
-import android.graphics.Paint;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.net.Uri;
import android.provider.MediaStore.Images.ImageColumns;
import android.util.Log;
@@ -57,40 +54,8 @@
this.context = context;
}
- /**
- * Creates a mutable bitmap from subset of source bitmap, transformed by the optional matrix.
- */
- private static Bitmap createBitmap(
- Bitmap source, int x, int y, int width, int height, Matrix m) {
- // Re-implement Bitmap createBitmap() to always return a mutable bitmap.
- Canvas canvas = new Canvas();
-
- Bitmap bitmap;
- Paint paint;
- if ((m == null) || m.isIdentity()) {
- bitmap = Bitmap.createBitmap(width, height, source.getConfig());
- paint = null;
- } else {
- RectF rect = new RectF(0, 0, width, height);
- m.mapRect(rect);
- bitmap = Bitmap.createBitmap(
- Math.round(rect.width()), Math.round(rect.height()), source.getConfig());
-
- canvas.translate(-rect.left, -rect.top);
- canvas.concat(m);
-
- paint = new Paint(Paint.FILTER_BITMAP_FLAG);
- if (!m.rectStaysRect()) {
- paint.setAntiAlias(true);
- }
- }
- bitmap.setDensity(source.getDensity());
- canvas.setBitmap(bitmap);
-
- Rect srcBounds = new Rect(x, y, x + width, y + height);
- RectF dstBounds = new RectF(0, 0, width, height);
- canvas.drawBitmap(source, srcBounds, dstBounds, paint);
- return bitmap;
+ private static Bitmap createBitmap(Bitmap source, Matrix m) {
+ return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), m, true);
}
private void closeStream(Closeable stream) {
@@ -143,7 +108,7 @@
}
/**
- * Decodes bitmap (maybe immutable) that keeps aspect-ratio and spans most within the bounds.
+ * Decodes bitmap that keeps aspect-ratio and spans most within the bounds.
*/
private Bitmap decodeBitmap(Uri uri, int width, int height) {
InputStream is = null;
@@ -184,8 +149,7 @@
if (scale < 1) {
Matrix m = new Matrix();
m.setScale(scale, scale);
- Bitmap transformed = createBitmap(
- bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m);
+ Bitmap transformed = createBitmap(bitmap, m);
bitmap.recycle();
return transformed;
}
@@ -194,7 +158,7 @@
}
/**
- * Gets decoded bitmap that keeps orientation as well.
+ * Gets decoded bitmap (maybe immutable) that keeps orientation as well.
*/
public Bitmap getBitmap(Uri uri, int width, int height) {
Bitmap bitmap = decodeBitmap(uri, width, height);
@@ -205,8 +169,7 @@
if (orientation != 0) {
Matrix m = new Matrix();
m.setRotate(orientation);
- Bitmap transformed = createBitmap(
- bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m);
+ Bitmap transformed = createBitmap(bitmap, m);
bitmap.recycle();
return transformed;
}
@@ -218,15 +181,13 @@
* Saves the bitmap by given directory, filename, and format; if the directory is given null,
* then saves it under the cache directory.
*/
- public File saveBitmap(
- Bitmap bitmap, String directory, String filename, CompressFormat format) {
+ public File saveBitmap(Bitmap bitmap, File directory, String filename, CompressFormat format) {
if (directory == null) {
- directory = context.getCacheDir().getAbsolutePath();
+ directory = context.getCacheDir();
} else {
// Check if the given directory exists or try to create it.
- File file = new File(directory);
- if (!file.isDirectory() && !file.mkdirs()) {
+ if (!directory.isDirectory() && !directory.mkdirs()) {
return null;
}
}
diff --git a/src/com/android/gallery3d/photoeditor/EffectsBar.java b/src/com/android/gallery3d/photoeditor/EffectsBar.java
index 4075404..fad0b90 100644
--- a/src/com/android/gallery3d/photoeditor/EffectsBar.java
+++ b/src/com/android/gallery3d/photoeditor/EffectsBar.java
@@ -22,11 +22,9 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
-import android.widget.TextView;
import com.android.gallery3d.R;
import com.android.gallery3d.photoeditor.actions.EffectAction;
-import com.android.gallery3d.photoeditor.actions.EffectToolFactory;
/**
* Effects bar that contains all effects and shows them in categorized views.
@@ -37,7 +35,6 @@
private FilterStack filterStack;
private EffectsMenu effectsMenu;
private View effectsGallery;
- private ViewGroup effectToolPanel;
private EffectAction activeEffect;
public EffectsBar(Context context, AttributeSet attrs) {
@@ -75,49 +72,40 @@
ViewGroup scrollView = (ViewGroup) effectsGallery.findViewById(R.id.scroll_view);
ViewGroup effects = (ViewGroup) inflater.inflate(effectsId, scrollView, false);
for (int i = 0; i < effects.getChildCount(); i++) {
- setupEffectListener((EffectAction) effects.getChildAt(i));
+ setupEffect((EffectAction) effects.getChildAt(i));
}
scrollView.addView(effects);
scrollView.scrollTo(0, 0);
addView(effectsGallery, 0);
}
- private void setupEffectListener(final EffectAction effect) {
- effect.setListener(new EffectAction.Listener() {
+ private void setupEffect(final EffectAction effect) {
+ effect.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick() {
+ public void onClick(View v) {
if (isEnabled()) {
// Set the clicked effect active before exiting effects-gallery.
activeEffect = effect;
exitEffectsGallery();
- // Create effect tool panel first before the factory could create tools within.
- createEffectToolPanel();
- activeEffect.begin(filterStack,
- new EffectToolFactory(effectToolPanel, inflater));
+ EffectAction.ActionListener listener = new EffectAction.ActionListener() {
+
+ @Override
+ public void onOk() {
+ exit(null);
+ }
+ };
+ activeEffect.begin(getRootView(), filterStack, listener);
}
}
-
- @Override
- public void onDone() {
- exit(null);
- }
});
}
- private void createEffectToolPanel() {
- effectToolPanel = (ViewGroup) inflater.inflate(
- R.layout.photoeditor_effect_tool_panel, this, false);
- ((TextView) effectToolPanel.findViewById(R.id.effect_label)).setText(activeEffect.name());
- addView(effectToolPanel, 0);
- }
-
private boolean exitEffectsGallery() {
if (effectsGallery != null) {
if (activeEffect != null) {
// Detach the active effect to prevent it stopping effects-gallery from gc.
- ViewGroup scrollView = (ViewGroup) effectsGallery.findViewById(R.id.scroll_view);
- ((ViewGroup) scrollView.getChildAt(0)).removeView(activeEffect);
+ ((ViewGroup) activeEffect.getParent()).removeView(activeEffect);
}
removeView(effectsGallery);
effectsGallery = null;
@@ -128,18 +116,13 @@
private boolean exitActiveEffect(final Runnable runnableOnDone) {
if (activeEffect != null) {
- SpinnerProgressDialog.showDialog();
+ final Toolbar toolbar = (Toolbar) getRootView().findViewById(R.id.toolbar);
+ toolbar.showSpinner();
activeEffect.end(new Runnable() {
@Override
public void run() {
- SpinnerProgressDialog.dismissDialog();
- View fullscreenTool = getRootView().findViewById(R.id.fullscreen_effect_tool);
- if (fullscreenTool != null) {
- ((ViewGroup) fullscreenTool.getParent()).removeView(fullscreenTool);
- }
- removeView(effectToolPanel);
- effectToolPanel = null;
+ toolbar.dismissSpinner();
activeEffect = null;
if (runnableOnDone != null) {
runnableOnDone.run();
diff --git a/src/com/android/gallery3d/photoeditor/EffectsMenu.java b/src/com/android/gallery3d/photoeditor/EffectsMenu.java
index 71614b1..6578849 100644
--- a/src/com/android/gallery3d/photoeditor/EffectsMenu.java
+++ b/src/com/android/gallery3d/photoeditor/EffectsMenu.java
@@ -51,13 +51,13 @@
}
public void setOnToggleListener(OnToggleListener listener) {
- setToggleRunnalbe(listener, R.id.exposure_button, R.layout.photoeditor_effects_exposure);
- setToggleRunnalbe(listener, R.id.artistic_button, R.layout.photoeditor_effects_artistic);
- setToggleRunnalbe(listener, R.id.color_button, R.layout.photoeditor_effects_color);
- setToggleRunnalbe(listener, R.id.fix_button, R.layout.photoeditor_effects_fix);
+ setToggleRunnable(listener, R.id.exposure_button, R.layout.photoeditor_effects_exposure);
+ setToggleRunnable(listener, R.id.artistic_button, R.layout.photoeditor_effects_artistic);
+ setToggleRunnable(listener, R.id.color_button, R.layout.photoeditor_effects_color);
+ setToggleRunnable(listener, R.id.fix_button, R.layout.photoeditor_effects_fix);
}
- private void setToggleRunnalbe(final OnToggleListener listener, final int toggleId,
+ private void setToggleRunnable(final OnToggleListener listener, final int toggleId,
final int effectsId) {
setClickRunnable(toggleId, new Runnable() {
diff --git a/src/com/android/gallery3d/photoeditor/ImageActionButton.java b/src/com/android/gallery3d/photoeditor/ImageActionButton.java
new file mode 100644
index 0000000..a919ac6
--- /dev/null
+++ b/src/com/android/gallery3d/photoeditor/ImageActionButton.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+/**
+ * Image buttons used in Action-bar and Effects-menu that can be grayed out when set disabled.
+ * (Text buttons are automatically grayed out when disabled; however, image buttons are not.)
+ */
+public class ImageActionButton extends ImageButton {
+
+ private static final float ENABLED_ALPHA = 1;
+ private static final float DISABLED_ALPHA = 0.28f;
+
+ public ImageActionButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ setAlpha(enabled ? ENABLED_ALPHA : DISABLED_ALPHA);
+ }
+}
diff --git a/src/com/android/gallery3d/photoeditor/PhotoEditor.java b/src/com/android/gallery3d/photoeditor/PhotoEditor.java
index 8f3990b..19e49c4 100644
--- a/src/com/android/gallery3d/photoeditor/PhotoEditor.java
+++ b/src/com/android/gallery3d/photoeditor/PhotoEditor.java
@@ -21,7 +21,6 @@
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
-import android.view.ViewGroup;
import com.android.gallery3d.R;
@@ -37,18 +36,19 @@
private FilterStack filterStack;
private ActionBar actionBar;
private EffectsBar effectsBar;
+ private Toolbar toolbar;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.photoeditor_main);
- SpinnerProgressDialog.initialize((ViewGroup) findViewById(R.id.toolbar));
Intent intent = getIntent();
if (Intent.ACTION_EDIT.equalsIgnoreCase(intent.getAction())) {
sourceUri = intent.getData();
}
+ toolbar = (Toolbar) findViewById(R.id.toolbar);
actionBar = (ActionBar) findViewById(R.id.action_bar);
filterStack = new FilterStack((PhotoView) findViewById(R.id.photo_view),
new FilterStack.StackListener() {
@@ -76,7 +76,7 @@
}
private void openPhoto() {
- SpinnerProgressDialog.showDialog();
+ toolbar.showSpinner();
LoadScreennailTask.Callback callback = new LoadScreennailTask.Callback() {
@Override
@@ -85,7 +85,7 @@
@Override
public void onDone() {
- SpinnerProgressDialog.dismissDialog();
+ toolbar.dismissSpinner();
effectsBar.setEnabled(result != null);
}
});
@@ -103,12 +103,12 @@
@Override
public void run() {
- SpinnerProgressDialog.showDialog();
+ toolbar.showSpinner();
OnDoneCallback callback = new OnDoneCallback() {
@Override
public void onDone() {
- SpinnerProgressDialog.dismissDialog();
+ toolbar.dismissSpinner();
}
};
if (undo) {
@@ -131,7 +131,7 @@
@Override
public void run() {
- SpinnerProgressDialog.showDialog();
+ toolbar.showSpinner();
filterStack.getOutputBitmap(new OnDoneBitmapCallback() {
@Override
@@ -140,7 +140,7 @@
@Override
public void onComplete(Uri result) {
- SpinnerProgressDialog.dismissDialog();
+ toolbar.dismissSpinner();
saveUri = result;
actionBar.updateSave(saveUri == null);
}
@@ -223,7 +223,7 @@
super.onPause();
filterStack.onPause();
// Dismiss any running progress dialog as all operations are paused.
- SpinnerProgressDialog.dismissDialog();
+ toolbar.dismissSpinner();
}
@Override
diff --git a/src/com/android/gallery3d/photoeditor/RendererUtils.java b/src/com/android/gallery3d/photoeditor/RendererUtils.java
index b92907d..3edcff5 100644
--- a/src/com/android/gallery3d/photoeditor/RendererUtils.java
+++ b/src/com/android/gallery3d/photoeditor/RendererUtils.java
@@ -19,6 +19,7 @@
import android.graphics.Bitmap;
import android.opengl.GLES20;
import android.opengl.GLUtils;
+import android.util.FloatMath;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -153,8 +154,8 @@
public static void setRenderToRotate(RenderContext context, int srcWidth, int srcHeight,
int dstWidth, int dstHeight, float degrees) {
float radian = -degrees * DEGREE_TO_RADIAN;
- float cosTheta = (float) Math.cos(radian);
- float sinTheta = (float) Math.sin(radian);
+ float cosTheta = FloatMath.cos(radian);
+ float sinTheta = FloatMath.sin(radian);
float cosWidth = cosTheta * srcWidth;
float sinWidth = sinTheta * srcWidth;
float cosHeight = cosTheta * srcHeight;
@@ -205,8 +206,8 @@
System.arraycopy(base, 0, vertices, 0, vertices.length);
if (horizontalDegrees % 180f != 0) {
float radian = (horizontalDegrees - horizontalRounds * 180) * DEGREE_TO_RADIAN;
- float cosTheta = (float) Math.cos(radian);
- float sinTheta = (float) Math.sin(radian);
+ float cosTheta = FloatMath.cos(radian);
+ float sinTheta = FloatMath.sin(radian);
float scale = length / (length + sinTheta * base[0]);
vertices[0] = cosTheta * base[0] * scale;
@@ -223,8 +224,8 @@
if (verticalDegrees % 180f != 0) {
float radian = (verticalDegrees - verticalRounds * 180) * DEGREE_TO_RADIAN;
- float cosTheta = (float) Math.cos(radian);
- float sinTheta = (float) Math.sin(radian);
+ float cosTheta = FloatMath.cos(radian);
+ float sinTheta = FloatMath.sin(radian);
float scale = length / (length + sinTheta * base[1]);
vertices[0] = base[0] * scale;
diff --git a/src/com/android/gallery3d/photoeditor/RestorableView.java b/src/com/android/gallery3d/photoeditor/RestorableView.java
index fc98741..705b412 100644
--- a/src/com/android/gallery3d/photoeditor/RestorableView.java
+++ b/src/com/android/gallery3d/photoeditor/RestorableView.java
@@ -32,9 +32,6 @@
*/
public abstract class RestorableView extends FrameLayout {
- private static final float ENABLED_ALPHA = 1;
- private static final float DISABLED_ALPHA = 0.47f;
-
private final HashMap<Integer, Runnable> clickRunnables = new HashMap<Integer, Runnable>();
private final HashSet<Integer> changedViews = new HashSet<Integer>();
private final LayoutInflater inflater;
@@ -92,9 +89,7 @@
}
public void setViewEnabled(int id, boolean enabled) {
- View view = findViewById(id);
- view.setEnabled(enabled);
- view.setAlpha(enabled ? ENABLED_ALPHA : DISABLED_ALPHA);
+ findViewById(id).setEnabled(enabled);
// Track views whose enabled status has been updated.
changedViews.add(id);
}
diff --git a/src/com/android/gallery3d/photoeditor/SaveCopyTask.java b/src/com/android/gallery3d/photoeditor/SaveCopyTask.java
index b7d5626..6b7b7c3 100644
--- a/src/com/android/gallery3d/photoeditor/SaveCopyTask.java
+++ b/src/com/android/gallery3d/photoeditor/SaveCopyTask.java
@@ -48,29 +48,24 @@
void onComplete(Uri result);
}
- private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
- private static final int INDEX_DATE_TAKEN = 0;
- private static final int INDEX_LATITUDE = 1;
- private static final int INDEX_LONGITUDE = 2;
+ private interface ContentResolverQueryCallback {
- private static final String[] IMAGE_PROJECTION = new String[] {
- ImageColumns.DATE_TAKEN,
- ImageColumns.LATITUDE,
- ImageColumns.LONGITUDE,
- };
+ void onCursorResult(Cursor cursor);
+ }
+
+ private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
private final Context context;
private final Uri sourceUri;
private final Callback callback;
- private final String albumName;
private final String saveFileName;
+ private File saveDirectory;
public SaveCopyTask(Context context, Uri sourceUri, Callback callback) {
this.context = context;
this.sourceUri = sourceUri;
this.callback = callback;
- albumName = context.getString(R.string.edited_photo_bucket_name);
saveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(
new Date(System.currentTimeMillis()));
}
@@ -85,7 +80,9 @@
return null;
}
Bitmap bitmap = params[0];
- File file = save(bitmap);
+ getSaveDirectory();
+ File file = new BitmapUtils(context).saveBitmap(
+ bitmap, saveDirectory, saveFileName, Bitmap.CompressFormat.JPEG);
Uri uri = (file != null) ? insertContent(file) : null;
bitmap.recycle();
return uri;
@@ -94,7 +91,7 @@
@Override
protected void onPostExecute(Uri result) {
String message = (result == null) ? context.getString(R.string.saving_failure)
- : context.getString(R.string.photo_saved, albumName);
+ : context.getString(R.string.photo_saved, saveDirectory.getName());
Toast toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
@@ -102,10 +99,36 @@
callback.onComplete(result);
}
- private File save(Bitmap bitmap) {
- String directory = Environment.getExternalStorageDirectory().toString() + "/" + albumName;
- return new BitmapUtils(context).saveBitmap(
- bitmap, directory, saveFileName, Bitmap.CompressFormat.JPEG);
+ private void querySource(String[] projection, ContentResolverQueryCallback callback) {
+ ContentResolver contentResolver = context.getContentResolver();
+ Cursor cursor = null;
+ try {
+ cursor = contentResolver.query(sourceUri, projection, null, null, null);
+ if ((cursor != null) && cursor.moveToNext()) {
+ callback.onCursorResult(cursor);
+ }
+ } catch (Exception e) {
+ // Ignore error for lacking the data column from the source.
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private void getSaveDirectory() {
+ querySource(new String[] { ImageColumns.DATA }, new ContentResolverQueryCallback () {
+
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ saveDirectory = new File(cursor.getString(0)).getParentFile();
+ }
+ });
+ // Use the default save directory if the source directory cannot be saved.
+ if ((saveDirectory == null) || !saveDirectory.canWrite()) {
+ saveDirectory = new File(Environment.getExternalStorageDirectory(),
+ context.getString(R.string.edited_photo_bucket_name));
+ }
}
/**
@@ -113,43 +136,39 @@
*/
private Uri insertContent(File file) {
long now = System.currentTimeMillis() / 1000;
- long dateTaken = now;
- double latitude = 0f;
- double longitude = 0f;
- ContentResolver contentResolver = context.getContentResolver();
- Cursor cursor = null;
- try {
- cursor = contentResolver.query(sourceUri, IMAGE_PROJECTION, null, null, null);
- if ((cursor != null) && cursor.moveToNext()) {
- dateTaken = cursor.getLong(INDEX_DATE_TAKEN);
- latitude = cursor.getDouble(INDEX_LATITUDE);
- longitude = cursor.getDouble(INDEX_LONGITUDE);
- }
- } catch (Exception e) {
- // Ignore error for lacking property columns from the source.
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- ContentValues values = new ContentValues();
+ final ContentValues values = new ContentValues();
values.put(Images.Media.TITLE, saveFileName);
values.put(Images.Media.DISPLAY_NAME, file.getName());
values.put(Images.Media.MIME_TYPE, "image/jpeg");
- values.put(Images.Media.DATE_TAKEN, dateTaken);
+ values.put(Images.Media.DATE_TAKEN, now);
values.put(Images.Media.DATE_MODIFIED, now);
values.put(Images.Media.DATE_ADDED, now);
values.put(Images.Media.ORIENTATION, 0);
values.put(Images.Media.DATA, file.getAbsolutePath());
values.put(Images.Media.SIZE, file.length());
- // TODO: Change || to && after the default location issue is fixed.
- if ((latitude != 0f) || (longitude != 0f)) {
- values.put(Images.Media.LATITUDE, latitude);
- values.put(Images.Media.LONGITUDE, longitude);
- }
- return contentResolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);
+ String[] projection = new String[] {
+ ImageColumns.DATE_TAKEN,
+ ImageColumns.LATITUDE,
+ ImageColumns.LONGITUDE,
+ };
+ querySource(projection, new ContentResolverQueryCallback() {
+
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
+
+ double latitude = cursor.getDouble(1);
+ double longitude = cursor.getDouble(2);
+ // TODO: Change || to && after the default location issue is fixed.
+ if ((latitude != 0f) || (longitude != 0f)) {
+ values.put(Images.Media.LATITUDE, latitude);
+ values.put(Images.Media.LONGITUDE, longitude);
+ }
+ }
+ });
+
+ return context.getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/SpinnerProgressDialog.java b/src/com/android/gallery3d/photoeditor/SpinnerProgressDialog.java
index 065075e..5bc544f 100644
--- a/src/com/android/gallery3d/photoeditor/SpinnerProgressDialog.java
+++ b/src/com/android/gallery3d/photoeditor/SpinnerProgressDialog.java
@@ -17,69 +17,69 @@
package com.android.gallery3d.photoeditor;
import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ProgressBar;
import com.android.gallery3d.R;
import java.util.ArrayList;
+import java.util.List;
/**
* Spinner model progress dialog that disables all tools for user interaction after it shows up and
- * and re-enables them after it dismisses; this class along with all its methods should be accessed
- * in only UI thread and allows only one instance at a time.
+ * and re-enables them after it dismisses.
*/
public class SpinnerProgressDialog extends Dialog {
- private static ViewGroup toolbar;
- private static SpinnerProgressDialog dialog;
- private final ArrayList<View> enabledTools = new ArrayList<View>();
+ /**
+ * Listener of touch events.
+ */
+ public interface OnTouchListener {
- public static void initialize(ViewGroup toolbar) {
- SpinnerProgressDialog.toolbar = toolbar;
+ public boolean onTouch(DialogInterface dialog, MotionEvent event);
}
- public static void showDialog() {
- // There should be only one progress dialog running at a time.
- if (dialog == null) {
- dialog = new SpinnerProgressDialog();
- dialog.setCancelable(false);
- dialog.show();
- // Disable enabled tools when showing spinner progress dialog.
- for (int i = 0; i < toolbar.getChildCount(); i++) {
- View view = toolbar.getChildAt(i);
- if (view.isEnabled()) {
- dialog.enabledTools.add(view);
- view.setEnabled(false);
- }
- }
- }
- }
+ private final List<View> enabledTools = new ArrayList<View>();
+ private final OnTouchListener listener;
- public static void dismissDialog() {
- if (dialog != null) {
- dialog.dismiss();
- // Enable tools that were disabled by this spinner progress dialog.
- for (View view : dialog.enabledTools) {
- view.setEnabled(true);
- }
- dialog = null;
- }
- }
-
- private SpinnerProgressDialog() {
- super(toolbar.getContext(), R.style.SpinnerProgressDialog);
- addContentView(new ProgressBar(toolbar.getContext()), new LayoutParams(
+ public SpinnerProgressDialog(Context context, List<View> tools, OnTouchListener listener) {
+ super(context, R.style.SpinnerProgressDialog);
+ addContentView(new ProgressBar(context), new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ setCancelable(false);
+
+ for (View view : tools) {
+ if (view.isEnabled()) {
+ enabledTools.add(view);
+ }
+ }
+ this.listener = listener;
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ // Disable enabled tools when showing spinner progress dialog.
+ for (View view : enabledTools) {
+ view.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ super.dismiss();
+ // Enable tools that were disabled by this spinner progress dialog.
+ for (View view : enabledTools) {
+ view.setEnabled(true);
+ }
}
@Override
public boolean onTouchEvent(MotionEvent event) {
- super.onTouchEvent(event);
- // Pass touch events to tools for killing idle even when the progress dialog is shown.
- return toolbar.dispatchTouchEvent(event);
+ return listener.onTouch(this, event);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/Toolbar.java b/src/com/android/gallery3d/photoeditor/Toolbar.java
index 45ec016..df68025 100644
--- a/src/com/android/gallery3d/photoeditor/Toolbar.java
+++ b/src/com/android/gallery3d/photoeditor/Toolbar.java
@@ -17,6 +17,7 @@
package com.android.gallery3d.photoeditor;
import android.content.Context;
+import android.content.DialogInterface;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
@@ -37,33 +38,75 @@
public class Toolbar extends RelativeLayout {
private final ToolbarIdleHandler idleHandler;
+ private final List<View> tools = new ArrayList<View>();
+ private SpinnerProgressDialog spinner;
public Toolbar(Context context, AttributeSet attrs) {
super(context, attrs);
- idleHandler = new ToolbarIdleHandler(context);
- setOnHierarchyChangeListener(idleHandler);
+ setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
+
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ // Photo-view isn't treated as a tool that responds to user events.
+ if (child.getId() != R.id.photo_view) {
+ tools.add(child);
+ }
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ tools.remove(child);
+ }
+ });
+
+ idleHandler = new ToolbarIdleHandler(context, tools);
idleHandler.killIdle();
}
+ public void showSpinner() {
+ // There should be only one progress spinner running at a time.
+ if (spinner == null) {
+ spinner = new SpinnerProgressDialog(getContext(), tools,
+ new SpinnerProgressDialog.OnTouchListener() {
+
+ @Override
+ public boolean onTouch(DialogInterface dialog, MotionEvent event) {
+ // Kill idle even when the progress dialog is shown.
+ idleHandler.killIdle();
+ return true;
+ }
+ });
+ spinner.show();
+ }
+ }
+
+ public void dismissSpinner() {
+ if (spinner != null) {
+ spinner.dismiss();
+ spinner = null;
+ }
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
idleHandler.killIdle();
return super.dispatchTouchEvent(ev);
}
- private static class ToolbarIdleHandler implements OnHierarchyChangeListener {
+ private static class ToolbarIdleHandler {
private static final int MAKE_IDLE = 1;
private static final int TIMEOUT_IDLE = 8000;
- private final List<View> childViews = new ArrayList<View>();
+ private final List<View> tools;
private final Handler mainHandler;
private final Animation fadeIn;
private final Animation fadeOut;
private boolean idle;
- public ToolbarIdleHandler(Context context) {
+ public ToolbarIdleHandler(Context context, final List<View> tools) {
+ this.tools = tools;
mainHandler = new Handler() {
@Override
@@ -72,7 +115,7 @@
case MAKE_IDLE:
if (!idle) {
idle = true;
- for (View view : childViews) {
+ for (View view : tools) {
view.startAnimation(fadeOut);
}
}
@@ -89,24 +132,11 @@
mainHandler.removeMessages(MAKE_IDLE);
if (idle) {
idle = false;
- for (View view : childViews) {
+ for (View view : tools) {
view.startAnimation(fadeIn);
}
}
mainHandler.sendEmptyMessageDelayed(MAKE_IDLE, TIMEOUT_IDLE);
}
-
- @Override
- public void onChildViewAdded(View parent, View child) {
- // All child views, except photo-view, will fade out on inactivity timeout.
- if (child.getId() != R.id.photo_view) {
- childViews.add(child);
- }
- }
-
- @Override
- public void onChildViewRemoved(View parent, View child) {
- childViews.remove(child);
- }
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/AbstractSeekBar.java b/src/com/android/gallery3d/photoeditor/actions/AbstractSeekBar.java
index 27a0bce..aaf0e5b 100644
--- a/src/com/android/gallery3d/photoeditor/actions/AbstractSeekBar.java
+++ b/src/com/android/gallery3d/photoeditor/actions/AbstractSeekBar.java
@@ -28,7 +28,7 @@
import com.android.gallery3d.R;
/**
- * Seek-bar base that implements a draggable thumb that fits seek-bar height.
+ * Seek-bar base that implements a draggable thumb that fits seek-bar's track height.
*/
abstract class AbstractSeekBar extends SeekBar {
@@ -38,22 +38,22 @@
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- // Scale the thumb to fit seek-bar height.
+ // Scale the thumb to fit seek-bar's track height.
Resources res = getResources();
Drawable thumb = res.getDrawable(R.drawable.photoeditor_seekbar_thumb);
+ int height = h - getPaddingTop() - getPaddingBottom();
+ int scaledWidth = thumb.getIntrinsicWidth() * height / thumb.getIntrinsicHeight();
- // Set the left/right padding to half width of the thumb drawn.
- int scaledWidth = thumb.getIntrinsicWidth() * h / thumb.getIntrinsicHeight();
- int padding = (scaledWidth + 1) / 2;
- setPadding(padding, 0, padding, 0);
-
- Bitmap bitmap = Bitmap.createBitmap(scaledWidth, h, Bitmap.Config.ARGB_8888);
+ Bitmap bitmap = Bitmap.createBitmap(scaledWidth, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
thumb.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
thumb.draw(canvas);
+ // The thumb should not extend out of the track per UX design.
setThumb(new BitmapDrawable(res, bitmap));
+ setThumbOffset(0);
+
+ // The thumb position is updated here after the thumb is changed.
+ super.onSizeChanged(w, h, oldw, oldh);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java b/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java
index 26b5f51..a419840 100644
--- a/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java
@@ -33,14 +33,10 @@
}
@Override
- public void doBegin() {
+ public void prepare() {
AutoFixFilter filter = new AutoFixFilter();
filter.setScale(DEFAULT_SCALE);
- notifyFilterChanged(filter, true);
- notifyDone();
- }
-
- @Override
- public void doEnd() {
+ notifyChanged(filter);
+ notifyOk();
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/ColorSeekBar.java b/src/com/android/gallery3d/photoeditor/actions/ColorSeekBar.java
index 5f9809b..41d1f24 100644
--- a/src/com/android/gallery3d/photoeditor/actions/ColorSeekBar.java
+++ b/src/com/android/gallery3d/photoeditor/actions/ColorSeekBar.java
@@ -42,7 +42,7 @@
}
private final int[] colors;
- private Bitmap background;
+ private Bitmap progressDrawable;
public ColorSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -61,46 +61,47 @@
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
- if (background != null) {
- background.recycle();
+ if (progressDrawable != null) {
+ progressDrawable.recycle();
}
- background = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(background);
+ int width = w - getPaddingLeft() - getPaddingRight();
+ int height = h - getPaddingTop() - getPaddingBottom();
+ progressDrawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(progressDrawable);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
- // Draw two half circles in the first and last colors at seek-bar left/right ends.
- int radius = getThumbOffset();
+ // Draw two half circles in the first and last colors at the left/right ends.
+ int radius = height / 2;
float left = radius;
- float right = w - radius;
- float cy = h / 2;
+ float right = width - radius;
canvas.save();
- canvas.clipRect(left, 0, right, h, Op.DIFFERENCE);
+ canvas.clipRect(left, 0, right, height, Op.DIFFERENCE);
paint.setColor(colors[0]);
- canvas.drawCircle(left, cy, radius, paint);
+ canvas.drawCircle(left, radius, radius, paint);
paint.setColor(colors[colors.length - 1]);
- canvas.drawCircle(right, cy, radius, paint);
+ canvas.drawCircle(right, radius, radius, paint);
canvas.restore();
// Draw color strips that make the thumb stop at every strip's center during seeking.
float strip = (right - left) / (colors.length - 1);
right = left + strip / 2;
paint.setColor(colors[0]);
- canvas.drawRect(left, 0, right, h, paint);
+ canvas.drawRect(left, 0, right, height, paint);
left = right;
for (int i = 1; i < colors.length - 1; i++) {
right = left + strip;
paint.setColor(colors[i]);
- canvas.drawRect(left, 0, right, h, paint);
+ canvas.drawRect(left, 0, right, height, paint);
left = right;
}
right = left + strip / 2;
paint.setColor(colors[colors.length - 1]);
- canvas.drawRect(left, 0, right, h, paint);
+ canvas.drawRect(left, 0, right, height, paint);
- setBackgroundDrawable(new BitmapDrawable(getResources(), background));
+ setProgressDrawable(new BitmapDrawable(getResources(), progressDrawable));
}
public void setOnColorChangeListener(final OnColorChangeListener listener) {
diff --git a/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java b/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java
index 24978fa..76bc446 100644
--- a/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java
@@ -28,32 +28,25 @@
private static final float DEFAULT_SCALE = 0.5f;
- private ScaleSeekBar scalePicker;
-
public ColorTemperatureAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final ColorTemperatureFilter filter = new ColorTemperatureFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.COLOR);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.COLOR);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
}
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
- }
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/CropAction.java b/src/com/android/gallery3d/photoeditor/actions/CropAction.java
index 60a0179..e06c4e9 100644
--- a/src/com/android/gallery3d/photoeditor/actions/CropAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/CropAction.java
@@ -29,25 +29,24 @@
private static final float DEFAULT_CROP = 0.2f;
- private CropFilter filter;
- private CropView cropView;
-
public CropAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
- filter = new CropFilter();
+ public void prepare() {
+ // Cropped results wouldn't be previewed for changed crop bounds.
+ final CropFilter filter = new CropFilter();
+ disableFilterOutput();
- cropView = factory.createCropView();
+ CropView cropView = toolKit.addCropView();
cropView.setOnCropChangeListener(new CropView.OnCropChangeListener() {
@Override
public void onCropChanged(RectF cropBounds, boolean fromUser) {
if (fromUser) {
filter.setCropBounds(cropBounds);
- notifyFilterChanged(filter, false);
+ notifyChanged(filter);
}
}
});
@@ -55,12 +54,6 @@
RectF bounds = new RectF(DEFAULT_CROP, DEFAULT_CROP, 1 - DEFAULT_CROP, 1 - DEFAULT_CROP);
cropView.setCropBounds(bounds);
filter.setCropBounds(bounds);
- notifyFilterChanged(filter, false);
- }
-
- @Override
- public void doEnd() {
- cropView.setOnCropChangeListener(null);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java b/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java
index 8be60d3..b61f6fa 100644
--- a/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java
@@ -31,12 +31,8 @@
}
@Override
- public void doBegin() {
- notifyFilterChanged(new CrossProcessFilter(), true);
- notifyDone();
- }
-
- @Override
- public void doEnd() {
+ public void prepare() {
+ notifyChanged(new CrossProcessFilter());
+ notifyOk();
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java b/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java
index 0ec4a02..b7f9a90 100644
--- a/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java
@@ -31,12 +31,8 @@
}
@Override
- public void doBegin() {
- notifyFilterChanged(new DocumentaryFilter(), true);
- notifyDone();
- }
-
- @Override
- public void doEnd() {
+ public void prepare() {
+ notifyChanged(new DocumentaryFilter());
+ notifyOk();
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/Doodle.java b/src/com/android/gallery3d/photoeditor/actions/Doodle.java
index ea23e23..bd08983 100644
--- a/src/com/android/gallery3d/photoeditor/actions/Doodle.java
+++ b/src/com/android/gallery3d/photoeditor/actions/Doodle.java
@@ -55,20 +55,23 @@
}
/**
- * Adds control points whose coordinates range from 0 to 1 to construct the doodle path.
- *
- * @return true if the constructed path is in (0, 0, 1, 1) bounds; otherwise, false.
+ * Checks if the constructed doodle path is in (0, 0, 1, 1) bounds.
*/
- public boolean addControlPoint(PointF point) {
- PointF last = points.lastElement();
- normalizedPath.quadTo(last.x, last.y, (last.x + point.x) / 2, (last.y + point.y) / 2);
- points.add(point);
-
+ public boolean inBounds() {
RectF r = new RectF();
normalizedPath.computeBounds(r, false);
return r.intersects(0, 0, 1, 1);
}
+ /**
+ * Adds control points whose coordinates range from 0 to 1 to construct the doodle path.
+ */
+ public void addControlPoint(PointF point) {
+ PointF last = points.lastElement();
+ normalizedPath.quadTo(last.x, last.y, (last.x + point.x) / 2, (last.y + point.y) / 2);
+ points.add(point);
+ }
+
public int getColor() {
return color;
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java b/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java
index 4ad2cfb..f148287 100644
--- a/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java
@@ -28,19 +28,37 @@
private static final int DEFAULT_COLOR_INDEX = 4;
- private DoodleFilter filter;
- private ColorSeekBar colorPicker;
- private DoodleView doodleView;
-
public DoodleAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
- filter = new DoodleFilter();
+ public void prepare() {
+ // Directly draw on doodle-view because running the doodle filter isn't fast enough.
+ final DoodleFilter filter = new DoodleFilter();
+ disableFilterOutput();
- colorPicker = factory.createColorPicker();
+ final DoodleView doodleView = toolKit.addDoodleView();
+ doodleView.setOnDoodleChangeListener(new DoodleView.OnDoodleChangeListener() {
+
+ @Override
+ public void onDoodleChanged(Doodle doodle) {
+ // Check if the user draws within photo bounds and makes visible changes on photo.
+ if (doodle.inBounds()) {
+ notifyChanged(filter);
+ }
+ }
+
+ @Override
+ public void onDoodleFinished(Doodle doodle) {
+ if (doodle.inBounds()) {
+ filter.addDoodle(doodle);
+ notifyChanged(filter);
+ }
+ }
+ });
+
+ ColorSeekBar colorPicker = toolKit.addColorPicker();
colorPicker.setOnColorChangeListener(new ColorSeekBar.OnColorChangeListener() {
@Override
@@ -51,30 +69,6 @@
}
});
colorPicker.setColorIndex(DEFAULT_COLOR_INDEX);
-
- doodleView = factory.createDoodleView();
- doodleView.setOnDoodleChangeListener(new DoodleView.OnDoodleChangeListener() {
-
- @Override
- public void onDoodleInPhotoBounds() {
- // Notify the user has drawn within photo bounds and made visible changes on photo.
- filter.setDoodledInPhotoBounds();
- notifyFilterChanged(filter, false);
- }
-
- @Override
- public void onDoodleFinished(Doodle doodle) {
- filter.addDoodle(doodle);
- notifyFilterChanged(filter, false);
- }
- });
doodleView.setColor(colorPicker.getColor());
}
-
- @Override
- public void doEnd() {
- colorPicker.setOnColorChangeListener(null);
- doodleView.setOnDoodleChangeListener(null);
- notifyFilterChanged(filter, true);
- }
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/DoodleView.java b/src/com/android/gallery3d/photoeditor/actions/DoodleView.java
index b596861..d396049 100644
--- a/src/com/android/gallery3d/photoeditor/actions/DoodleView.java
+++ b/src/com/android/gallery3d/photoeditor/actions/DoodleView.java
@@ -37,7 +37,7 @@
*/
public interface OnDoodleChangeListener {
- void onDoodleInPhotoBounds();
+ void onDoodleChanged(Doodle doodle);
void onDoodleFinished(Doodle doodle);
}
@@ -111,9 +111,10 @@
}
private void addLastPointIntoDoodle() {
- if ((doodle != null) && doodle.addControlPoint(new PointF(lastPoint.x, lastPoint.y))) {
+ if (doodle != null) {
+ doodle.addControlPoint(new PointF(lastPoint.x, lastPoint.y));
if (listener != null) {
- listener.onDoodleInPhotoBounds();
+ listener.onDoodleChanged(doodle);
}
invalidate();
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java b/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java
index b8da71e..3ba8aa5 100644
--- a/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java
@@ -34,15 +34,11 @@
}
@Override
- public void doBegin() {
+ public void prepare() {
// TODO: Add several sets of duo-tone colors to select from.
DuotoneFilter filter = new DuotoneFilter();
filter.setDuotone(DEFAULT_FIRST_COLOR, DEFAULT_SECOND_COLOR);
- notifyFilterChanged(filter, true);
- notifyDone();
- }
-
- @Override
- public void doEnd() {
+ notifyChanged(filter);
+ notifyOk();
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/EffectAction.java b/src/com/android/gallery3d/photoeditor/actions/EffectAction.java
index 6c6a893..92bcee4 100644
--- a/src/com/android/gallery3d/photoeditor/actions/EffectAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/EffectAction.java
@@ -37,46 +37,32 @@
/**
* Listener of effect action.
*/
- public interface Listener {
+ public interface ActionListener {
- void onClick();
-
- void onDone();
+ /**
+ * Invoked when the action is okayed (effect is applied and completed).
+ */
+ void onOk();
}
- protected EffectToolFactory factory;
-
- private Listener listener;
+ protected EffectToolKit toolKit;
private Toast tooltip;
private FilterStack filterStack;
private boolean pushedFilter;
+ private boolean disableFilterOutput;
private FilterChangedCallback lastFilterChangedCallback;
+ private ActionListener listener;
public EffectAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public void setListener(Listener l) {
- listener = l;
- findViewById(R.id.effect_button).setOnClickListener(
- (listener == null) ? null : new View.OnClickListener() {
-
- @Override
- public void onClick(View v) {
- listener.onClick();
- }
- });
- }
-
- public CharSequence name() {
- return ((TextView) findViewById(R.id.effect_label)).getText();
- }
-
- public void begin(FilterStack filterStack, EffectToolFactory factory) {
+ public void begin(View root, FilterStack filterStack, ActionListener listener) {
// This view is already detached from UI view hierarchy by reaching here; findViewById()
// could only access its own child views from here.
+ toolKit = new EffectToolKit(root, ((TextView) findViewById(R.id.effect_label)).getText());
this.filterStack = filterStack;
- this.factory = factory;
+ this.listener = listener;
// Shows the tooltip if it's available.
if (getTag() != null) {
@@ -84,14 +70,30 @@
tooltip.setGravity(Gravity.CENTER, 0, 0);
tooltip.show();
}
- doBegin();
+ prepare();
}
/**
+ * Subclasses should create a specific filter and bind the filter to necessary UI controls here
+ * when the action is about to begin.
+ */
+ protected abstract void prepare();
+
+ /**
* Ends the effect and then executes the runnable after the effect is finished.
*/
public void end(final Runnable runnableOnODone) {
- doEnd();
+ // Cancel the tooltip if it's still showing.
+ if ((tooltip != null) && (tooltip.getView().getParent() != null)) {
+ tooltip.cancel();
+ tooltip = null;
+ }
+ // End tool editing by canceling unfinished touch events.
+ toolKit.cancel();
+ // Output the pushed filter if it wasn't outputted.
+ if (pushedFilter && disableFilterOutput) {
+ outputFilter();
+ }
// Wait till last output callback is done before finishing.
if ((lastFilterChangedCallback == null) || lastFilterChangedCallback.done) {
@@ -108,45 +110,38 @@
}
private void finish(Runnable runnableOnDone) {
- // Close the tooltip if it's still showing.
- if ((tooltip != null) && (tooltip.getView().getParent() != null)) {
- tooltip.cancel();
- tooltip = null;
- }
+ toolKit.close();
pushedFilter = false;
+ disableFilterOutput = false;
lastFilterChangedCallback = null;
runnableOnDone.run();
}
- protected void notifyDone() {
- if (listener != null) {
- listener.onDone();
- }
+ protected void disableFilterOutput() {
+ // Filter output won't be outputted until this effect has done editing its filter.
+ disableFilterOutput = true;
}
- protected void notifyFilterChanged(Filter filter, boolean output) {
- if (!pushedFilter && filter.isValid()) {
+ protected void outputFilter() {
+ // Notify the stack to execute the changed top filter and output the results.
+ lastFilterChangedCallback = new FilterChangedCallback();
+ filterStack.topFilterChanged(lastFilterChangedCallback);
+ }
+
+ protected void notifyChanged(Filter filter) {
+ if (!pushedFilter) {
filterStack.pushFilter(filter);
pushedFilter = true;
}
- if (pushedFilter && output) {
- // Notify the stack to execute the changed top filter and output the results.
- lastFilterChangedCallback = new FilterChangedCallback();
- filterStack.topFilterChanged(lastFilterChangedCallback);
+ if (pushedFilter && !disableFilterOutput) {
+ outputFilter();
}
}
- /**
- * Subclasses should creates a specific filter and binds the filter to necessary UI controls
- * here when the action is about to begin.
- */
- protected abstract void doBegin();
-
- /**
- * Subclasses could do specific ending operations here when the action is about to end.
- */
- protected abstract void doEnd();
+ protected void notifyOk() {
+ listener.onOk();
+ }
/**
* Done callback for executing top filter changes.
diff --git a/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java b/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java
deleted file mode 100644
index 3641828..0000000
--- a/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.photoeditor.actions;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.photoeditor.PhotoView;
-
-/**
- * Factory to create tools that will be used by effect actions.
- */
-public class EffectToolFactory {
-
- public enum ScalePickerType {
- LIGHT, SHADOW, COLOR, GENERIC
- }
-
- private final ViewGroup effectToolPanel;
- private final LayoutInflater inflater;
-
- public EffectToolFactory(ViewGroup effectToolPanel, LayoutInflater inflater) {
- this.effectToolPanel = effectToolPanel;
- this.inflater = inflater;
- }
-
- private View createFullscreenTool(int toolId) {
- // Create full screen effect tool on top of photo-view and place it within the same
- // view group that contains photo-view.
- View photoView = effectToolPanel.getRootView().findViewById(R.id.photo_view);
- ViewGroup parent = (ViewGroup) photoView.getParent();
- FullscreenToolView view = (FullscreenToolView) inflater.inflate(toolId, parent, false);
- view.setPhotoBounds(((PhotoView) photoView).getPhotoBounds());
- parent.addView(view, parent.indexOfChild(photoView) + 1);
- return view;
- }
-
- private View createPanelTool(int toolId) {
- View view = inflater.inflate(toolId, effectToolPanel, false);
- effectToolPanel.addView(view, 0);
- return view;
- }
-
- private int getScalePickerBackground(ScalePickerType type) {
- switch (type) {
- case LIGHT:
- return R.drawable.photoeditor_scale_seekbar_light;
-
- case SHADOW:
- return R.drawable.photoeditor_scale_seekbar_shadow;
-
- case COLOR:
- return R.drawable.photoeditor_scale_seekbar_color;
- }
- return R.drawable.photoeditor_scale_seekbar_generic;
- }
-
- public ScaleSeekBar createScalePicker(ScalePickerType type) {
- ScaleSeekBar scalePicker = (ScaleSeekBar) createPanelTool(
- R.layout.photoeditor_scale_seekbar);
- scalePicker.setBackgroundResource(getScalePickerBackground(type));
- return scalePicker;
- }
-
- public ColorSeekBar createColorPicker() {
- return (ColorSeekBar) createPanelTool(R.layout.photoeditor_color_seekbar);
- }
-
- public DoodleView createDoodleView() {
- return (DoodleView) createFullscreenTool(R.layout.photoeditor_doodle_view);
- }
-
- public TouchView createTouchView() {
- return (TouchView) createFullscreenTool(R.layout.photoeditor_touch_view);
- }
-
- public FlipView createFlipView() {
- return (FlipView) createFullscreenTool(R.layout.photoeditor_flip_view);
- }
-
- public RotateView createRotateView() {
- return (RotateView) createFullscreenTool(R.layout.photoeditor_rotate_view);
- }
-
- public CropView createCropView() {
- return (CropView) createFullscreenTool(R.layout.photoeditor_crop_view);
- }
-}
diff --git a/src/com/android/gallery3d/photoeditor/actions/EffectToolKit.java b/src/com/android/gallery3d/photoeditor/actions/EffectToolKit.java
new file mode 100644
index 0000000..285e06b
--- /dev/null
+++ b/src/com/android/gallery3d/photoeditor/actions/EffectToolKit.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.photoeditor.actions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.photoeditor.PhotoView;
+
+/**
+ * Tool kit used by effect actions to retrieve tools, including managing tool creation/removal.
+ */
+public class EffectToolKit {
+
+ public enum ScaleType {
+ LIGHT, SHADOW, COLOR, GENERIC
+ }
+
+ private final LayoutInflater inflater;
+ private final PhotoView photoView;
+ private final ViewGroup toolPanel;
+ private final ViewGroup toolFullscreen;
+
+ public EffectToolKit(View root, CharSequence label) {
+ inflater = (LayoutInflater) root.getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ // Create effect tool panel as the first child of effects-bar.
+ ViewGroup effectsBar = (ViewGroup) root.findViewById(R.id.effects_bar);
+ toolPanel = (ViewGroup) inflater.inflate(
+ R.layout.photoeditor_effect_tool_panel, effectsBar, false);
+ ((TextView) toolPanel.findViewById(R.id.effect_label)).setText(label);
+ effectsBar.addView(toolPanel, 0);
+
+ // Create effect tool full-screen on top of photo-view and place it within the same
+ // view group that contains photo-view.
+ photoView = (PhotoView) root.findViewById(R.id.photo_view);
+ ViewGroup parent = (ViewGroup) photoView.getParent();
+ toolFullscreen = (ViewGroup) inflater.inflate(
+ R.layout.photoeditor_effect_tool_fullscreen, parent, false);
+ parent.addView(toolFullscreen, parent.indexOfChild(photoView) + 1);
+ }
+
+ public PhotoView getPhotoView() {
+ return photoView;
+ }
+
+ /**
+ * Cancel pending touch events and stop dispatching further touch events to tools.
+ */
+ public void cancel() {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ toolFullscreen.dispatchTouchEvent(cancelEvent);
+ toolPanel.dispatchTouchEvent(cancelEvent);
+ cancelEvent.recycle();
+ View.OnTouchListener listener = new View.OnTouchListener() {
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // Consume all further touch events and don't dispatch them.
+ return true;
+ }
+ };
+ toolFullscreen.setOnTouchListener(listener);
+ toolPanel.setOnTouchListener(listener);
+ }
+
+ /**
+ * Close to remove all created tools.
+ */
+ public void close() {
+ ((ViewGroup) toolFullscreen.getParent()).removeView(toolFullscreen);
+ ((ViewGroup) toolPanel.getParent()).removeView(toolPanel);
+ }
+
+ private View addFullscreenTool(int toolId) {
+ FullscreenToolView tool = (FullscreenToolView) inflater.inflate(
+ toolId, toolFullscreen, false);
+ tool.setPhotoBounds(getPhotoView().getPhotoBounds());
+ toolFullscreen.addView(tool);
+ return tool;
+ }
+
+ private View addPanelTool(int toolId) {
+ // Add the tool right above the effect-label in the panel.
+ View tool = inflater.inflate(toolId, toolPanel, false);
+ toolPanel.addView(tool, toolPanel.indexOfChild(toolPanel.findViewById(R.id.effect_label)));
+ return tool;
+ }
+
+ private Drawable getScalePickerProgressDrawable(Resources res, ScaleType type) {
+ switch (type) {
+ case LIGHT:
+ return res.getDrawable(R.drawable.photoeditor_scale_seekbar_light);
+
+ case SHADOW:
+ return res.getDrawable(R.drawable.photoeditor_scale_seekbar_shadow);
+
+ case COLOR:
+ return res.getDrawable(R.drawable.photoeditor_scale_seekbar_color);
+ }
+ return res.getDrawable(R.drawable.photoeditor_scale_seekbar_generic);
+ }
+
+ public ScaleSeekBar addScalePicker(ScaleType type) {
+ ScaleSeekBar scalePicker = (ScaleSeekBar) addPanelTool(
+ R.layout.photoeditor_scale_seekbar);
+ scalePicker.setProgressDrawable(getScalePickerProgressDrawable(
+ toolPanel.getResources(), type));
+ return scalePicker;
+ }
+
+ public ColorSeekBar addColorPicker() {
+ return (ColorSeekBar) addPanelTool(R.layout.photoeditor_color_seekbar);
+ }
+
+ public DoodleView addDoodleView() {
+ return (DoodleView) addFullscreenTool(R.layout.photoeditor_doodle_view);
+ }
+
+ public TouchView addTouchView() {
+ return (TouchView) addFullscreenTool(R.layout.photoeditor_touch_view);
+ }
+
+ public FlipView addFlipView() {
+ return (FlipView) addFullscreenTool(R.layout.photoeditor_flip_view);
+ }
+
+ public RotateView addRotateView() {
+ return (RotateView) addFullscreenTool(R.layout.photoeditor_rotate_view);
+ }
+
+ public CropView addCropView() {
+ return (CropView) addFullscreenTool(R.layout.photoeditor_crop_view);
+ }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FaceTanAction.java b/src/com/android/gallery3d/photoeditor/actions/FaceTanAction.java
index a82f330..6b8f1d1 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FaceTanAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FaceTanAction.java
@@ -28,35 +28,28 @@
private static final float DEFAULT_SCALE = 0.5f;
- private ScaleSeekBar scalePicker;
-
public FaceTanAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final FaceTanFilter filter = new FaceTanFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
filter.setScale(DEFAULT_SCALE);
- notifyFilterChanged(filter, true);
- }
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
+ notifyChanged(filter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FaceliftAction.java b/src/com/android/gallery3d/photoeditor/actions/FaceliftAction.java
index 90d4e0c..4c1a918 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FaceliftAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FaceliftAction.java
@@ -28,35 +28,28 @@
private static final float DEFAULT_SCALE = 0.5f;
- private ScaleSeekBar scalePicker;
-
public FaceliftAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final FaceliftFilter filter = new FaceliftFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
filter.setScale(DEFAULT_SCALE);
- notifyFilterChanged(filter, true);
- }
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
+ notifyChanged(filter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java b/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java
index 73cf3d8..323bfd6 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java
@@ -28,32 +28,25 @@
private static final float DEFAULT_SCALE = 0f;
- private ScaleSeekBar scalePicker;
-
public FillLightAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final FillLightFilter filter = new FillLightFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.LIGHT);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.LIGHT);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
}
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
- }
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java b/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java
index 348f004..df7ed78 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java
@@ -28,35 +28,28 @@
private static final float DEFAULT_SCALE = 0.5f;
- private ScaleSeekBar scalePicker;
-
public FisheyeAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final FisheyeFilter filter = new FisheyeFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
filter.setScale(DEFAULT_SCALE);
- notifyFilterChanged(filter, true);
- }
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
+ notifyChanged(filter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FlipAction.java b/src/com/android/gallery3d/photoeditor/actions/FlipAction.java
index da238ba..71e5a90 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FlipAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FlipAction.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.util.AttributeSet;
-import com.android.gallery3d.R;
import com.android.gallery3d.photoeditor.PhotoView;
import com.android.gallery3d.photoeditor.filters.FlipFilter;
@@ -31,35 +30,32 @@
private static final float DEFAULT_ANGLE = 0.0f;
private static final float DEFAULT_FLIP_SPAN = 180.0f;
- private FlipFilter filter;
- private float horizontalFlipDegrees;
- private float verticalFlipDegrees;
- private Runnable queuedFlipChange;
- private FlipView flipView;
-
public FlipAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
- filter = new FlipFilter();
+ public void prepare() {
+ // Disable outputting flipped results and directly flip photo-view for animations.
+ final FlipFilter filter = new FlipFilter();
+ disableFilterOutput();
- flipView = factory.createFlipView();
+ final FlipView flipView = toolKit.addFlipView();
flipView.setOnFlipChangeListener(new FlipView.OnFlipChangeListener() {
- // Directly transform photo-view because running the flip filter isn't fast enough.
- PhotoView photoView = (PhotoView) flipView.getRootView().findViewById(
- R.id.photo_view);
+ float horizontalDegrees;
+ float verticalDegrees;
+ Runnable queuedTransform;
+ PhotoView photoView = toolKit.getPhotoView();
@Override
public void onAngleChanged(float horizontalDegrees, float verticalDegrees,
boolean fromUser) {
if (fromUser) {
- horizontalFlipDegrees = horizontalDegrees;
- verticalFlipDegrees = verticalDegrees;
- updateFlipFilter(false);
+ this.horizontalDegrees = horizontalDegrees;
+ this.verticalDegrees = verticalDegrees;
transformPhotoView(horizontalDegrees, verticalDegrees);
+ notifyChanged(filter);
}
}
@@ -70,59 +66,42 @@
@Override
public void onStopTrackingTouch() {
- roundFlipDegrees();
- updateFlipFilter(false);
- transformPhotoView(horizontalFlipDegrees, verticalFlipDegrees);
- flipView.setFlippedAngles(horizontalFlipDegrees, verticalFlipDegrees);
+ // Round flip degrees to multiples of 180 degrees.
+ horizontalDegrees = roundTo180(horizontalDegrees);
+ verticalDegrees = roundTo180(verticalDegrees);
+ transformPhotoView(horizontalDegrees, verticalDegrees);
+ flipView.setFlippedAngles(horizontalDegrees, verticalDegrees);
+
+ // Flip the filter according to the flipped directions of flip-view.
+ filter.setFlip(((int) horizontalDegrees / 180) % 2 != 0,
+ ((int) verticalDegrees / 180) % 2 != 0);
+ notifyChanged(filter);
+ }
+
+ private float roundTo180(float degrees) {
+ if (degrees % 180 != 0) {
+ degrees = Math.round(degrees / 180) * 180;
+ }
+ return degrees;
}
private void transformPhotoView(final float horizontalDegrees,
final float verticalDegrees) {
// Remove the outdated flip change before queuing a new one.
- if (queuedFlipChange != null) {
- photoView.remove(queuedFlipChange);
+ if (queuedTransform != null) {
+ photoView.remove(queuedTransform);
}
- queuedFlipChange = new Runnable() {
+ queuedTransform = new Runnable() {
@Override
public void run() {
photoView.flipPhoto(horizontalDegrees, verticalDegrees);
}
};
- photoView.queue(queuedFlipChange);
+ photoView.queue(queuedTransform);
}
});
flipView.setFlippedAngles(DEFAULT_ANGLE, DEFAULT_ANGLE);
flipView.setFlipSpan(DEFAULT_FLIP_SPAN);
- horizontalFlipDegrees = 0;
- verticalFlipDegrees = 0;
- queuedFlipChange = null;
- }
-
- @Override
- public void doEnd() {
- flipView.setOnFlipChangeListener(null);
- // Round the current flip degrees in case flip tracking has not stopped yet.
- roundFlipDegrees();
- updateFlipFilter(true);
- }
-
- /**
- * Rounds flip degrees to multiples of 180 degrees.
- */
- private void roundFlipDegrees() {
- if (horizontalFlipDegrees % 180 != 0) {
- horizontalFlipDegrees = Math.round(horizontalFlipDegrees / 180) * 180;
- }
- if (verticalFlipDegrees % 180 != 0) {
- verticalFlipDegrees = Math.round(verticalFlipDegrees / 180) * 180;
- }
- }
-
- private void updateFlipFilter(boolean outputFilter) {
- // Flip the filter if the flipped degrees are at the opposite directions.
- filter.setFlip(((int) horizontalFlipDegrees / 180) % 2 != 0,
- ((int) verticalFlipDegrees / 180) % 2 != 0);
- notifyFilterChanged(filter, outputFilter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/GrainAction.java b/src/com/android/gallery3d/photoeditor/actions/GrainAction.java
index 258eb8a..5e99129 100644
--- a/src/com/android/gallery3d/photoeditor/actions/GrainAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/GrainAction.java
@@ -28,35 +28,28 @@
private static final float DEFAULT_SCALE = 0.5f;
- private ScaleSeekBar scalePicker;
-
public GrainAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final GrainFilter filter = new GrainFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
filter.setScale(DEFAULT_SCALE);
- notifyFilterChanged(filter, true);
- }
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
+ notifyChanged(filter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java b/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java
index ac89cd1..f534ca8 100644
--- a/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java
@@ -31,12 +31,8 @@
}
@Override
- public void doBegin() {
- notifyFilterChanged(new GrayscaleFilter(), true);
- notifyDone();
- }
-
- @Override
- public void doEnd() {
+ public void prepare() {
+ notifyChanged(new GrayscaleFilter());
+ notifyOk();
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java b/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java
index a3d62d2..18d7add 100644
--- a/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java
@@ -28,32 +28,25 @@
private static final float DEFAULT_SCALE = 0f;
- private ScaleSeekBar scalePicker;
-
public HighlightAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final HighlightFilter filter = new HighlightFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.LIGHT);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.LIGHT);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
}
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
- }
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java b/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java
index 44ffc52..17af429 100644
--- a/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java
@@ -31,12 +31,8 @@
}
@Override
- public void doBegin() {
- notifyFilterChanged(new LomoishFilter(), true);
- notifyDone();
- }
-
- @Override
- public void doEnd() {
+ public void prepare() {
+ notifyChanged(new LomoishFilter());
+ notifyOk();
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java b/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java
index 5276421..8893154 100644
--- a/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java
@@ -31,12 +31,8 @@
}
@Override
- public void doBegin() {
- notifyFilterChanged(new NegativeFilter(), true);
- notifyDone();
- }
-
- @Override
- public void doEnd() {
+ public void prepare() {
+ notifyChanged(new NegativeFilter());
+ notifyOk();
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java b/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java
index 760539d..9a9f874 100644
--- a/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java
@@ -31,12 +31,8 @@
}
@Override
- public void doBegin() {
- notifyFilterChanged(new PosterizeFilter(), true);
- notifyDone();
- }
-
- @Override
- public void doEnd() {
+ public void prepare() {
+ notifyChanged(new PosterizeFilter());
+ notifyOk();
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java b/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java
index a472ad9..b957715 100644
--- a/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.PointF;
+import android.graphics.RectF;
import android.util.AttributeSet;
import com.android.gallery3d.photoeditor.filters.RedEyeFilter;
@@ -27,29 +28,27 @@
*/
public class RedEyeAction extends EffectAction {
- private TouchView touchView;
-
public RedEyeAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final RedEyeFilter filter = new RedEyeFilter();
- touchView = factory.createTouchView();
+ TouchView touchView = toolKit.addTouchView();
touchView.setSingleTapListener(new TouchView.SingleTapListener() {
+ final RectF bounds = new RectF(0, 0, 1, 1);
+
@Override
public void onSingleTap(PointF point) {
- filter.addRedEyePosition(point);
- notifyFilterChanged(filter, true);
+ // Check if the user taps within photo bounds to remove red eye on photo.
+ if (bounds.contains(point.x, point.y)) {
+ filter.addRedEyePosition(point);
+ notifyChanged(filter);
+ }
}
});
}
-
- @Override
- public void doEnd() {
- touchView.setSingleTapListener(null);
- }
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/RotateAction.java b/src/com/android/gallery3d/photoeditor/actions/RotateAction.java
index 36a09d9..98d6555 100644
--- a/src/com/android/gallery3d/photoeditor/actions/RotateAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/RotateAction.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.util.AttributeSet;
-import com.android.gallery3d.R;
import com.android.gallery3d.photoeditor.PhotoView;
import com.android.gallery3d.photoeditor.filters.RotateFilter;
@@ -31,32 +30,29 @@
private static final float DEFAULT_ANGLE = 0.0f;
private static final float DEFAULT_ROTATE_SPAN = 360.0f;
- private RotateFilter filter;
- private float rotateDegrees;
- private Runnable queuedRotationChange;
- private RotateView rotateView;
-
public RotateAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
- filter = new RotateFilter();
+ public void prepare() {
+ // Disable outputting rotated results and directly rotate photo-view for animations.
+ final RotateFilter filter = new RotateFilter();
+ disableFilterOutput();
- rotateView = factory.createRotateView();
+ final RotateView rotateView = toolKit.addRotateView();
rotateView.setOnRotateChangeListener(new RotateView.OnRotateChangeListener() {
- // Directly transform photo-view because running the rotate filter isn't fast enough.
- PhotoView photoView = (PhotoView) rotateView.getRootView().findViewById(
- R.id.photo_view);
+ float rotateDegrees;
+ Runnable queuedTransform;
+ PhotoView photoView = toolKit.getPhotoView();
@Override
- public void onAngleChanged(float degrees, boolean fromUser){
+ public void onAngleChanged(float degrees, boolean fromUser) {
if (fromUser) {
rotateDegrees = degrees;
- updateRotateFilter(false);
- transformPhotoView(degrees);
+ transformPhotoView(rotateDegrees);
+ notifyChanged(filter);
}
}
@@ -67,52 +63,32 @@
@Override
public void onStopTrackingTouch() {
- roundRotateDegrees();
- updateRotateFilter(false);
+ // Round rotate degrees to multiples of 90 degrees.
+ if (rotateDegrees % 90 != 0) {
+ rotateDegrees = Math.round(rotateDegrees / 90) * 90;
+ }
transformPhotoView(rotateDegrees);
rotateView.setRotatedAngle(rotateDegrees);
+ filter.setAngle(rotateDegrees);
+ notifyChanged(filter);
}
private void transformPhotoView(final float degrees) {
// Remove the outdated rotation change before queuing a new one.
- if (queuedRotationChange != null) {
- photoView.remove(queuedRotationChange);
+ if (queuedTransform != null) {
+ photoView.remove(queuedTransform);
}
- queuedRotationChange = new Runnable() {
+ queuedTransform = new Runnable() {
@Override
public void run() {
photoView.rotatePhoto(degrees);
}
};
- photoView.queue(queuedRotationChange);
+ photoView.queue(queuedTransform);
}
});
rotateView.setRotatedAngle(DEFAULT_ANGLE);
rotateView.setRotateSpan(DEFAULT_ROTATE_SPAN);
- rotateDegrees = 0;
- queuedRotationChange = null;
- }
-
- @Override
- public void doEnd() {
- rotateView.setOnRotateChangeListener(null);
- // Round the current rotation degrees in case rotation tracking has not stopped yet.
- roundRotateDegrees();
- updateRotateFilter(true);
- }
-
- /**
- * Rounds rotate degrees to multiples of 90 degrees.
- */
- private void roundRotateDegrees() {
- if (rotateDegrees % 90 != 0) {
- rotateDegrees = Math.round(rotateDegrees / 90) * 90;
- }
- }
-
- private void updateRotateFilter(boolean outputFilter) {
- filter.setAngle(rotateDegrees);
- notifyFilterChanged(filter, outputFilter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java b/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java
index 2f67e0a..6afd7ba 100644
--- a/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java
@@ -28,32 +28,25 @@
private static final float DEFAULT_SCALE = 0.5f;
- private ScaleSeekBar scalePicker;
-
public SaturationAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final SaturationFilter filter = new SaturationFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.COLOR);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.COLOR);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
}
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
- }
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java b/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java
index c431115..5f551c8 100644
--- a/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java
@@ -31,12 +31,8 @@
}
@Override
- public void doBegin() {
- notifyFilterChanged(new SepiaFilter(), true);
- notifyDone();
- }
-
- @Override
- public void doEnd() {
+ public void prepare() {
+ notifyChanged(new SepiaFilter());
+ notifyOk();
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java b/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java
index 15ba850..cfd0538 100644
--- a/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java
@@ -28,32 +28,25 @@
private static final float DEFAULT_SCALE = 0f;
- private ScaleSeekBar scalePicker;
-
public ShadowAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final ShadowFilter filter = new ShadowFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.SHADOW);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.SHADOW);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
}
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
- }
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java b/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java
index c6b240b..7c00b21 100644
--- a/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java
@@ -28,35 +28,28 @@
private static final float DEFAULT_SCALE = 0.5f;
- private ScaleSeekBar scalePicker;
-
public SharpenAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final SharpenFilter filter = new SharpenFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
filter.setScale(DEFAULT_SCALE);
- notifyFilterChanged(filter, true);
- }
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
+ notifyChanged(filter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java b/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java
index 42b384d..55eb8fd 100644
--- a/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java
@@ -29,24 +29,22 @@
private static final float DEFAULT_ANGLE = 0.0f;
private static final float DEFAULT_ROTATE_SPAN = StraightenFilter.MAX_DEGREES * 2;
- private RotateView rotateView;
-
public StraightenAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final StraightenFilter filter = new StraightenFilter();
- rotateView = factory.createRotateView();
+ RotateView rotateView = toolKit.addRotateView();
rotateView.setOnRotateChangeListener(new RotateView.OnRotateChangeListener() {
@Override
public void onAngleChanged(float degrees, boolean fromUser){
if (fromUser) {
filter.setAngle(degrees);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
@@ -64,9 +62,4 @@
rotateView.setRotatedAngle(DEFAULT_ANGLE);
rotateView.setRotateSpan(DEFAULT_ROTATE_SPAN);
}
-
- @Override
- public void doEnd() {
- rotateView.setOnRotateChangeListener(null);
- }
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/TintAction.java b/src/com/android/gallery3d/photoeditor/actions/TintAction.java
index defd2a3..417c8f5 100644
--- a/src/com/android/gallery3d/photoeditor/actions/TintAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/TintAction.java
@@ -28,35 +28,28 @@
private static final int DEFAULT_COLOR_INDEX = 13;
- private ColorSeekBar colorPicker;
-
public TintAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final TintFilter filter = new TintFilter();
- colorPicker = factory.createColorPicker();
+ ColorSeekBar colorPicker = toolKit.addColorPicker();
colorPicker.setOnColorChangeListener(new ColorSeekBar.OnColorChangeListener() {
@Override
public void onColorChanged(int color, boolean fromUser) {
if (fromUser) {
filter.setTint(color);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
- // Tint photo with the default color.
colorPicker.setColorIndex(DEFAULT_COLOR_INDEX);
- filter.setTint(colorPicker.getColor());
- notifyFilterChanged(filter, true);
- }
- @Override
- public void doEnd() {
- colorPicker.setOnColorChangeListener(null);
+ filter.setTint(colorPicker.getColor());
+ notifyChanged(filter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/TouchView.java b/src/com/android/gallery3d/photoeditor/actions/TouchView.java
index 0548cc4..d5f311c 100644
--- a/src/com/android/gallery3d/photoeditor/actions/TouchView.java
+++ b/src/com/android/gallery3d/photoeditor/actions/TouchView.java
@@ -28,20 +28,6 @@
class TouchView extends FullscreenToolView {
/**
- * Listener of swipes.
- */
- public interface SwipeListener {
-
- void onSwipeLeft();
-
- void onSwipeRight();
-
- void onSwipeUp();
-
- void onSwipeDown();
- }
-
- /**
* Listener of single tap on a point (relative to photo coordinates).
*/
public interface SingleTapListener {
@@ -50,21 +36,18 @@
}
private final GestureDetector gestureDetector;
-
- private SwipeListener swipeListener;
private SingleTapListener singleTapListener;
public TouchView(Context context, AttributeSet attrs) {
super(context, attrs);
- final int swipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
gestureDetector = new GestureDetector(
context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDown(MotionEvent e) {
- // GestureDetector onTouchEvent returns true for fling events only when their
- // preceding down events are consumed.
+ // GestureDetector onTouchEvent returns true only for events whose preceding
+ // down-events have been consumed.
return true;
}
@@ -77,37 +60,10 @@
}
return true;
}
-
- @Override
- public boolean onFling(
- MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
- if (swipeListener != null) {
- float absX = Math.abs(velocityX);
- float absY = Math.abs(velocityY);
- float deltaX = me2.getX() - me1.getX();
- float deltaY = me2.getY() - me1.getY();
- int travelX = getWidth() / 4;
- int travelY = getHeight() / 4;
- if (velocityX > swipeThreshold && absY < absX && deltaX > travelX) {
- swipeListener.onSwipeRight();
- } else if (velocityX < -swipeThreshold && absY < absX && deltaX < -travelX) {
- swipeListener.onSwipeLeft();
- } else if (velocityY < -swipeThreshold && absX < absY && deltaY < -travelY) {
- swipeListener.onSwipeUp();
- } else if (velocityY > swipeThreshold && absX < absY / 2 && deltaY > travelY) {
- swipeListener.onSwipeDown();
- }
- }
- return true;
- }
});
gestureDetector.setIsLongpressEnabled(false);
}
- public void setSwipeListener(SwipeListener listener) {
- swipeListener = listener;
- }
-
public void setSingleTapListener(SingleTapListener listener) {
singleTapListener = listener;
}
diff --git a/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java b/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java
index f59c636..9f6bcc7 100644
--- a/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java
@@ -28,35 +28,28 @@
private static final float DEFAULT_SCALE = 0.5f;
- private ScaleSeekBar scalePicker;
-
public VignetteAction(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- public void doBegin() {
+ public void prepare() {
final VignetteFilter filter = new VignetteFilter();
- scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+ ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
@Override
public void onProgressChanged(float progress, boolean fromUser) {
if (fromUser) {
filter.setScale(progress);
- notifyFilterChanged(filter, true);
+ notifyChanged(filter);
}
}
});
scalePicker.setProgress(DEFAULT_SCALE);
filter.setScale(DEFAULT_SCALE);
- notifyFilterChanged(filter, true);
- }
-
- @Override
- public void doEnd() {
- scalePicker.setOnScaleChangeListener(null);
+ notifyChanged(filter);
}
}
diff --git a/src/com/android/gallery3d/photoeditor/filters/AbstractScaleFilter.java b/src/com/android/gallery3d/photoeditor/filters/AbstractScaleFilter.java
index 727a98c..6376d33 100644
--- a/src/com/android/gallery3d/photoeditor/filters/AbstractScaleFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/AbstractScaleFilter.java
@@ -30,7 +30,6 @@
*/
public void setScale(float scale) {
this.scale = scale;
- validate();
}
@Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/CropFilter.java b/src/com/android/gallery3d/photoeditor/filters/CropFilter.java
index ccca813..00a6c42 100644
--- a/src/com/android/gallery3d/photoeditor/filters/CropFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/CropFilter.java
@@ -37,7 +37,6 @@
*/
public void setCropBounds(RectF bounds) {
this.bounds = bounds;
- validate();
}
@Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java b/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java
index e82a667..bc233da 100644
--- a/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java
@@ -27,10 +27,6 @@
public static final Creator<CrossProcessFilter> CREATOR = creatorOf(CrossProcessFilter.class);
- public CrossProcessFilter() {
- validate();
- }
-
@Override
public void process(Photo src, Photo dst) {
getEffect(EffectFactory.EFFECT_CROSSPROCESS).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java b/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java
index d6f347b..d2e4c7c 100644
--- a/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java
@@ -27,10 +27,6 @@
public static final Creator<DocumentaryFilter> CREATOR = creatorOf(DocumentaryFilter.class);
- public DocumentaryFilter() {
- validate();
- }
-
@Override
public void process(Photo src, Photo dst) {
getEffect(EffectFactory.EFFECT_DOCUMENTARY).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java b/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java
index 277e06d..61920d3 100644
--- a/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java
@@ -40,14 +40,6 @@
private final Vector<Doodle> doodles = new Vector<Doodle>();
- /**
- * Signals once at least a doodle drawn within photo bounds; this filter is regarded as invalid
- * (no-op on the photo) until not all its doodling is out of bounds.
- */
- public void setDoodledInPhotoBounds() {
- validate();
- }
-
public void addDoodle(Doodle doodle) {
doodles.add(doodle);
}
diff --git a/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java b/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java
index b94f95e..b2c5525 100644
--- a/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java
@@ -35,7 +35,6 @@
public void setDuotone(int firstColor, int secondColor) {
this.firstColor = firstColor;
this.secondColor = secondColor;
- validate();
}
@Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/Filter.java b/src/com/android/gallery3d/photoeditor/filters/Filter.java
index baa3747..5d1ac22 100644
--- a/src/com/android/gallery3d/photoeditor/filters/Filter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/Filter.java
@@ -26,8 +26,7 @@
import java.util.HashMap;
/**
- * Image filter for photo editing; most of its methods must be called from a single GL thread except
- * validate()/isValid() that are called from UI thread.
+ * Image filter for photo editing; all of its methods must be called from a single GL thread.
*/
public abstract class Filter implements Parcelable {
@@ -37,8 +36,6 @@
private static final HashMap<Filter, Effect> effects = new HashMap<Filter, Effect>();
private static EffectContext context;
- private boolean isValid;
-
/**
* Filter context should be released before the current GL context is lost.
*/
@@ -74,18 +71,6 @@
return effect;
}
- protected void validate() {
- isValid = true;
- }
-
- /**
- * Some filters, e.g. lighting filters, are initially invalid until set up with parameters while
- * others, e.g. Sepia or Posterize filters, are initially valid without parameters.
- */
- public boolean isValid() {
- return isValid;
- }
-
/**
* Processes the source bitmap and matrix and output the destination bitmap and matrix.
*
diff --git a/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java b/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java
index 816aad8..9c325c1 100644
--- a/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java
@@ -34,7 +34,6 @@
public void setFlip(boolean flipHorizontal, boolean flipVertical) {
flips[0] = flipHorizontal;
flips[1] = flipVertical;
- validate();
}
@Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java b/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java
index 38dfb52..b0e94ef 100644
--- a/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java
@@ -27,10 +27,6 @@
public static final Creator<GrayscaleFilter> CREATOR = creatorOf(GrayscaleFilter.class);
- public GrayscaleFilter() {
- validate();
- }
-
@Override
public void process(Photo src, Photo dst) {
getEffect(EffectFactory.EFFECT_GRAYSCALE).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java b/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java
index f8c5173..16a1d61 100644
--- a/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java
@@ -27,10 +27,6 @@
public static final Creator<LomoishFilter> CREATOR = creatorOf(LomoishFilter.class);
- public LomoishFilter() {
- validate();
- }
-
@Override
public void process(Photo src, Photo dst) {
getEffect(EffectFactory.EFFECT_LOMOISH).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java b/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java
index 88bbd58..db702d7 100644
--- a/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java
@@ -27,10 +27,6 @@
public static final Creator<NegativeFilter> CREATOR = creatorOf(NegativeFilter.class);
- public NegativeFilter() {
- validate();
- }
-
@Override
public void process(Photo src, Photo dst) {
getEffect(EffectFactory.EFFECT_NEGATIVE).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java b/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java
index 186baa9..23e78bf 100644
--- a/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java
@@ -27,10 +27,6 @@
public static final Creator<PosterizeFilter> CREATOR = creatorOf(PosterizeFilter.class);
- public PosterizeFilter() {
- validate();
- }
-
@Override
public void process(Photo src, Photo dst) {
getEffect(EffectFactory.EFFECT_POSTERIZE).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java b/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java
index 257d322..32e8f7c 100644
--- a/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java
@@ -39,7 +39,6 @@
*/
public void addRedEyePosition(PointF point) {
redeyes.add(point);
- validate();
}
@Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java b/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java
index d377f96..d820bda 100644
--- a/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java
@@ -31,9 +31,11 @@
private float degrees;
+ /**
+ * Sets rotation angle which must be multiples of 90 degrees.
+ */
public void setAngle(float degrees) {
this.degrees = degrees;
- validate();
}
@Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java b/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java
index 6c1a70e..d95c0d8 100644
--- a/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java
@@ -27,10 +27,6 @@
public static final Creator<SepiaFilter> CREATOR = creatorOf(SepiaFilter.class);
- public SepiaFilter() {
- validate();
- }
-
@Override
public void process(Photo src, Photo dst) {
getEffect(EffectFactory.EFFECT_SEPIA).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java b/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java
index 90738f0..bf4ace5 100644
--- a/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java
@@ -28,13 +28,12 @@
public class StraightenFilter extends Filter {
public static final Creator<StraightenFilter> CREATOR = creatorOf(StraightenFilter.class);
- public static final float MAX_DEGREES = 30.0f;
+ public static final float MAX_DEGREES = 45.0f;
private float degrees;
public void setAngle(float degrees) {
this.degrees = degrees;
- validate();
}
@Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/TintFilter.java b/src/com/android/gallery3d/photoeditor/filters/TintFilter.java
index af3d777..7a7463e 100644
--- a/src/com/android/gallery3d/photoeditor/filters/TintFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/TintFilter.java
@@ -33,7 +33,6 @@
public void setTint(int color) {
this.color = color;
- validate();
}
@Override
diff --git a/src/com/android/gallery3d/provider/GalleryProvider.java b/src/com/android/gallery3d/provider/GalleryProvider.java
index 4068d46..79ec66b 100644
--- a/src/com/android/gallery3d/provider/GalleryProvider.java
+++ b/src/com/android/gallery3d/provider/GalleryProvider.java
@@ -16,17 +16,6 @@
package com.android.gallery3d.provider;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.DownloadCache;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.MtpImage;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.picasasource.PicasaSource;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -39,6 +28,16 @@
import android.provider.MediaStore.Images.ImageColumns;
import android.util.Log;
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.MtpImage;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.picasasource.PicasaSource;
+import com.android.gallery3d.util.GalleryUtils;
+
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
@@ -48,7 +47,15 @@
public static final String AUTHORITY = "com.android.gallery3d.provider";
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
+
+ public static interface PicasaColumns {
+ public static final String USER_ACCOUNT = "user_account";
+ public static final String PICASA_ID = "picasa_id";
+ }
+
private static final String[] SUPPORTED_PICASA_COLUMNS = {
+ PicasaColumns.USER_ACCOUNT,
+ PicasaColumns.PICASA_ID,
ImageColumns.DISPLAY_NAME,
ImageColumns.SIZE,
ImageColumns.MIME_TYPE,
@@ -58,7 +65,6 @@
ImageColumns.ORIENTATION};
private DataManager mDataManager;
- private DownloadCache mDownloadCache;
private static Uri sBaseUri;
public static String getAuthority(Context context) {
@@ -104,14 +110,6 @@
return true;
}
- private DownloadCache getDownloadCache() {
- if (mDownloadCache == null) {
- GalleryApp app = (GalleryApp) getContext().getApplicationContext();
- mDownloadCache = app.getDownloadCache();
- }
- return mDownloadCache;
- }
-
// TODO: consider concurrent access
@Override
public Cursor query(Uri uri, String[] projection,
@@ -170,7 +168,11 @@
for (int i = 0, n = projection.length; i < n; ++i) {
String column = projection[i];
- if (ImageColumns.DISPLAY_NAME.equals(column)) {
+ if (PicasaColumns.USER_ACCOUNT.equals(column)) {
+ columnValues[i] = PicasaSource.getUserAccount(getContext(), image);
+ } else if (PicasaColumns.PICASA_ID.equals(column)) {
+ columnValues[i] = PicasaSource.getPicasaId(image);
+ } else if (ImageColumns.DISPLAY_NAME.equals(column)) {
columnValues[i] = PicasaSource.getImageTitle(image);
} else if (ImageColumns.SIZE.equals(column)){
columnValues[i] = PicasaSource.getImageSize(image);
diff --git a/src/com/android/gallery3d/ui/AbstractDisplayItem.java b/src/com/android/gallery3d/ui/AbstractDisplayItem.java
deleted file mode 100644
index 28acc3b..0000000
--- a/src/com/android/gallery3d/ui/AbstractDisplayItem.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import com.android.gallery3d.data.MediaItem;
-
-import android.graphics.Bitmap;
-
-public abstract class AbstractDisplayItem extends DisplayItem {
-
- private static final String TAG = "AbstractDisplayItem";
-
- private static final int STATE_INVALID = 0x01;
- private static final int STATE_VALID = 0x02;
- private static final int STATE_UPDATING = 0x04;
- private static final int STATE_CANCELING = 0x08;
- private static final int STATE_ERROR = 0x10;
-
- private int mState = STATE_INVALID;
- private boolean mImageRequested = false;
- private boolean mRecycling = false;
- private Bitmap mBitmap;
-
- protected final MediaItem mMediaItem;
-
- public AbstractDisplayItem(MediaItem item) {
- mMediaItem = item;
- if (item == null) mState = STATE_ERROR;
- }
-
- protected void updateImage(Bitmap bitmap, boolean isCancelled) {
- if (mRecycling) {
- return;
- }
-
- if (isCancelled && bitmap == null) {
- mState = STATE_INVALID;
- if (mImageRequested) {
- // request image again.
- requestImage();
- }
- return;
- }
-
- mBitmap = bitmap;
- mState = bitmap == null ? STATE_ERROR : STATE_VALID ;
- onBitmapAvailable(mBitmap);
- }
-
- @Override
- public int getRotation() {
- if (mMediaItem != null) return mMediaItem.getRotation();
- return 0;
- }
-
- @Override
- public long getIdentity() {
- return mMediaItem != null
- ? System.identityHashCode(mMediaItem.getPath())
- : System.identityHashCode(this);
- }
-
- public void requestImage() {
- mImageRequested = true;
- if (mState == STATE_INVALID) {
- mState = STATE_UPDATING;
- startLoadBitmap();
- }
- }
-
- public void cancelImageRequest() {
- mImageRequested = false;
- if (mState == STATE_UPDATING) {
- mState = STATE_CANCELING;
- cancelLoadBitmap();
- }
- }
-
- private boolean inState(int states) {
- return (mState & states) != 0;
- }
-
- public void recycle() {
- if (!inState(STATE_UPDATING | STATE_CANCELING)) {
- if (mBitmap != null) mBitmap = null;
- } else {
- mRecycling = true;
- cancelImageRequest();
- }
- }
-
- public boolean isRequestInProgress() {
- return mImageRequested && inState(STATE_UPDATING | STATE_CANCELING);
- }
-
- abstract protected void startLoadBitmap();
- abstract protected void cancelLoadBitmap();
- abstract protected void onBitmapAvailable(Bitmap bitmap);
-}
diff --git a/src/com/android/gallery3d/ui/AbstractSlotRenderer.java b/src/com/android/gallery3d/ui/AbstractSlotRenderer.java
new file mode 100644
index 0000000..98eae56
--- /dev/null
+++ b/src/com/android/gallery3d/ui/AbstractSlotRenderer.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.content.Context;
+import android.graphics.Rect;
+
+import com.android.gallery3d.R;
+
+public abstract class AbstractSlotRenderer implements SlotView.SlotRenderer {
+
+ private final ResourceTexture mVideoOverlay;
+ private final ResourceTexture mVideoPlayIcon;
+ private final NinePatchTexture mPanoramaBorder;
+ private final NinePatchTexture mFramePressed;
+ private final NinePatchTexture mFrameSelected;
+ private FadeOutTexture mFramePressedUp;
+
+ protected AbstractSlotRenderer(Context context) {
+ mVideoOverlay = new ResourceTexture(context, R.drawable.ic_video_thumb);
+ mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_gallery_play);
+ mPanoramaBorder = new NinePatchTexture(context, R.drawable.ic_pan_thumb);
+ mFramePressed = new NinePatchTexture(context, R.drawable.grid_pressed);
+ mFrameSelected = new NinePatchTexture(context, R.drawable.grid_selected);
+ }
+
+ protected void drawContent(GLCanvas canvas,
+ Texture content, int width, int height, int rotation) {
+ canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
+
+ if (rotation != 0) {
+ canvas.translate(width / 2, height / 2);
+ canvas.rotate(rotation, 0, 0, 1);
+ canvas.translate(-width / 2, -height / 2);
+ if (((rotation % 90) & 1) != 0) {
+ int temp = height;
+ height = width;
+ width = height;
+ }
+ }
+
+ // Fit the content into the box
+ float scale = Math.min(
+ (float) width / content.getWidth(),
+ (float) height / content.getHeight());
+ canvas.scale(scale, scale, 1);
+ content.draw(canvas, 0, 0);
+
+ canvas.restore();
+ }
+
+ protected void drawVideoOverlay(GLCanvas canvas, int width, int height) {
+ // Scale the video overlay to the height of the thumbnail and put it
+ // on the left side.
+ ResourceTexture v = mVideoOverlay;
+ float scale = (float) height / v.getHeight();
+ int w = Math.round(scale * v.getWidth());
+ int h = Math.round(scale * v.getHeight());
+ v.draw(canvas, 0, 0, w, h);
+
+ int s = Math.min(width, height) / 6;
+ mVideoPlayIcon.draw(canvas, (width - s) / 2, (height - s) / 2, s, s);
+ }
+
+ protected void drawPanoramaBorder(GLCanvas canvas, int width, int height) {
+ float scale = (float) width / mPanoramaBorder.getWidth();
+ int w = Math.round(scale * mPanoramaBorder.getWidth());
+ int h = Math.round(scale * mPanoramaBorder.getHeight());
+ // draw at the top
+ mPanoramaBorder.draw(canvas, 0, 0, w, h);
+ // draw at the bottom
+ mPanoramaBorder.draw(canvas, 0, height - h, w, h);
+ }
+
+ protected boolean isPressedUpFrameFinished() {
+ if (mFramePressedUp != null) {
+ if (mFramePressedUp.isAnimating()) {
+ return false;
+ } else {
+ mFramePressedUp = null;
+ }
+ }
+ return true;
+ }
+
+ protected void drawPressedUpFrame(GLCanvas canvas, int width, int height) {
+ if (mFramePressedUp == null) {
+ mFramePressedUp = new FadeOutTexture(mFramePressed);
+ }
+ drawFrame(canvas, mFramePressed.getPaddings(), mFramePressedUp, 0, 0, width, height);
+ }
+
+ protected void drawPressedFrame(GLCanvas canvas, int width, int height) {
+ drawFrame(canvas, mFramePressed.getPaddings(), mFramePressed, 0, 0, width, height);
+ }
+
+ protected void drawSelectedFrame(GLCanvas canvas, int width, int height) {
+ drawFrame(canvas, mFrameSelected.getPaddings(), mFrameSelected, 0, 0, width, height);
+ }
+
+ protected static void drawFrame(GLCanvas canvas, Rect padding, Texture frame,
+ int x, int y, int width, int height) {
+ frame.draw(canvas, x - padding.left, y - padding.top, width + padding.left + padding.right,
+ height + padding.top + padding.bottom);
+ }
+}
diff --git a/src/com/android/gallery3d/ui/ActionModeHandler.java b/src/com/android/gallery3d/ui/ActionModeHandler.java
index b96932e..0188393 100644
--- a/src/com/android/gallery3d/ui/ActionModeHandler.java
+++ b/src/com/android/gallery3d/ui/ActionModeHandler.java
@@ -28,8 +28,8 @@
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
-import android.widget.ShareActionProvider;
import android.widget.PopupMenu.OnMenuItemClickListener;
+import android.widget.ShareActionProvider;
import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
import com.android.gallery3d.R;
@@ -88,6 +88,7 @@
R.menu.selection);
updateSelectionMenu();
customMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ @Override
public boolean onMenuItemClick(MenuItem item) {
return onActionItemClicked(actionMode, item);
}
@@ -103,25 +104,40 @@
mListener = listener;
}
+ @Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- boolean result;
- if (mListener != null) {
- result = mListener.onActionItemClicked(item);
- if (result) {
- mSelectionManager.leaveSelectionMode();
- return result;
+ GLRoot root = mActivity.getGLRoot();
+ root.lockRenderThread();
+ try {
+ boolean result;
+ // Give listener a chance to process this command before it's routed to
+ // ActionModeHandler, which handles command only based on the action id.
+ // Sometimes the listener may have more background information to handle
+ // an action command.
+ if (mListener != null) {
+ result = mListener.onActionItemClicked(item);
+ if (result) {
+ mSelectionManager.leaveSelectionMode();
+ return result;
+ }
}
+ ProgressListener listener = null;
+ boolean needsConfirm = false;
+ int action = item.getItemId();
+ if (action == R.id.action_import) {
+ listener = new ImportCompleteListener(mActivity);
+ } else if (item.getItemId() == R.id.action_delete) {
+ needsConfirm = true;
+ }
+ mMenuExecutor.onMenuClicked(item, needsConfirm, listener);
+ if (action == R.id.action_select_all) {
+ updateSupportedOperation();
+ updateSelectionMenu();
+ }
+ } finally {
+ root.unlockRenderThread();
}
- ProgressListener listener = null;
- if (item.getItemId() == R.id.action_import) {
- listener = new ImportCompleteListener(mActivity);
- }
- result = mMenuExecutor.onMenuClicked(item, listener);
- if (item.getItemId() == R.id.action_select_all) {
- updateSupportedOperation();
- updateSelectionMenu();
- }
- return result;
+ return true;
}
private void updateSelectionMenu() {
@@ -173,55 +189,48 @@
// We cannot expand it because MenuExecuter executes it based on
// the selection set instead of the expanded result.
// e.g. LocalImage can be rotated but collections of them (LocalAlbum) can't.
- private void updateMenuOptions(JobContext jc) {
- ArrayList<Path> paths = mSelectionManager.getSelected(false);
-
+ private int computeMenuOptions(JobContext jc) {
+ ArrayList<Path> unexpandedPaths = mSelectionManager.getSelected(false);
+ if (unexpandedPaths.isEmpty()) {
+ // This happens when starting selection mode from overflow menu
+ // (instead of long press a media object)
+ return 0;
+ }
int operation = MediaObject.SUPPORT_ALL;
DataManager manager = mActivity.getDataManager();
int type = 0;
- for (Path path : paths) {
- if (jc.isCancelled()) return;
+ for (Path path : unexpandedPaths) {
+ if (jc.isCancelled()) return 0;
int support = manager.getSupportedOperations(path);
type |= manager.getMediaType(path);
operation &= support;
}
- final String mimeType = MenuExecutor.getMimeType(type);
- if (paths.size() == 0) {
- operation = 0;
- } else if (paths.size() == 1) {
- if (!GalleryUtils.isEditorAvailable((Context) mActivity, mimeType)) {
- operation &= ~MediaObject.SUPPORT_EDIT;
- }
- } else {
- operation &= SUPPORT_MULTIPLE_MASK;
+ switch (unexpandedPaths.size()) {
+ case 1:
+ final String mimeType = MenuExecutor.getMimeType(type);
+ if (!GalleryUtils.isEditorAvailable((Context) mActivity, mimeType)) {
+ operation &= ~MediaObject.SUPPORT_EDIT;
+ }
+ break;
+ default:
+ operation &= SUPPORT_MULTIPLE_MASK;
}
- final int supportedOperation = operation;
-
- mMainHandler.post(new Runnable() {
- @Override
- public void run() {
- mMenuTask = null;
- MenuExecutor.updateMenuOperation(mMenu, supportedOperation);
- }
- });
+ return operation;
}
// Share intent needs to expand the selection set so we can get URI of
// each media item
- private void updateSharingIntent(JobContext jc) {
- if (mShareActionProvider == null) return;
- ArrayList<Path> paths = mSelectionManager.getSelected(true);
- if (paths.size() == 0) return;
-
+ private Intent computeSharingIntent(JobContext jc) {
+ ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true);
+ if (expandedPaths.size() == 0) return null;
final ArrayList<Uri> uris = new ArrayList<Uri>();
-
DataManager manager = mActivity.getDataManager();
int type = 0;
-
final Intent intent = new Intent();
- for (Path path : paths) {
+ for (Path path : expandedPaths) {
+ if (jc.isCancelled()) return null;
int support = manager.getSupportedOperations(path);
type |= manager.getMediaType(path);
@@ -241,15 +250,9 @@
intent.putExtra(Intent.EXTRA_STREAM, uris.get(0));
}
intent.setType(mimeType);
-
- mMainHandler.post(new Runnable() {
- @Override
- public void run() {
- Log.v(TAG, "Sharing intent is ready: action = " + intent.getAction());
- mShareActionProvider.setShareIntent(intent);
- }
- });
}
+
+ return intent;
}
public void updateSupportedOperation(Path path, boolean selected) {
@@ -258,21 +261,38 @@
}
public void updateSupportedOperation() {
+ // Interrupt previous unfinished task, mMenuTask is only accessed in main thread
if (mMenuTask != null) {
mMenuTask.cancel();
}
// Disable share action until share intent is in good shape
- if (mShareActionProvider != null) {
- Log.v(TAG, "Disable sharing until intent is ready");
- mShareActionProvider.setShareIntent(null);
- }
+ final MenuItem item = mShareActionProvider != null ?
+ mMenu.findItem(R.id.action_share) : null;
+ final boolean supportShare = item != null;
+ if (supportShare) item.setEnabled(false);
// Generate sharing intent and update supported operations in the background
+ // The task can take a long time and be canceled in the mean time.
mMenuTask = mActivity.getThreadPool().submit(new Job<Void>() {
- public Void run(JobContext jc) {
- updateMenuOptions(jc);
- updateSharingIntent(jc);
+ public Void run(final JobContext jc) {
+ // Pass1: Deal with unexpanded media object list for menu operation.
+ final int operation = computeMenuOptions(jc);
+
+ // Pass2: Deal with expanded media object list for sharing operation.
+ final Intent intent = supportShare ? computeSharingIntent(jc) : null;
+ mMainHandler.post(new Runnable() {
+ public void run() {
+ mMenuTask = null;
+ if (!jc.isCancelled()) {
+ MenuExecutor.updateMenuOperation(mMenu, operation);
+ if (supportShare) {
+ item.setEnabled(true);
+ mShareActionProvider.setShareIntent(intent);
+ }
+ }
+ }
+ });
return null;
}
});
diff --git a/src/com/android/gallery3d/ui/AdaptiveBackground.java b/src/com/android/gallery3d/ui/AdaptiveBackground.java
deleted file mode 100644
index 42cb2cc..0000000
--- a/src/com/android/gallery3d/ui/AdaptiveBackground.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.LightingColorFilter;
-import android.graphics.Paint;
-
-import com.android.gallery3d.anim.FloatAnimation;
-
-public class AdaptiveBackground extends GLView {
-
- private static final int BACKGROUND_WIDTH = 128;
- private static final int BACKGROUND_HEIGHT = 64;
- private static final int FILTERED_COLOR = 0xffaaaaaa;
- private static final int ANIMATION_DURATION = 500;
-
- private BasicTexture mOldBackground;
- private BasicTexture mBackground;
-
- private final Paint mPaint;
- private Bitmap mPendingBitmap;
- private final FloatAnimation mAnimation =
- new FloatAnimation(0, 1, ANIMATION_DURATION);
-
- public AdaptiveBackground() {
- Paint paint = new Paint();
- paint.setFilterBitmap(true);
- paint.setColorFilter(new LightingColorFilter(FILTERED_COLOR, 0));
- mPaint = paint;
- }
-
- public Bitmap getAdaptiveBitmap(Bitmap bitmap) {
- Bitmap target = Bitmap.createBitmap(
- BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(target);
- int width = bitmap.getWidth();
- int height = bitmap.getHeight();
- int left = 0;
- int top = 0;
- if (width * BACKGROUND_HEIGHT > height * BACKGROUND_WIDTH) {
- float scale = (float) BACKGROUND_HEIGHT / height;
- canvas.scale(scale, scale);
- left = (BACKGROUND_WIDTH - (int) (width * scale + 0.5)) / 2;
- } else {
- float scale = (float) BACKGROUND_WIDTH / width;
- canvas.scale(scale, scale);
- top = (BACKGROUND_HEIGHT - (int) (height * scale + 0.5)) / 2;
- }
- canvas.drawBitmap(bitmap, left, top, mPaint);
- BoxBlurFilter.apply(target,
- BoxBlurFilter.MODE_REPEAT, BoxBlurFilter.MODE_CLAMP);
- return target;
- }
-
- private void startTransition(Bitmap bitmap) {
- BitmapTexture texture = new BitmapTexture(bitmap);
- if (mBackground == null) {
- mBackground = texture;
- } else {
- if (mOldBackground != null) mOldBackground.recycle();
- mOldBackground = mBackground;
- mBackground = texture;
- mAnimation.start();
- }
- invalidate();
- }
-
- public void setImage(Bitmap bitmap) {
- if (mAnimation.isActive()) {
- mPendingBitmap = bitmap;
- } else {
- startTransition(bitmap);
- }
- }
-
- public void setScrollPosition(int position) {
- if (mScrollX == position) return;
- mScrollX = position;
- invalidate();
- }
-
- @Override
- protected void render(GLCanvas canvas) {
- if (mBackground == null) return;
-
- int height = getHeight();
- float scale = (float) height / BACKGROUND_HEIGHT;
- int width = (int) (BACKGROUND_WIDTH * scale + 0.5f);
- int scroll = mScrollX;
- int start = (scroll / width) * width;
-
- if (mOldBackground == null) {
- for (int i = start, n = scroll + getWidth(); i < n; i += width) {
- mBackground.draw(canvas, i - scroll, 0, width, height);
- }
- } else {
- boolean moreAnimation =
- mAnimation.calculate(canvas.currentAnimationTimeMillis());
- float ratio = mAnimation.get();
- for (int i = start, n = scroll + getWidth(); i < n; i += width) {
- canvas.drawMixed(mOldBackground,
- mBackground, ratio, i - scroll, 0, width, height);
- }
- if (moreAnimation) {
- invalidate();
- } else if (mPendingBitmap != null) {
- startTransition(mPendingBitmap);
- mPendingBitmap = null;
- }
- }
- }
-}
diff --git a/src/com/android/gallery3d/ui/AlbumLabelMaker.java b/src/com/android/gallery3d/ui/AlbumLabelMaker.java
new file mode 100644
index 0000000..f837092
--- /dev/null
+++ b/src/com/android/gallery3d/ui/AlbumLabelMaker.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Typeface;
+import android.text.TextPaint;
+import android.text.TextUtils;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.data.BitmapPool;
+import com.android.gallery3d.data.DataSourceType;
+import com.android.gallery3d.util.ThreadPool;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+public class AlbumLabelMaker {
+ private static final int FONT_COLOR_TITLE = Color.WHITE;
+ private static final int FONT_COLOR_COUNT = 0x80FFFFFF; // 50% white
+
+ // We keep a border around the album label to prevent aliasing
+ private static final int BORDER_SIZE = 1;
+ private static final int BACKGROUND_COLOR = 0x60000000; // 36% Dark
+
+ private final AlbumSetSlotRenderer.LabelSpec mSpec;
+ private final TextPaint mTitlePaint;
+ private final TextPaint mCountPaint;
+ private final Context mContext;
+
+ private int mLabelWidth;
+ private BitmapPool mBitmapPool;
+
+ private final LazyLoadedBitmap mLocalSetIcon;
+ private final LazyLoadedBitmap mPicasaIcon;
+ private final LazyLoadedBitmap mCameraIcon;
+ private final LazyLoadedBitmap mMtpIcon;
+
+ public AlbumLabelMaker(Context context, AlbumSetSlotRenderer.LabelSpec spec) {
+ mContext = context;
+ mSpec = spec;
+ mTitlePaint = getTextPaint(spec.titleFontSize, FONT_COLOR_TITLE, false);
+ mCountPaint = getTextPaint(spec.countFontSize, FONT_COLOR_COUNT, true);
+
+ mLocalSetIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_folder);
+ mPicasaIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_picasa);
+ mCameraIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_camera);
+ mMtpIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_ptp);
+ }
+
+ public static int getBorderSize() {
+ return BORDER_SIZE;
+ }
+
+ private Bitmap getOverlayAlbumIcon(int sourceType) {
+ switch (sourceType) {
+ case DataSourceType.TYPE_CAMERA:
+ return mCameraIcon.get();
+ case DataSourceType.TYPE_LOCAL:
+ return mLocalSetIcon.get();
+ case DataSourceType.TYPE_MTP:
+ return mMtpIcon.get();
+ case DataSourceType.TYPE_PICASA:
+ return mPicasaIcon.get();
+ }
+ return null;
+ }
+
+ private static TextPaint getTextPaint(int textSize, int color, boolean isBold) {
+ TextPaint paint = new TextPaint();
+ paint.setTextSize(textSize);
+ paint.setAntiAlias(true);
+ paint.setColor(color);
+ paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
+ if (isBold) {
+ paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+ }
+ return paint;
+ }
+
+ private class LazyLoadedBitmap {
+ private Bitmap mBitmap;
+ private int mResId;
+
+ public LazyLoadedBitmap(int resId) {
+ mResId = resId;
+ }
+
+ public synchronized Bitmap get() {
+ if (mBitmap == null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ mBitmap = BitmapFactory.decodeResource(
+ mContext.getResources(), mResId, options);
+ }
+ return mBitmap;
+ }
+ }
+
+ public synchronized void setLabelWidth(int width) {
+ if (mLabelWidth == width) return;
+ mLabelWidth = width;
+ int borders = 2 * BORDER_SIZE;
+ mBitmapPool = new BitmapPool(
+ width + borders, mSpec.labelBackgroundHeight + borders, 16);
+ }
+
+ public ThreadPool.Job<Bitmap> requestLabel(
+ String title, String count, int sourceType) {
+ return new AlbumLabelJob(title, count, sourceType);
+ }
+
+ static void drawText(Canvas canvas,
+ int x, int y, String text, int lengthLimit, TextPaint p) {
+ // The TextPaint cannot be used concurrently
+ synchronized (p) {
+ text = TextUtils.ellipsize(
+ text, p, lengthLimit, TextUtils.TruncateAt.END).toString();
+ canvas.drawText(text, x, y - p.getFontMetricsInt().ascent, p);
+ }
+ }
+
+ private class AlbumLabelJob implements ThreadPool.Job<Bitmap> {
+ private final String mTitle;
+ private final String mCount;
+ private final int mSourceType;
+
+ public AlbumLabelJob(String title, String count, int sourceType) {
+ mTitle = title;
+ mCount = count;
+ mSourceType = sourceType;
+ }
+
+ @Override
+ public Bitmap run(JobContext jc) {
+ AlbumSetSlotRenderer.LabelSpec s = mSpec;
+
+ String title = mTitle;
+ String count = mCount;
+ Bitmap icon = getOverlayAlbumIcon(mSourceType);
+
+ Bitmap bitmap;
+ int labelWidth;
+
+ synchronized (this) {
+ labelWidth = mLabelWidth;
+ bitmap = mBitmapPool.getBitmap();
+ }
+
+ if (bitmap == null) {
+ int borders = 2 * BORDER_SIZE;
+ bitmap = Bitmap.createBitmap(labelWidth + borders,
+ s.labelBackgroundHeight + borders, Config.ARGB_8888);
+ }
+
+ Canvas canvas = new Canvas(bitmap);
+ canvas.clipRect(BORDER_SIZE, BORDER_SIZE,
+ bitmap.getWidth() - BORDER_SIZE,
+ bitmap.getHeight() - BORDER_SIZE);
+ canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC);
+
+ canvas.translate(BORDER_SIZE, BORDER_SIZE);
+
+ // draw title
+ if (jc.isCancelled()) return null;
+ int x = s.leftMargin;
+ int y = s.titleOffset;
+ drawText(canvas, x, y, title, labelWidth - s.leftMargin, mTitlePaint);
+
+ // draw the count
+ if (jc.isCancelled()) return null;
+ if (icon != null) x = s.iconSize;
+ y += s.titleFontSize + s.countOffset;
+ drawText(canvas, x, y, count,
+ labelWidth - s.leftMargin - s.iconSize, mCountPaint);
+
+ // draw the icon
+ if (icon != null) {
+ if (jc.isCancelled()) return null;
+ float scale = (float) s.iconSize / icon.getWidth();
+ canvas.translate(0, bitmap.getHeight()
+ - Math.round(scale * icon.getHeight()));
+ canvas.scale(scale, scale);
+ canvas.drawBitmap(icon, 0, 0, null);
+ }
+
+ return bitmap;
+ }
+ }
+
+ public void recycleLabel(Bitmap label) {
+ mBitmapPool.recycle(label);
+ }
+
+ public void clearRecycledLabels() {
+ mBitmapPool.clear();
+ }
+}
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
index 87ff557..7fa7df4 100644
--- a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
@@ -1,4 +1,4 @@
-/*
+/*T
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,37 +17,33 @@
package com.android.gallery3d.ui;
import android.graphics.Bitmap;
-import android.graphics.Color;
import android.os.Message;
import com.android.gallery3d.R;
+import com.android.gallery3d.app.AlbumSetDataLoader;
import com.android.gallery3d.app.GalleryActivity;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.DataSourceType;
import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;
-import com.android.gallery3d.ui.AlbumSetView.AlbumSetItem;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.FutureListener;
import com.android.gallery3d.util.GalleryUtils;
-import com.android.gallery3d.util.MediaSetUtils;
import com.android.gallery3d.util.ThreadPool;
-public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
- private static final String TAG = "GallerySlidingWindow";
- private static final int MSG_LOAD_BITMAP_DONE = 0;
- private static final int PLACEHOLDER_COLOR = 0xFF222222;
+public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener {
+ private static final String TAG = "AlbumSetSlidingWindow";
+ private static final int MSG_UPDATE_ALBUM_ENTRY = 1;
public static interface Listener {
public void onSizeChanged(int size);
- public void onContentInvalidated();
- public void onWindowContentChanged(
- int slot, AlbumSetItem old, AlbumSetItem update);
+ public void onContentChanged();
}
- private final AlbumSetView.Model mSource;
+ private final AlbumSetDataLoader mSource;
private int mSize;
- private final AlbumSetView.LabelSpec mLabelSpec;
private int mContentStart = 0;
private int mContentEnd = 0;
@@ -57,61 +53,70 @@
private Listener mListener;
- private final MyAlbumSetItem mData[];
- private SelectionDrawer mSelectionDrawer;
- private final ColorTexture mWaitLoadingTexture;
-
+ private final AlbumSetEntry mData[];
private final SynchronizedHandler mHandler;
private final ThreadPool mThreadPool;
+ private final AlbumLabelMaker mLabelMaker;
+ private final String mLoadingText;
+ private final TextureUploader mTextureUploader;
private int mActiveRequestCount = 0;
- private final String mLoadingLabel;
private boolean mIsActive = false;
+ private BitmapTexture mLoadingLabel;
- private static class MyAlbumSetItem extends AlbumSetItem {
+ private int mSlotWidth;
+
+ public static class AlbumSetEntry {
+ public MediaSet album;
+ public MediaItem coverItem;
+ public Texture content;
+ public Texture label;
public Path setPath;
+ public String title;
+ public int totalCount;
public int sourceType;
public int cacheFlag;
public int cacheStatus;
+ public int rotation;
+ public int mediaType;
+ public boolean isPanorama;
+ public boolean isWaitLoadingDisplayed;
+ public long setDataVersion;
+ public long coverDataVersion;
+ private BitmapLoader labelLoader;
+ private BitmapLoader coverLoader;
}
public AlbumSetSlidingWindow(GalleryActivity activity,
- AlbumSetView.LabelSpec labelSpec, SelectionDrawer drawer,
- AlbumSetView.Model source, int cacheSize) {
+ AlbumSetDataLoader source, AlbumSetSlotRenderer.LabelSpec labelSpec, int cacheSize) {
source.setModelListener(this);
- mLabelSpec = labelSpec;
- mLoadingLabel = activity.getAndroidContext().getString(R.string.loading);
mSource = source;
- mSelectionDrawer = drawer;
- mData = new MyAlbumSetItem[cacheSize];
+ mData = new AlbumSetEntry[cacheSize];
mSize = source.size();
+ mThreadPool = activity.getThreadPool();
- mWaitLoadingTexture = new ColorTexture(PLACEHOLDER_COLOR);
- mWaitLoadingTexture.setSize(1, 1);
+ mLabelMaker = new AlbumLabelMaker(activity.getAndroidContext(), labelSpec);
+ mLoadingText = activity.getAndroidContext().getString(R.string.loading);
+ mTextureUploader = new TextureUploader(activity.getGLRoot());
mHandler = new SynchronizedHandler(activity.getGLRoot()) {
@Override
public void handleMessage(Message message) {
- Utils.assertTrue(message.what == MSG_LOAD_BITMAP_DONE);
- ((GalleryDisplayItem) message.obj).onLoadBitmapDone();
+ Utils.assertTrue(message.what == MSG_UPDATE_ALBUM_ENTRY);
+ ((EntryUpdater) message.obj).updateEntry();
}
};
-
- mThreadPool = activity.getThreadPool();
- }
-
- public void setSelectionDrawer(SelectionDrawer drawer) {
- mSelectionDrawer = drawer;
}
public void setListener(Listener listener) {
mListener = listener;
}
- public AlbumSetItem get(int slotIndex) {
- Utils.assertTrue(isActiveSlot(slotIndex),
- "invalid slot: %s outsides (%s, %s)",
- slotIndex, mActiveStart, mActiveEnd);
+ public AlbumSetEntry get(int slotIndex) {
+ if (!isActiveSlot(slotIndex)) {
+ Utils.fail("invalid slot: %s outsides (%s, %s)",
+ slotIndex, mActiveStart, mActiveEnd);
+ }
return mData[slotIndex % mData.length];
}
@@ -155,21 +160,23 @@
}
public void setActiveWindow(int start, int end) {
- Utils.assertTrue(
- start <= end && end - start <= mData.length && end <= mSize,
- "start = %s, end = %s, length = %s, size = %s",
- start, end, mData.length, mSize);
+ if (!(start <= end && end - start <= mData.length && end <= mSize)) {
+ Utils.fail("start = %s, end = %s, length = %s, size = %s",
+ start, end, mData.length, mSize);
+ }
- AlbumSetItem data[] = mData;
-
+ AlbumSetEntry data[] = mData;
mActiveStart = start;
mActiveEnd = end;
-
int contentStart = Utils.clamp((start + end) / 2 - data.length / 2,
0, Math.max(0, mSize - data.length));
int contentEnd = Math.min(contentStart + data.length, mSize);
setContentWindow(contentStart, contentEnd);
- if (mIsActive) updateAllImageRequests();
+
+ if (mIsActive) {
+ updateTextureUploadQueue();
+ updateAllImageRequests();
+ }
}
// We would like to request non active slots in the following order:
@@ -197,129 +204,136 @@
private void requestImagesInSlot(int slotIndex) {
if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
- AlbumSetItem items = mData[slotIndex % mData.length];
- for (DisplayItem item : items.covers) {
- ((GalleryDisplayItem) item).requestImage();
- }
+ AlbumSetEntry entry = mData[slotIndex % mData.length];
+ if (entry.coverLoader != null) entry.coverLoader.startLoad();
+ if (entry.labelLoader != null) entry.labelLoader.startLoad();
}
private void cancelImagesInSlot(int slotIndex) {
if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
- AlbumSetItem items = mData[slotIndex % mData.length];
- for (DisplayItem item : items.covers) {
- ((GalleryDisplayItem) item).cancelImageRequest();
- }
+ AlbumSetEntry entry = mData[slotIndex % mData.length];
+ if (entry.coverLoader != null) entry.coverLoader.cancelLoad();
+ if (entry.labelLoader != null) entry.labelLoader.cancelLoad();
+ }
+
+ private static long getDataVersion(MediaObject object) {
+ return object == null
+ ? MediaSet.INVALID_DATA_VERSION
+ : object.getDataVersion();
}
private void freeSlotContent(int slotIndex) {
- AlbumSetItem data[] = mData;
- int index = slotIndex % data.length;
- AlbumSetItem original = data[index];
- if (original != null) {
- data[index] = null;
- for (DisplayItem item : original.covers) {
- ((GalleryDisplayItem) item).recycle();
- }
- }
+ AlbumSetEntry entry = mData[slotIndex % mData.length];
+ if (entry.coverLoader != null) entry.coverLoader.recycle();
+ if (entry.labelLoader != null) entry.labelLoader.recycle();
+ mData[slotIndex % mData.length] = null;
}
- private long getMediaSetDataVersion(MediaSet set) {
- return set == null
- ? MediaSet.INVALID_DATA_VERSION
- : set.getDataVersion();
+ private boolean isLabelChanged(
+ AlbumSetEntry entry, String title, int totalCount, int sourceType) {
+ return !Utils.equals(entry.title, title)
+ || entry.totalCount != totalCount
+ || entry.sourceType != sourceType;
+ }
+
+ private void updateAlbumSetEntry(AlbumSetEntry entry, int slotIndex) {
+ MediaSet album = mSource.getMediaSet(slotIndex);
+ MediaItem cover = mSource.getCoverItem(slotIndex);
+ int totalCount = mSource.getTotalCount(slotIndex);
+
+ entry.album = album;
+ entry.setDataVersion = getDataVersion(album);
+ entry.cacheFlag = identifyCacheFlag(album);
+ entry.cacheStatus = identifyCacheStatus(album);
+ entry.setPath = (album == null) ? null : album.getPath();
+
+ String title = (album == null) ? "" : Utils.ensureNotNull(album.getName());
+ int sourceType = DataSourceType.identifySourceType(album);
+ if (isLabelChanged(entry, title, totalCount, sourceType)) {
+ entry.title = title;
+ entry.totalCount = totalCount;
+ entry.sourceType = sourceType;
+ if (entry.labelLoader != null) {
+ entry.labelLoader.recycle();
+ entry.labelLoader = null;
+ entry.label = null;
+ }
+ if (album != null) {
+ entry.labelLoader = new AlbumLabelLoader(
+ slotIndex, title, totalCount, sourceType);
+ }
+ }
+
+ entry.coverItem = cover;
+ if (getDataVersion(cover) != entry.coverDataVersion) {
+ entry.coverDataVersion = getDataVersion(cover);
+ entry.isPanorama = GalleryUtils.isPanorama(cover);
+ entry.rotation = (cover == null) ? 0 : cover.getRotation();
+ entry.mediaType = (cover == null) ? 0 : cover.getMediaType();
+ if (entry.coverLoader != null) {
+ entry.coverLoader.recycle();
+ entry.coverLoader = null;
+ entry.content = null;
+ }
+ if (cover != null) {
+ entry.coverLoader = new AlbumCoverLoader(slotIndex, cover);
+ }
+ }
}
private void prepareSlotContent(int slotIndex) {
- MediaSet set = mSource.getMediaSet(slotIndex);
-
- MyAlbumSetItem item = new MyAlbumSetItem();
- MediaItem[] coverItems = mSource.getCoverItems(slotIndex);
- item.covers = new GalleryDisplayItem[coverItems.length];
- item.sourceType = identifySourceType(set);
- item.cacheFlag = identifyCacheFlag(set);
- item.cacheStatus = identifyCacheStatus(set);
- item.setPath = set == null ? null : set.getPath();
-
- for (int i = 0; i < coverItems.length; ++i) {
- item.covers[i] = new GalleryDisplayItem(slotIndex, i, coverItems[i]);
- }
- item.labelItem = new LabelDisplayItem(slotIndex);
- item.setDataVersion = getMediaSetDataVersion(set);
- mData[slotIndex % mData.length] = item;
+ AlbumSetEntry entry = new AlbumSetEntry();
+ updateAlbumSetEntry(entry, slotIndex);
+ mData[slotIndex % mData.length] = entry;
}
- private boolean isCoverItemsChanged(int slotIndex) {
- AlbumSetItem original = mData[slotIndex % mData.length];
- if (original == null) return true;
- MediaItem[] coverItems = mSource.getCoverItems(slotIndex);
-
- if (original.covers.length != coverItems.length) return true;
- for (int i = 0, n = coverItems.length; i < n; ++i) {
- GalleryDisplayItem g = (GalleryDisplayItem) original.covers[i];
- if (g.mDataVersion != coverItems[i].getDataVersion()) return true;
- }
- return false;
+ private static boolean startLoadBitmap(BitmapLoader loader) {
+ if (loader == null) return false;
+ loader.startLoad();
+ return loader.isRequestInProgress();
}
- private void updateSlotContent(final int slotIndex) {
-
- MyAlbumSetItem data[] = mData;
- int pos = slotIndex % data.length;
- MyAlbumSetItem original = data[pos];
-
- if (!isCoverItemsChanged(slotIndex)) {
- MediaSet set = mSource.getMediaSet(slotIndex);
- original.sourceType = identifySourceType(set);
- original.cacheFlag = identifyCacheFlag(set);
- original.cacheStatus = identifyCacheStatus(set);
- original.setPath = set == null ? null : set.getPath();
- ((LabelDisplayItem) original.labelItem).updateContent();
- if (mListener != null) mListener.onContentInvalidated();
- return;
+ private void uploadBackgroundTextureInSlot(int index) {
+ if (index < mContentStart || index >= mContentEnd) return;
+ AlbumSetEntry entry = mData[index % mData.length];
+ if (entry.content instanceof BitmapTexture) {
+ mTextureUploader.addBgTexture((BitmapTexture) entry.content);
}
-
- prepareSlotContent(slotIndex);
- AlbumSetItem update = data[pos];
-
- if (mListener != null && isActiveSlot(slotIndex)) {
- mListener.onWindowContentChanged(slotIndex, original, update);
+ if (entry.label instanceof BitmapTexture) {
+ mTextureUploader.addBgTexture((BitmapTexture) entry.label);
}
- if (original != null) {
- for (DisplayItem item : original.covers) {
- ((GalleryDisplayItem) item).recycle();
+ }
+
+ private void updateTextureUploadQueue() {
+ if (!mIsActive) return;
+ mTextureUploader.clear();
+
+ // Upload foreground texture
+ for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
+ AlbumSetEntry entry = mData[i % mData.length];
+ if (entry.content instanceof BitmapTexture) {
+ mTextureUploader.addFgTexture((BitmapTexture) entry.content);
+ }
+ if (entry.label instanceof BitmapTexture) {
+ mTextureUploader.addFgTexture((BitmapTexture) entry.label);
}
}
- }
- private void notifySlotChanged(int slotIndex) {
- // If the updated content is not cached, ignore it
- if (slotIndex < mContentStart || slotIndex >= mContentEnd) {
- Log.w(TAG, String.format(
- "invalid update: %s is outside (%s, %s)",
- slotIndex, mContentStart, mContentEnd) );
- return;
- }
- updateSlotContent(slotIndex);
- boolean isActiveSlot = isActiveSlot(slotIndex);
- if (mActiveRequestCount == 0 || isActiveSlot) {
- for (DisplayItem item : mData[slotIndex % mData.length].covers) {
- GalleryDisplayItem galleryItem = (GalleryDisplayItem) item;
- galleryItem.requestImage();
- if (isActiveSlot && galleryItem.isRequestInProgress()) {
- ++mActiveRequestCount;
- }
- }
+ // add background textures
+ int range = Math.max(
+ (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
+ for (int i = 0; i < range; ++i) {
+ uploadBackgroundTextureInSlot(mActiveEnd + i);
+ uploadBackgroundTextureInSlot(mActiveStart - i - 1);
}
}
private void updateAllImageRequests() {
mActiveRequestCount = 0;
for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
- for (DisplayItem item : mData[i % mData.length].covers) {
- GalleryDisplayItem coverItem = (GalleryDisplayItem) item;
- coverItem.requestImage();
- if (coverItem.isRequestInProgress()) ++mActiveRequestCount;
- }
+ AlbumSetEntry entry = mData[i % mData.length];
+ if (startLoadBitmap(entry.coverLoader)) ++mActiveRequestCount;
+ if (startLoadBitmap(entry.labelLoader)) ++mActiveRequestCount;
}
if (mActiveRequestCount == 0) {
requestNonactiveImages();
@@ -328,143 +342,114 @@
}
}
- private class GalleryDisplayItem extends AbstractDisplayItem
- implements FutureListener<Bitmap> {
- private Future<Bitmap> mFuture;
- private final int mSlotIndex;
- private final int mCoverIndex;
- private final int mMediaType;
- private Texture mContent;
- private final long mDataVersion;
- private final boolean mIsPanorama;
- private boolean mWaitLoadingDisplayed;
-
- public GalleryDisplayItem(int slotIndex, int coverIndex, MediaItem item) {
- super(item);
- mSlotIndex = slotIndex;
- mCoverIndex = coverIndex;
- mMediaType = item.getMediaType();
- mDataVersion = item.getDataVersion();
- mIsPanorama = GalleryUtils.isPanorama(item);
- updateContent(mWaitLoadingTexture);
- }
-
- @Override
- protected void onBitmapAvailable(Bitmap bitmap) {
- if (isActiveSlot(mSlotIndex)) {
- --mActiveRequestCount;
- if (mActiveRequestCount == 0) requestNonactiveImages();
- }
- if (bitmap != null) {
- BitmapTexture texture = new BitmapTexture(bitmap, true);
- texture.setThrottled(true);
- if (mWaitLoadingDisplayed) {
- updateContent(new FadeInTexture(PLACEHOLDER_COLOR, texture));
- } else {
- updateContent(texture);
- }
- if (mListener != null) mListener.onContentInvalidated();
- }
- }
-
- private void updateContent(Texture content) {
- mContent = content;
- }
-
- @Override
- public int render(GLCanvas canvas, int pass) {
- // Fit the content into the box
- int width = mContent.getWidth();
- int height = mContent.getHeight();
-
- float scalex = mBoxWidth / (float) width;
- float scaley = mBoxHeight / (float) height;
- float scale = Math.min(scalex, scaley);
-
- width = (int) Math.floor(width * scale);
- height = (int) Math.floor(height * scale);
-
- // Now draw it
- int sourceType = SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED;
- int cacheFlag = MediaSet.CACHE_FLAG_NO;
- int cacheStatus = MediaSet.CACHE_STATUS_NOT_CACHED;
- MyAlbumSetItem set = mData[mSlotIndex % mData.length];
- Path path = set.setPath;
- if (mCoverIndex == 0) {
- sourceType = set.sourceType;
- cacheFlag = set.cacheFlag;
- cacheStatus = set.cacheStatus;
- }
-
- mSelectionDrawer.draw(canvas, mContent, width, height,
- getRotation(), path, sourceType, mMediaType,
- mIsPanorama, mLabelSpec.labelBackgroundHeight,
- cacheFlag == MediaSet.CACHE_FLAG_FULL,
- (cacheFlag == MediaSet.CACHE_FLAG_FULL)
- && (cacheStatus != MediaSet.CACHE_STATUS_CACHED_FULL));
-
- if (mContent == mWaitLoadingTexture) {
- mWaitLoadingDisplayed = true;
- }
-
- if ((mContent instanceof FadeInTexture) &&
- ((FadeInTexture) mContent).isAnimating()) {
- return RENDER_MORE_FRAME;
- } else {
- return 0;
- }
- }
-
- @Override
- public void startLoadBitmap() {
- mFuture = mThreadPool.submit(mMediaItem.requestImage(
- MediaItem.TYPE_MICROTHUMBNAIL), this);
- }
-
- @Override
- public void cancelLoadBitmap() {
- mFuture.cancel();
- }
-
- @Override
- public void onFutureDone(Future<Bitmap> future) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_LOAD_BITMAP_DONE, this));
- }
-
- private void onLoadBitmapDone() {
- Future<Bitmap> future = mFuture;
- mFuture = null;
- updateImage(future.get(), future.isCancelled());
- }
-
- @Override
- public String toString() {
- return String.format("GalleryDisplayItem(%s, %s)", mSlotIndex, mCoverIndex);
+ @Override
+ public void onSizeChanged(int size) {
+ if (mIsActive && mSize != size) {
+ mSize = size;
+ if (mListener != null) mListener.onSizeChanged(mSize);
+ if (mContentEnd > mSize) mContentEnd = mSize;
+ if (mActiveEnd > mSize) mActiveEnd = mSize;
}
}
- private static int identifySourceType(MediaSet set) {
- if (set == null) {
- return SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED;
+ @Override
+ public void onContentChanged(int index) {
+ if (!mIsActive) {
+ // paused, ignore slot changed event
+ return;
}
- Path path = set.getPath();
- if (MediaSetUtils.isCameraSource(path)) {
- return SelectionDrawer.DATASOURCE_TYPE_CAMERA;
+ // If the updated content is not cached, ignore it
+ if (index < mContentStart || index >= mContentEnd) {
+ Log.w(TAG, String.format(
+ "invalid update: %s is outside (%s, %s)",
+ index, mContentStart, mContentEnd) );
+ return;
}
- int type = SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED;
- String prefix = path.getPrefix();
+ AlbumSetEntry entry = mData[index % mData.length];
+ updateAlbumSetEntry(entry, index);
+ updateAllImageRequests();
+ updateTextureUploadQueue();
+ if (mListener != null && isActiveSlot(index)) {
+ mListener.onContentChanged();
+ }
+ }
- if (prefix.equals("picasa")) {
- type = SelectionDrawer.DATASOURCE_TYPE_PICASA;
- } else if (prefix.equals("local") || prefix.equals("merge")) {
- type = SelectionDrawer.DATASOURCE_TYPE_LOCAL;
- } else if (prefix.equals("mtp")) {
- type = SelectionDrawer.DATASOURCE_TYPE_MTP;
+ public BitmapTexture getLoadingTexture() {
+ if (mLoadingLabel == null) {
+ Bitmap bitmap = mLabelMaker.requestLabel(
+ mLoadingText, "", DataSourceType.TYPE_NOT_CATEGORIZED)
+ .run(ThreadPool.JOB_CONTEXT_STUB);
+ mLoadingLabel = new BitmapTexture(bitmap);
+ mLoadingLabel.setOpaque(false);
+ }
+ return mLoadingLabel;
+ }
+
+ public void pause() {
+ mIsActive = false;
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ freeSlotContent(i);
+ }
+ mLabelMaker.clearRecycledLabels();
+ }
+
+ public void resume() {
+ mIsActive = true;
+ for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+ prepareSlotContent(i);
+ }
+ updateAllImageRequests();
+ }
+
+ private static interface EntryUpdater {
+ public void updateEntry();
+ }
+
+ private class AlbumCoverLoader extends BitmapLoader implements EntryUpdater {
+ private MediaItem mMediaItem;
+ private final int mSlotIndex;
+
+ public AlbumCoverLoader(int slotIndex, MediaItem item) {
+ mSlotIndex = slotIndex;
+ mMediaItem = item;
}
- return type;
+ @Override
+ protected void recycleBitmap(Bitmap bitmap) {
+ MediaItem.getMicroThumbPool().recycle(bitmap);
+ }
+
+ @Override
+ protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
+ return mThreadPool.submit(mMediaItem.requestImage(
+ MediaItem.TYPE_MICROTHUMBNAIL), l);
+ }
+
+ @Override
+ protected void onLoadComplete(Bitmap bitmap) {
+ mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
+ }
+
+ @Override
+ public void updateEntry() {
+ Bitmap bitmap = getBitmap();
+ if (bitmap == null) return; // error or recycled
+
+ AlbumSetEntry entry = mData[mSlotIndex % mData.length];
+ BitmapTexture texture = new BitmapTexture(bitmap);
+ entry.content = texture;
+
+ if (isActiveSlot(mSlotIndex)) {
+ mTextureUploader.addFgTexture(texture);
+ --mActiveRequestCount;
+ if (mActiveRequestCount == 0) requestNonactiveImages();
+ if (mListener != null) mListener.onContentChanged();
+ } else {
+ mTextureUploader.addBgTexture(texture);
+ }
+ }
}
private static int identifyCacheFlag(MediaSet set) {
@@ -485,102 +470,79 @@
return set.getCacheStatus();
}
- private class LabelDisplayItem extends DisplayItem {
- private static final int FONT_COLOR_TITLE = Color.WHITE;
- private static final int FONT_COLOR_COUNT = 0x80FFFFFF; // 50% white
-
- private StringTexture mTextureTitle;
- private StringTexture mTextureCount;
- private String mTitle;
- private String mCount;
- private int mLastWidth;
+ private class AlbumLabelLoader extends BitmapLoader implements EntryUpdater {
private final int mSlotIndex;
- private boolean mHasIcon;
+ private final String mTitle;
+ private final int mTotalCount;
+ private final int mSourceType;
- public LabelDisplayItem(int slotIndex) {
+ public AlbumLabelLoader(
+ int slotIndex, String title, int totalCount, int sourceType) {
mSlotIndex = slotIndex;
- }
-
- public boolean updateContent() {
- String title = mLoadingLabel;
- String count = "";
- MediaSet set = mSource.getMediaSet(mSlotIndex);
- if (set != null) {
- title = Utils.ensureNotNull(set.getName());
- count = "" + set.getTotalMediaItemCount();
- }
- if (Utils.equals(title, mTitle)
- && Utils.equals(count, mCount)
- && Utils.equals(mBoxWidth, mLastWidth)) {
- return false;
- }
mTitle = title;
- mCount = count;
- mLastWidth = mBoxWidth;
- mHasIcon = (identifySourceType(set) !=
- SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED);
-
- AlbumSetView.LabelSpec s = mLabelSpec;
- mTextureTitle = StringTexture.newInstance(
- title, s.titleFontSize, FONT_COLOR_TITLE,
- mBoxWidth - s.leftMargin, false);
- mTextureCount = StringTexture.newInstance(
- count, s.countFontSize, FONT_COLOR_COUNT,
- mBoxWidth - s.leftMargin, true);
-
- return true;
+ mTotalCount = totalCount;
+ mSourceType = sourceType;
}
@Override
- public int render(GLCanvas canvas, int pass) {
- if (mBoxWidth != mLastWidth) {
- updateContent();
+ protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
+ return mThreadPool.submit(mLabelMaker.requestLabel(
+ mTitle, String.valueOf(mTotalCount), mSourceType), l);
+ }
+
+ @Override
+ protected void recycleBitmap(Bitmap bitmap) {
+ mLabelMaker.recycleLabel(bitmap);
+ }
+
+ @Override
+ protected void onLoadComplete(Bitmap bitmap) {
+ mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
+ }
+
+ @Override
+ public void updateEntry() {
+ Bitmap bitmap = getBitmap();
+ if (bitmap == null) return; // Error or recycled
+
+ AlbumSetEntry entry = mData[mSlotIndex % mData.length];
+ BitmapTexture texture = new BitmapTexture(bitmap);
+ texture.setOpaque(false);
+ entry.label = texture;
+
+ if (isActiveSlot(mSlotIndex)) {
+ mTextureUploader.addFgTexture(texture);
+ --mActiveRequestCount;
+ if (mActiveRequestCount == 0) requestNonactiveImages();
+ if (mListener != null) mListener.onContentChanged();
+ } else {
+ mTextureUploader.addBgTexture(texture);
}
-
- AlbumSetView.LabelSpec s = mLabelSpec;
- int x = -mBoxWidth / 2;
- int y = (mBoxHeight + 1) / 2 - s.labelBackgroundHeight;
- y += s.titleOffset;
- mTextureTitle.draw(canvas, x + s.leftMargin, y);
- y += s.titleFontSize + s.countOffset;
- x += mHasIcon ? s.iconSize : s.leftMargin;
- mTextureCount.draw(canvas, x, y);
- return 0;
- }
-
- @Override
- public long getIdentity() {
- return System.identityHashCode(this);
}
}
- public void onSizeChanged(int size) {
- if (mIsActive && mSize != size) {
- mSize = size;
- if (mListener != null) mListener.onSizeChanged(mSize);
- }
- }
+ public void onSlotSizeChanged(int width, int height) {
+ if (mSlotWidth == width) return;
- public void onWindowContentChanged(int index) {
- if (!mIsActive) {
- // paused, ignore slot changed event
- return;
- }
- notifySlotChanged(index);
- }
+ mSlotWidth = width;
+ mLoadingLabel = null;
+ mLabelMaker.setLabelWidth(mSlotWidth);
- public void pause() {
- mIsActive = false;
+ if (!mIsActive) return;
+
for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
- freeSlotContent(i);
- }
- }
-
- public void resume() {
- mIsActive = true;
- for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
- prepareSlotContent(i);
+ AlbumSetEntry entry = mData[i % mData.length];
+ if (entry.labelLoader != null) {
+ entry.labelLoader.recycle();
+ entry.labelLoader = null;
+ entry.label = null;
+ }
+ if (entry.album != null) {
+ entry.labelLoader = new AlbumLabelLoader(i,
+ entry.title, entry.totalCount, entry.sourceType);
+ }
}
updateAllImageRequests();
+ updateTextureUploadQueue();
}
}
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java
new file mode 100644
index 0000000..b0e6153
--- /dev/null
+++ b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.content.Context;
+
+import com.android.gallery3d.app.AlbumSetDataLoader;
+import com.android.gallery3d.app.GalleryActivity;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.ui.AlbumSetSlidingWindow.AlbumSetEntry;
+
+public class AlbumSetSlotRenderer extends AbstractSlotRenderer {
+ @SuppressWarnings("unused")
+ private static final String TAG = "AlbumSetView";
+ private static final int CACHE_SIZE = 96;
+ private static final int PLACEHOLDER_COLOR = 0xFF222222;
+
+ private final ColorTexture mWaitLoadingTexture;
+ private final GalleryActivity mActivity;
+ private final SelectionManager mSelectionManager;
+ protected final LabelSpec mLabelSpec;
+
+ protected AlbumSetSlidingWindow mDataWindow;
+ private SlotView mSlotView;
+
+ private int mPressedIndex = -1;
+ private boolean mAnimatePressedUp;
+ private Path mHighlightItemPath = null;
+ private boolean mInSelectionMode;
+
+ public static class LabelSpec {
+ public int labelBackgroundHeight;
+ public int titleOffset;
+ public int countOffset;
+ public int titleFontSize;
+ public int countFontSize;
+ public int leftMargin;
+ public int iconSize;
+ }
+
+ public AlbumSetSlotRenderer(GalleryActivity activity, SelectionManager selectionManager,
+ SlotView slotView, LabelSpec labelSpec) {
+ super ((Context) activity);
+ mActivity = activity;
+ mSelectionManager = selectionManager;
+ mSlotView = slotView;
+ mLabelSpec = labelSpec;
+
+ mWaitLoadingTexture = new ColorTexture(PLACEHOLDER_COLOR);
+ mWaitLoadingTexture.setSize(1, 1);
+
+ Context context = activity.getAndroidContext();
+ }
+
+ public void setPressedIndex(int index) {
+ if (mPressedIndex == index) return;
+ mPressedIndex = index;
+ mSlotView.invalidate();
+ }
+
+ public void setPressedUp() {
+ if (mPressedIndex == -1) return;
+ mAnimatePressedUp = true;
+ mSlotView.invalidate();
+ }
+
+ public void setHighlightItemPath(Path path) {
+ if (mHighlightItemPath == path) return;
+ mHighlightItemPath = path;
+ mSlotView.invalidate();
+ }
+
+ public void setModel(AlbumSetDataLoader model) {
+ if (mDataWindow != null) {
+ mDataWindow.setListener(null);
+ mDataWindow = null;
+ mSlotView.setSlotCount(0);
+ }
+ if (model != null) {
+ mDataWindow = new AlbumSetSlidingWindow(
+ mActivity, model, mLabelSpec, CACHE_SIZE);
+ mDataWindow.setListener(new MyCacheListener());
+ mSlotView.setSlotCount(mDataWindow.size());
+ }
+ }
+
+ private static Texture checkTexture(GLCanvas canvas, Texture texture) {
+ return ((texture == null) || ((texture instanceof UploadedTexture)
+ && !((UploadedTexture) texture).isContentValid(canvas)))
+ ? null
+ : texture;
+ }
+
+ @Override
+ public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) {
+ AlbumSetEntry entry = mDataWindow.get(index);
+ int renderRequestFlags = 0;
+ renderRequestFlags |= renderContent(canvas, entry, width, height);
+ renderRequestFlags |= renderLabel(canvas, entry, width, height);
+ renderRequestFlags |= renderOverlay(canvas, index, entry, width, height);
+ return renderRequestFlags;
+ }
+
+ protected int renderOverlay(
+ GLCanvas canvas, int index, AlbumSetEntry entry, int width, int height) {
+ int renderRequestFlags = 0;
+ if (mPressedIndex == index) {
+ if (mAnimatePressedUp) {
+ drawPressedUpFrame(canvas, width, height);
+ renderRequestFlags |= SlotView.RENDER_MORE_FRAME;
+ if (isPressedUpFrameFinished()) {
+ mAnimatePressedUp = false;
+ mPressedIndex = -1;
+ }
+ } else {
+ drawPressedFrame(canvas, width, height);
+ }
+ } else if ((mHighlightItemPath != null) && (mHighlightItemPath == entry.setPath)) {
+ drawSelectedFrame(canvas, width, height);
+ } else if (mInSelectionMode && mSelectionManager.isItemSelected(entry.setPath)) {
+ drawSelectedFrame(canvas, width, height);
+ }
+ return renderRequestFlags;
+ }
+
+ protected int renderContent(
+ GLCanvas canvas, AlbumSetEntry entry, int width, int height) {
+ int renderRequestFlags = 0;
+
+ Texture content = checkTexture(canvas, entry.content);
+ if (content == null) {
+ content = mWaitLoadingTexture;
+ entry.isWaitLoadingDisplayed = true;
+ } else if (entry.isWaitLoadingDisplayed) {
+ entry.isWaitLoadingDisplayed = false;
+ entry.content = new FadeInTexture(
+ PLACEHOLDER_COLOR, (BitmapTexture) entry.content);
+ content = entry.content;
+ }
+ drawContent(canvas, content, width, height, entry.rotation);
+ if ((content instanceof FadeInTexture) &&
+ ((FadeInTexture) content).isAnimating()) {
+ renderRequestFlags |= SlotView.RENDER_MORE_FRAME;
+ }
+
+ if (entry.mediaType == MediaObject.MEDIA_TYPE_VIDEO) {
+ drawVideoOverlay(canvas, width, height);
+ }
+
+ if (entry.isPanorama) {
+ drawPanoramaBorder(canvas, width, height);
+ }
+
+ return renderRequestFlags;
+ }
+
+ protected int renderLabel(
+ GLCanvas canvas, AlbumSetEntry entry, int width, int height) {
+ // We show the loading message only when the album is still loading
+ // (Not when we are still preparing the label)
+ Texture content = checkTexture(canvas, entry.label);
+ if (entry.album == null) {
+ content = mDataWindow.getLoadingTexture();
+ }
+ if (content != null) {
+ int b = AlbumLabelMaker.getBorderSize();
+ int h = content.getHeight();
+ content.draw(canvas, -b, height - h + b, width + b + b, h);
+ }
+ return 0;
+ }
+
+ @Override
+ public void prepareDrawing() {
+ mInSelectionMode = mSelectionManager.inSelectionMode();
+ }
+
+ private class MyCacheListener implements AlbumSetSlidingWindow.Listener {
+
+ @Override
+ public void onSizeChanged(int size) {
+ mSlotView.setSlotCount(size);
+ }
+
+ @Override
+ public void onContentChanged() {
+ mSlotView.invalidate();
+ }
+ }
+
+ public void pause() {
+ mDataWindow.pause();
+ }
+
+ public void resume() {
+ mDataWindow.resume();
+ }
+
+ @Override
+ public void onVisibleRangeChanged(int visibleStart, int visibleEnd) {
+ if (mDataWindow != null) {
+ mDataWindow.setActiveWindow(visibleStart, visibleEnd);
+ }
+ }
+
+ @Override
+ public void onSlotSizeChanged(int width, int height) {
+ if (mDataWindow != null) {
+ mDataWindow.onSlotSizeChanged(width, height);
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/ui/AlbumSetView.java b/src/com/android/gallery3d/ui/AlbumSetView.java
deleted file mode 100644
index 86398ec..0000000
--- a/src/com/android/gallery3d/ui/AlbumSetView.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import android.graphics.Rect;
-
-import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.ui.PositionRepository.Position;
-
-import java.util.Random;
-
-public class AlbumSetView extends SlotView {
- @SuppressWarnings("unused")
- private static final String TAG = "AlbumSetView";
- private static final int CACHE_SIZE = 32;
- private static final float PHOTO_DISTANCE = 35f;
-
- private int mVisibleStart;
- private int mVisibleEnd;
-
- private final Random mRandom = new Random();
- private final long mSeed = mRandom.nextLong();
-
- private AlbumSetSlidingWindow mDataWindow;
- private final GalleryActivity mActivity;
- private final LabelSpec mLabelSpec;
-
- private SelectionDrawer mSelectionDrawer;
-
- public static interface Model {
- public MediaItem[] getCoverItems(int index);
- public MediaSet getMediaSet(int index);
- public int size();
- public void setActiveWindow(int start, int end);
- public void setModelListener(ModelListener listener);
- }
-
- public static interface ModelListener {
- public void onWindowContentChanged(int index);
- public void onSizeChanged(int size);
- }
-
- public static class AlbumSetItem {
- public DisplayItem[] covers;
- public DisplayItem labelItem;
- public long setDataVersion;
- }
-
- public static class LabelSpec {
- public int labelBackgroundHeight;
- public int titleOffset;
- public int countOffset;
- public int titleFontSize;
- public int countFontSize;
- public int leftMargin;
- public int iconSize;
- }
-
- public AlbumSetView(GalleryActivity activity, SelectionDrawer drawer,
- SlotView.Spec slotViewSpec, LabelSpec labelSpec) {
- super(activity.getAndroidContext());
- mActivity = activity;
- setSelectionDrawer(drawer);
- setSlotSpec(slotViewSpec);
- mLabelSpec = labelSpec;
- }
-
- public void setSelectionDrawer(SelectionDrawer drawer) {
- mSelectionDrawer = drawer;
- if (mDataWindow != null) {
- mDataWindow.setSelectionDrawer(drawer);
- }
- }
-
- public void setModel(AlbumSetView.Model model) {
- if (mDataWindow != null) {
- mDataWindow.setListener(null);
- setSlotCount(0);
- mDataWindow = null;
- }
- if (model != null) {
- mDataWindow = new AlbumSetSlidingWindow(mActivity, mLabelSpec,
- mSelectionDrawer, model, CACHE_SIZE);
- mDataWindow.setListener(new MyCacheListener());
- setSlotCount(mDataWindow.size());
- updateVisibleRange(getVisibleStart(), getVisibleEnd());
- }
- }
-
- private void putSlotContent(int slotIndex, AlbumSetItem entry) {
- // Get displayItems from mItemsetMap or create them from MediaSet.
- Utils.assertTrue(entry != null);
- Rect rect = getSlotRect(slotIndex);
-
- DisplayItem[] items = entry.covers;
- mRandom.setSeed(slotIndex ^ mSeed);
-
- int x = (rect.left + rect.right) / 2;
- int y = (rect.top + rect.bottom) / 2;
-
- Position basePosition = new Position(x, y, 0);
-
- // Put the cover items in reverse order, so that the first item is on
- // top of the rest.
- Position position = new Position(x, y, 0f);
- putDisplayItem(position, position, entry.labelItem);
-
- for (int i = 0, n = items.length; i < n; ++i) {
- DisplayItem item = items[i];
- float dx = 0;
- float dy = 0;
- float dz = 0f;
- float theta = 0;
- if (i != 0) {
- dz = i * PHOTO_DISTANCE;
- }
- position = new Position(x + dx, y + dy, dz);
- position.theta = theta;
- putDisplayItem(position, basePosition, item);
- }
-
- }
-
- private void freeSlotContent(int index, AlbumSetItem entry) {
- if (entry == null) return;
- for (DisplayItem item : entry.covers) {
- removeDisplayItem(item);
- }
- removeDisplayItem(entry.labelItem);
- }
-
- public int size() {
- return mDataWindow.size();
- }
-
- @Override
- public void onLayoutChanged(int width, int height) {
- updateVisibleRange(0, 0);
- updateVisibleRange(getVisibleStart(), getVisibleEnd());
- }
-
- @Override
- public void onScrollPositionChanged(int position) {
- super.onScrollPositionChanged(position);
- updateVisibleRange(getVisibleStart(), getVisibleEnd());
- }
-
- private void updateVisibleRange(int start, int end) {
- if (start == mVisibleStart && end == mVisibleEnd) {
- // we need to set the mDataWindow active range in any case.
- mDataWindow.setActiveWindow(start, end);
- return;
- }
- if (start >= mVisibleEnd || mVisibleStart >= end) {
- for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
- freeSlotContent(i, mDataWindow.get(i));
- }
- mDataWindow.setActiveWindow(start, end);
- for (int i = start; i < end; ++i) {
- putSlotContent(i, mDataWindow.get(i));
- }
- } else {
- for (int i = mVisibleStart; i < start; ++i) {
- freeSlotContent(i, mDataWindow.get(i));
- }
- for (int i = end, n = mVisibleEnd; i < n; ++i) {
- freeSlotContent(i, mDataWindow.get(i));
- }
- mDataWindow.setActiveWindow(start, end);
- for (int i = start, n = mVisibleStart; i < n; ++i) {
- putSlotContent(i, mDataWindow.get(i));
- }
- for (int i = mVisibleEnd; i < end; ++i) {
- putSlotContent(i, mDataWindow.get(i));
- }
- }
- mVisibleStart = start;
- mVisibleEnd = end;
-
- invalidate();
- }
-
- @Override
- protected void render(GLCanvas canvas) {
- mSelectionDrawer.prepareDrawing();
- super.render(canvas);
- }
-
- private class MyCacheListener implements AlbumSetSlidingWindow.Listener {
-
- public void onSizeChanged(int size) {
- if (setSlotCount(size)) {
- // If the layout parameters are changed, we need reput all items.
- // We keep the visible range at the same center but with size 0.
- // So that we can:
- // 1.) flush all visible items
- // 2.) keep the cached data
- int center = (getVisibleStart() + getVisibleEnd()) / 2;
- updateVisibleRange(center, center);
- }
- updateVisibleRange(getVisibleStart(), getVisibleEnd());
- invalidate();
- }
-
- public void onWindowContentChanged(int slot, AlbumSetItem old, AlbumSetItem update) {
- freeSlotContent(slot, old);
- putSlotContent(slot, update);
- invalidate();
- }
-
- public void onContentInvalidated() {
- invalidate();
- }
- }
-
- public void pause() {
- for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
- freeSlotContent(i, mDataWindow.get(i));
- }
- mDataWindow.pause();
- }
-
- public void resume() {
- mDataWindow.resume();
- for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
- putSlotContent(i, mDataWindow.get(i));
- }
- }
-}
diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
index b40d72c..bc6f738 100644
--- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
@@ -19,9 +19,8 @@
import android.graphics.Bitmap;
import android.os.Message;
+import com.android.gallery3d.app.AlbumDataLoader;
import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.LruCache;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.Path;
@@ -29,26 +28,36 @@
import com.android.gallery3d.util.FutureListener;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.JobLimiter;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-public class AlbumSlidingWindow implements AlbumView.ModelListener {
+public class AlbumSlidingWindow implements AlbumDataLoader.DataListener {
@SuppressWarnings("unused")
private static final String TAG = "AlbumSlidingWindow";
- private static final int MSG_LOAD_BITMAP_DONE = 0;
- private static final int MSG_UPDATE_SLOT = 1;
+ private static final int MSG_UPDATE_ENTRY = 0;
private static final int JOB_LIMIT = 2;
- private static final int PLACEHOLDER_COLOR = 0xFF222222;
public static interface Listener {
public void onSizeChanged(int size);
- public void onContentInvalidated();
- public void onWindowContentChanged(
- int slot, DisplayItem old, DisplayItem update);
+ public void onContentChanged();
}
- private final AlbumView.Model mSource;
+ public static class AlbumEntry {
+ public MediaItem item;
+ public Path path;
+ public boolean isPanorama;
+ public int rotation;
+ public int mediaType;
+ public boolean isWaitDisplayed;
+ public Texture content;
+ private BitmapLoader contentLoader;
+ }
+
+ private final AlbumDataLoader mSource;
+ private final AlbumEntry mData[];
+ private final SynchronizedHandler mHandler;
+ private final JobLimiter mThreadPool;
+ private final TextureUploader mTextureUploader;
+
private int mSize;
private int mContentStart = 0;
@@ -58,74 +67,41 @@
private int mActiveEnd = 0;
private Listener mListener;
- private int mFocusIndex = -1;
-
- private final AlbumDisplayItem mData[];
- private final ColorTexture mWaitLoadingTexture;
- private SelectionDrawer mSelectionDrawer;
-
- private SynchronizedHandler mHandler;
- private JobLimiter mThreadPool;
private int mActiveRequestCount = 0;
private boolean mIsActive = false;
- private int mCacheThumbSize; // 0: Don't cache the thumbnails
- private LruCache<Path, Bitmap> mImageCache = new LruCache<Path, Bitmap>(1000);
-
public AlbumSlidingWindow(GalleryActivity activity,
- AlbumView.Model source, int cacheSize,
- int cacheThumbSize) {
- source.setModelListener(this);
+ AlbumDataLoader source, int cacheSize) {
+ source.setDataListener(this);
mSource = source;
- mData = new AlbumDisplayItem[cacheSize];
+ mData = new AlbumEntry[cacheSize];
mSize = source.size();
- mWaitLoadingTexture = new ColorTexture(PLACEHOLDER_COLOR);
- mWaitLoadingTexture.setSize(1, 1);
-
mHandler = new SynchronizedHandler(activity.getGLRoot()) {
@Override
public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_LOAD_BITMAP_DONE: {
- ((AlbumDisplayItem) message.obj).onLoadBitmapDone();
- break;
- }
- case MSG_UPDATE_SLOT: {
- updateSlotContent(message.arg1);
- break;
- }
- }
+ Utils.assertTrue(message.what == MSG_UPDATE_ENTRY);
+ ((ThumbnailLoader) message.obj).updateEntry();
}
};
mThreadPool = new JobLimiter(activity.getThreadPool(), JOB_LIMIT);
- }
-
- public void setSelectionDrawer(SelectionDrawer drawer) {
- mSelectionDrawer = drawer;
+ mTextureUploader = new TextureUploader(activity.getGLRoot());
}
public void setListener(Listener listener) {
mListener = listener;
}
- public void setFocusIndex(int slotIndex) {
- mFocusIndex = slotIndex;
- }
-
- public DisplayItem get(int slotIndex) {
- Utils.assertTrue(isActiveSlot(slotIndex),
- "invalid slot: %s outsides (%s, %s)",
- slotIndex, mActiveStart, mActiveEnd);
+ public AlbumEntry get(int slotIndex) {
+ if (!isActiveSlot(slotIndex)) {
+ Utils.fail("invalid slot: %s outsides (%s, %s)",
+ slotIndex, mActiveStart, mActiveEnd);
+ }
return mData[slotIndex % mData.length];
}
- public int size() {
- return mSize;
- }
-
public boolean isActiveSlot(int slotIndex) {
return slotIndex >= mActiveStart && slotIndex < mActiveEnd;
}
@@ -169,10 +145,10 @@
}
public void setActiveWindow(int start, int end) {
- Utils.assertTrue(start <= end
- && end - start <= mData.length && end <= mSize,
- "%s, %s, %s, %s", start, end, mData.length, mSize);
- DisplayItem data[] = mData;
+ if (!(start <= end && end - start <= mData.length && end <= mSize)) {
+ Utils.fail("%s, %s, %s, %s", start, end, mData.length, mSize);
+ }
+ AlbumEntry data[] = mData;
mActiveStart = start;
mActiveEnd = end;
@@ -181,9 +157,40 @@
0, Math.max(0, mSize - data.length));
int contentEnd = Math.min(contentStart + data.length, mSize);
setContentWindow(contentStart, contentEnd);
+ updateTextureUploadQueue();
if (mIsActive) updateAllImageRequests();
}
+ private void uploadBgTextureInSlot(int index) {
+ if (index < mContentEnd && index >= mContentStart) {
+ AlbumEntry entry = mData[index % mData.length];
+ if (entry.content instanceof BitmapTexture) {
+ mTextureUploader.addBgTexture((BitmapTexture) entry.content);
+ }
+ }
+ }
+
+ private void updateTextureUploadQueue() {
+ if (!mIsActive) return;
+ mTextureUploader.clear();
+
+ // add foreground textures
+ for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
+ AlbumEntry entry = mData[i % mData.length];
+ if (entry.content instanceof BitmapTexture) {
+ mTextureUploader.addFgTexture((BitmapTexture) entry.content);
+ }
+ }
+
+ // add background textures
+ int range = Math.max(
+ (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
+ for (int i = 0; i < range; ++i) {
+ uploadBgTextureInSlot(mActiveEnd + i);
+ uploadBgTextureInSlot(mActiveStart - i - 1);
+ }
+ }
+
// We would like to request non active slots in the following order:
// Order: 8 6 4 2 1 3 5 7
// |---------|---------------|---------|
@@ -193,80 +200,64 @@
int range = Math.max(
(mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
for (int i = 0 ;i < range; ++i) {
- requestSlotImage(mActiveEnd + i, false);
- requestSlotImage(mActiveStart - 1 - i, false);
+ requestSlotImage(mActiveEnd + i);
+ requestSlotImage(mActiveStart - 1 - i);
}
}
- private void requestSlotImage(int slotIndex, boolean isActive) {
- if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
- AlbumDisplayItem item = mData[slotIndex % mData.length];
- item.requestImage();
+ // return whether the request is in progress or not
+ private boolean requestSlotImage(int slotIndex) {
+ if (slotIndex < mContentStart || slotIndex >= mContentEnd) return false;
+ AlbumEntry entry = mData[slotIndex % mData.length];
+ if (entry.content != null || entry.item == null) return false;
+
+ entry.contentLoader.startLoad();
+ return entry.contentLoader.isRequestInProgress();
}
private void cancelNonactiveImages() {
int range = Math.max(
(mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
for (int i = 0 ;i < range; ++i) {
- cancelSlotImage(mActiveEnd + i, false);
- cancelSlotImage(mActiveStart - 1 - i, false);
+ cancelSlotImage(mActiveEnd + i);
+ cancelSlotImage(mActiveStart - 1 - i);
}
}
- private void cancelSlotImage(int slotIndex, boolean isActive) {
+ private void cancelSlotImage(int slotIndex) {
if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
- AlbumDisplayItem item = mData[slotIndex % mData.length];
- item.cancelImageRequest();
+ AlbumEntry item = mData[slotIndex % mData.length];
+ if (item.contentLoader != null) item.contentLoader.cancelLoad();
}
private void freeSlotContent(int slotIndex) {
- AlbumDisplayItem data[] = mData;
+ AlbumEntry data[] = mData;
int index = slotIndex % data.length;
- AlbumDisplayItem original = data[index];
- if (original != null) {
- original.recycle();
- data[index] = null;
+ AlbumEntry entry = data[index];
+ if (entry.contentLoader != null) {
+ entry.contentLoader.recycle();
}
+ data[index] = null;
}
- private void prepareSlotContent(final int slotIndex) {
- mData[slotIndex % mData.length] = new AlbumDisplayItem(
- slotIndex, mSource.get(slotIndex));
- }
-
- private void updateSlotContent(final int slotIndex) {
- MediaItem item = mSource.get(slotIndex);
- AlbumDisplayItem data[] = mData;
- int index = slotIndex % data.length;
- AlbumDisplayItem original = data[index];
- AlbumDisplayItem update = new AlbumDisplayItem(slotIndex, item);
- data[index] = update;
- boolean isActive = isActiveSlot(slotIndex);
- if (mListener != null && isActive) {
- mListener.onWindowContentChanged(slotIndex, original, update);
- }
- if (original != null) {
- if (isActive && original.isRequestInProgress()) {
- --mActiveRequestCount;
- }
- original.recycle();
- }
- if (isActive) {
- if (mActiveRequestCount == 0) cancelNonactiveImages();
- ++mActiveRequestCount;
- update.requestImage();
- } else {
- if (mActiveRequestCount == 0) update.requestImage();
- }
+ private void prepareSlotContent(int slotIndex) {
+ AlbumEntry entry = new AlbumEntry();
+ MediaItem item = mSource.get(slotIndex); // item could be null;
+ entry.item = item;
+ entry.isPanorama = GalleryUtils.isPanorama(entry.item);
+ entry.mediaType = (item == null)
+ ? MediaItem.MEDIA_TYPE_UNKNOWN
+ : entry.item.getMediaType();
+ entry.path = (item == null) ? null : item.getPath();
+ entry.rotation = (item == null) ? 0 : item.getRotation();
+ entry.contentLoader = new ThumbnailLoader(slotIndex, entry.item);
+ mData[slotIndex % mData.length] = entry;
}
private void updateAllImageRequests() {
mActiveRequestCount = 0;
- AlbumDisplayItem data[] = mData;
for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
- AlbumDisplayItem item = data[i % data.length];
- item.requestImage();
- if (item.isRequestInProgress()) ++mActiveRequestCount;
+ if (requestSlotImage(i)) ++mActiveRequestCount;
}
if (mActiveRequestCount == 0) {
requestNonactiveImages();
@@ -275,155 +266,68 @@
}
}
- private class AlbumDisplayItem extends AbstractDisplayItem
- implements FutureListener<Bitmap>, Job<Bitmap> {
- private Future<Bitmap> mFuture;
+ private class ThumbnailLoader extends BitmapLoader {
private final int mSlotIndex;
- private final int mMediaType;
- private Texture mContent;
- private boolean mIsPanorama;
- private boolean mWaitLoadingDisplayed;
+ private final MediaItem mItem;
- public AlbumDisplayItem(int slotIndex, MediaItem item) {
- super(item);
- mMediaType = (item == null)
- ? MediaItem.MEDIA_TYPE_UNKNOWN
- : item.getMediaType();
+ public ThumbnailLoader(int slotIndex, MediaItem item) {
mSlotIndex = slotIndex;
- mIsPanorama = GalleryUtils.isPanorama(item);
- updateContent(mWaitLoadingTexture);
+ mItem = item;
}
@Override
- protected void onBitmapAvailable(Bitmap bitmap) {
- boolean isActiveSlot = isActiveSlot(mSlotIndex);
- if (isActiveSlot) {
+ protected void recycleBitmap(Bitmap bitmap) {
+ MediaItem.getMicroThumbPool().recycle(bitmap);
+ }
+
+ @Override
+ protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
+ return mThreadPool.submit(
+ mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
+ }
+
+ @Override
+ protected void onLoadComplete(Bitmap bitmap) {
+ mHandler.obtainMessage(MSG_UPDATE_ENTRY, this).sendToTarget();
+ }
+
+ public void updateEntry() {
+ Bitmap bitmap = getBitmap();
+ if (bitmap == null) return; // error or recycled
+
+ AlbumEntry entry = mData[mSlotIndex % mData.length];
+ entry.content = new BitmapTexture(bitmap);
+
+ if (isActiveSlot(mSlotIndex)) {
+ mTextureUploader.addFgTexture((BitmapTexture) entry.content);
--mActiveRequestCount;
if (mActiveRequestCount == 0) requestNonactiveImages();
- }
- if (bitmap != null) {
- BitmapTexture texture = new BitmapTexture(bitmap, true);
- texture.setThrottled(true);
- if (mWaitLoadingDisplayed) {
- updateContent(new FadeInTexture(PLACEHOLDER_COLOR, texture));
- } else {
- updateContent(texture);
- }
- if (mListener != null && isActiveSlot) {
- mListener.onContentInvalidated();
- }
- }
- }
-
- private void updateContent(Texture content) {
- mContent = content;
- }
-
- @Override
- public int render(GLCanvas canvas, int pass) {
- // Fit the content into the box
- int width = mContent.getWidth();
- int height = mContent.getHeight();
-
- float scalex = mBoxWidth / (float) width;
- float scaley = mBoxHeight / (float) height;
- float scale = Math.min(scalex, scaley);
-
- width = (int) Math.floor(width * scale);
- height = (int) Math.floor(height * scale);
-
- // Now draw it
- if (pass == 0) {
- Path path = null;
- if (mMediaItem != null) path = mMediaItem.getPath();
- mSelectionDrawer.draw(canvas, mContent, width, height,
- getRotation(), path, mMediaType, mIsPanorama);
- if (mContent == mWaitLoadingTexture) {
- mWaitLoadingDisplayed = true;
- }
- int result = 0;
- if (mFocusIndex == mSlotIndex) {
- result |= RENDER_MORE_PASS;
- }
- if ((mContent instanceof FadeInTexture) &&
- ((FadeInTexture) mContent).isAnimating()) {
- result |= RENDER_MORE_FRAME;
- }
- return result;
- } else if (pass == 1) {
- mSelectionDrawer.drawFocus(canvas, width, height);
- }
- return 0;
- }
-
- @Override
- public void startLoadBitmap() {
- if (mCacheThumbSize > 0) {
- Path path = mMediaItem.getPath();
- if (mImageCache.containsKey(path)) {
- Bitmap bitmap = mImageCache.get(path);
- updateImage(bitmap, false);
- return;
- }
- mFuture = mThreadPool.submit(this, this);
+ if (mListener != null) mListener.onContentChanged();
} else {
- mFuture = mThreadPool.submit(mMediaItem.requestImage(
- MediaItem.TYPE_MICROTHUMBNAIL), this);
+ mTextureUploader.addBgTexture((BitmapTexture) entry.content);
}
}
-
- // This gets the bitmap and scale it down.
- public Bitmap run(JobContext jc) {
- Job<Bitmap> job = mMediaItem.requestImage(
- MediaItem.TYPE_MICROTHUMBNAIL);
- Bitmap bitmap = job.run(jc);
- if (bitmap != null) {
- bitmap = BitmapUtils.resizeDownBySideLength(
- bitmap, mCacheThumbSize, true);
- }
- return bitmap;
- }
-
- @Override
- public void cancelLoadBitmap() {
- if (mFuture != null) {
- mFuture.cancel();
- }
- }
-
- @Override
- public void onFutureDone(Future<Bitmap> bitmap) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_LOAD_BITMAP_DONE, this));
- }
-
- private void onLoadBitmapDone() {
- Future<Bitmap> future = mFuture;
- mFuture = null;
- Bitmap bitmap = future.get();
- boolean isCancelled = future.isCancelled();
- if (mCacheThumbSize > 0 && (bitmap != null || !isCancelled)) {
- Path path = mMediaItem.getPath();
- mImageCache.put(path, bitmap);
- }
- updateImage(bitmap, isCancelled);
- }
-
- @Override
- public String toString() {
- return String.format("AlbumDisplayItem[%s]", mSlotIndex);
- }
}
+ @Override
public void onSizeChanged(int size) {
if (mSize != size) {
mSize = size;
if (mListener != null) mListener.onSizeChanged(mSize);
+ if (mContentEnd > mSize) mContentEnd = mSize;
+ if (mActiveEnd > mSize) mActiveEnd = mSize;
}
}
- public void onWindowContentChanged(int index) {
+ @Override
+ public void onContentChanged(int index) {
if (index >= mContentStart && index < mContentEnd && mIsActive) {
- updateSlotContent(index);
+ freeSlotContent(index);
+ prepareSlotContent(index);
+ updateAllImageRequests();
+ if (mListener != null && isActiveSlot(index)) {
+ mListener.onContentChanged();
+ }
}
}
@@ -440,6 +344,5 @@
for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
freeSlotContent(i);
}
- mImageCache.clear();
}
}
diff --git a/src/com/android/gallery3d/ui/AlbumSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSlotRenderer.java
new file mode 100644
index 0000000..922e2c3
--- /dev/null
+++ b/src/com/android/gallery3d/ui/AlbumSlotRenderer.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.content.Context;
+
+import com.android.gallery3d.app.AlbumDataLoader;
+import com.android.gallery3d.app.GalleryActivity;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.Path;
+
+public class AlbumSlotRenderer extends AbstractSlotRenderer {
+ private static final int PLACEHOLDER_COLOR = 0xFF222222;
+
+ @SuppressWarnings("unused")
+ private static final String TAG = "AlbumView";
+ private static final int CACHE_SIZE = 96;
+
+ private AlbumSlidingWindow mDataWindow;
+ private final GalleryActivity mActivity;
+ private final ColorTexture mWaitLoadingTexture;
+ private final SlotView mSlotView;
+ private final SelectionManager mSelectionManager;
+
+ private int mPressedIndex = -1;
+ private boolean mAnimatePressedUp;
+ private Path mHighlightItemPath = null;
+ private boolean mInSelectionMode;
+
+ public AlbumSlotRenderer(GalleryActivity activity, SlotView slotView,
+ SelectionManager selectionManager) {
+ super((Context) activity);
+ mActivity = activity;
+ mSlotView = slotView;
+ mSelectionManager = selectionManager;
+
+ mWaitLoadingTexture = new ColorTexture(PLACEHOLDER_COLOR);
+ mWaitLoadingTexture.setSize(1, 1);
+ }
+
+ public void setPressedIndex(int index) {
+ if (mPressedIndex == index) return;
+ mPressedIndex = index;
+ mSlotView.invalidate();
+ }
+
+ public void setPressedUp() {
+ if (mPressedIndex == -1) return;
+ mAnimatePressedUp = true;
+ mSlotView.invalidate();
+ }
+
+ public void setHighlightItemPath(Path path) {
+ if (mHighlightItemPath == path) return;
+ mHighlightItemPath = path;
+ mSlotView.invalidate();
+ }
+
+ public void setModel(AlbumDataLoader model) {
+ if (mDataWindow != null) {
+ mDataWindow.setListener(null);
+ mSlotView.setSlotCount(0);
+ mDataWindow = null;
+ }
+ if (model != null) {
+ mDataWindow = new AlbumSlidingWindow(mActivity, model, CACHE_SIZE);
+ mDataWindow.setListener(new MyDataModelListener());
+ mSlotView.setSlotCount(model.size());
+ }
+ }
+
+ private static Texture checkTexture(GLCanvas canvas, Texture texture) {
+ return ((texture == null) || ((texture instanceof UploadedTexture)
+ && !((UploadedTexture) texture).isContentValid(canvas)))
+ ? null
+ : texture;
+ }
+
+ @Override
+ public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) {
+ AlbumSlidingWindow.AlbumEntry entry = mDataWindow.get(index);
+
+ int renderRequestFlags = 0;
+
+ Texture content = checkTexture(canvas, entry.content);
+ if (content == null) {
+ content = mWaitLoadingTexture;
+ entry.isWaitDisplayed = true;
+ } else if (entry.isWaitDisplayed) {
+ entry.isWaitDisplayed = false;
+ entry.content = new FadeInTexture(
+ PLACEHOLDER_COLOR, (BitmapTexture) entry.content);
+ content = entry.content;
+ }
+ drawContent(canvas, content, width, height, entry.rotation);
+ if ((content instanceof FadeInTexture) &&
+ ((FadeInTexture) content).isAnimating()) {
+ renderRequestFlags |= SlotView.RENDER_MORE_FRAME;
+ }
+
+ if (entry.mediaType == MediaObject.MEDIA_TYPE_VIDEO) {
+ drawVideoOverlay(canvas, width, height);
+ }
+
+ if (entry.isPanorama) {
+ drawPanoramaBorder(canvas, width, height);
+ }
+
+ renderRequestFlags |= renderOverlay(canvas, index, entry, width, height);
+
+ return renderRequestFlags;
+ }
+
+ private int renderOverlay(GLCanvas canvas, int index,
+ AlbumSlidingWindow.AlbumEntry entry, int width, int height) {
+ int renderRequestFlags = 0;
+ if (mPressedIndex == index) {
+ if (mAnimatePressedUp) {
+ drawPressedUpFrame(canvas, width, height);
+ renderRequestFlags |= SlotView.RENDER_MORE_FRAME;
+ if (isPressedUpFrameFinished()) {
+ mAnimatePressedUp = false;
+ mPressedIndex = -1;
+ }
+ } else {
+ drawPressedFrame(canvas, width, height);
+ }
+ } else if ((entry.path != null) && (mHighlightItemPath == entry.path)) {
+ drawSelectedFrame(canvas, width, height);
+ } else if (mInSelectionMode && mSelectionManager.isItemSelected(entry.path)) {
+ drawSelectedFrame(canvas, width, height);
+ }
+ return renderRequestFlags;
+ }
+
+ private class MyDataModelListener implements AlbumSlidingWindow.Listener {
+ @Override
+ public void onContentChanged() {
+ mSlotView.invalidate();
+ }
+
+ @Override
+ public void onSizeChanged(int size) {
+ mSlotView.setSlotCount(size);
+ }
+ }
+
+ public void resume() {
+ mDataWindow.resume();
+ }
+
+ public void pause() {
+ mDataWindow.pause();
+ }
+
+ @Override
+ public void prepareDrawing() {
+ mInSelectionMode = mSelectionManager.inSelectionMode();
+ }
+
+ @Override
+ public void onVisibleRangeChanged(int visibleStart, int visibleEnd) {
+ if (mDataWindow != null) {
+ mDataWindow.setActiveWindow(visibleStart, visibleEnd);
+ }
+ }
+
+ @Override
+ public void onSlotSizeChanged(int width, int height) {
+ // Do nothing
+ }
+}
diff --git a/src/com/android/gallery3d/ui/AlbumView.java b/src/com/android/gallery3d/ui/AlbumView.java
deleted file mode 100644
index 6344851..0000000
--- a/src/com/android/gallery3d/ui/AlbumView.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import android.graphics.Rect;
-
-import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.ui.PositionRepository.Position;
-
-public class AlbumView extends SlotView {
- @SuppressWarnings("unused")
- private static final String TAG = "AlbumView";
- private static final int CACHE_SIZE = 64;
-
- private int mVisibleStart = 0;
- private int mVisibleEnd = 0;
-
- private AlbumSlidingWindow mDataWindow;
- private final GalleryActivity mActivity;
- private SelectionDrawer mSelectionDrawer;
- private int mCacheThumbSize;
-
- private boolean mIsActive = false;
-
- public static interface Model {
- public int size();
- public MediaItem get(int index);
- public void setActiveWindow(int start, int end);
- public void setModelListener(ModelListener listener);
- }
-
- public static interface ModelListener {
- public void onWindowContentChanged(int index);
- public void onSizeChanged(int size);
- }
-
- public AlbumView(GalleryActivity activity, SlotView.Spec spec,
- int cacheThumbSize) {
- super(activity.getAndroidContext());
- mCacheThumbSize = cacheThumbSize;
- setSlotSpec(spec);
- mActivity = activity;
- }
-
- public void setSelectionDrawer(SelectionDrawer drawer) {
- mSelectionDrawer = drawer;
- if (mDataWindow != null) mDataWindow.setSelectionDrawer(drawer);
- }
-
- public void setModel(Model model) {
- if (mDataWindow != null) {
- mDataWindow.setListener(null);
- setSlotCount(0);
- mDataWindow = null;
- }
- if (model != null) {
- mDataWindow = new AlbumSlidingWindow(
- mActivity, model, CACHE_SIZE,
- mCacheThumbSize);
- mDataWindow.setSelectionDrawer(mSelectionDrawer);
- mDataWindow.setListener(new MyDataModelListener());
- setSlotCount(model.size());
- updateVisibleRange(getVisibleStart(), getVisibleEnd());
- }
- }
-
- public void setFocusIndex(int slotIndex) {
- if (mDataWindow != null) {
- mDataWindow.setFocusIndex(slotIndex);
- }
- }
-
- private void putSlotContent(int slotIndex, DisplayItem item) {
- Rect rect = getSlotRect(slotIndex);
- Position position = new Position(
- (rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2, 0);
- putDisplayItem(position, position, item);
- }
-
- private void updateVisibleRange(int start, int end) {
- if (start == mVisibleStart && end == mVisibleEnd) {
- // we need to set the mDataWindow active range in any case.
- mDataWindow.setActiveWindow(start, end);
- return;
- }
-
- if (!mIsActive) {
- mVisibleStart = start;
- mVisibleEnd = end;
- mDataWindow.setActiveWindow(start, end);
- return;
- }
-
- if (start >= mVisibleEnd || mVisibleStart >= end) {
- for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
- DisplayItem item = mDataWindow.get(i);
- if (item != null) removeDisplayItem(item);
- }
- mDataWindow.setActiveWindow(start, end);
- for (int i = start; i < end; ++i) {
- putSlotContent(i, mDataWindow.get(i));
- }
- } else {
- for (int i = mVisibleStart; i < start; ++i) {
- DisplayItem item = mDataWindow.get(i);
- if (item != null) removeDisplayItem(item);
- }
- for (int i = end, n = mVisibleEnd; i < n; ++i) {
- DisplayItem item = mDataWindow.get(i);
- if (item != null) removeDisplayItem(item);
- }
- mDataWindow.setActiveWindow(start, end);
- for (int i = start, n = mVisibleStart; i < n; ++i) {
- putSlotContent(i, mDataWindow.get(i));
- }
- for (int i = mVisibleEnd; i < end; ++i) {
- putSlotContent(i, mDataWindow.get(i));
- }
- }
-
- mVisibleStart = start;
- mVisibleEnd = end;
- }
-
- @Override
- protected void onLayoutChanged(int width, int height) {
- // Reput all the items
- updateVisibleRange(0, 0);
- updateVisibleRange(getVisibleStart(), getVisibleEnd());
- }
-
- @Override
- protected void onScrollPositionChanged(int position) {
- super.onScrollPositionChanged(position);
- updateVisibleRange(getVisibleStart(), getVisibleEnd());
- }
-
- @Override
- protected void render(GLCanvas canvas) {
- mSelectionDrawer.prepareDrawing();
- super.render(canvas);
- }
-
- private class MyDataModelListener implements AlbumSlidingWindow.Listener {
-
- public void onContentInvalidated() {
- invalidate();
- }
-
- public void onSizeChanged(int size) {
- if (setSlotCount(size)) {
- // If the layout parameters are changed, we need reput all items.
- // We keep the visible range at the same center but with size 0.
- // So that we can:
- // 1.) flush all visible items
- // 2.) keep the cached data
- int center = (getVisibleStart() + getVisibleEnd()) / 2;
- updateVisibleRange(center, center);
- }
- updateVisibleRange(getVisibleStart(), getVisibleEnd());
- invalidate();
- }
-
- public void onWindowContentChanged(
- int slotIndex, DisplayItem old, DisplayItem update) {
- removeDisplayItem(old);
- putSlotContent(slotIndex, update);
- }
- }
-
- public void resume() {
- mIsActive = true;
- mDataWindow.resume();
- for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
- putSlotContent(i, mDataWindow.get(i));
- }
- }
-
- public void pause() {
- mIsActive = false;
- for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
- removeDisplayItem(mDataWindow.get(i));
- }
- mDataWindow.pause();
- }
-}
diff --git a/src/com/android/gallery3d/ui/AnimationTime.java b/src/com/android/gallery3d/ui/AnimationTime.java
new file mode 100644
index 0000000..0636774
--- /dev/null
+++ b/src/com/android/gallery3d/ui/AnimationTime.java
@@ -0,0 +1,45 @@
+
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.os.SystemClock;
+
+//
+// The animation time should ideally be the vsync time the frame will be
+// displayed, but that is an unknown time in the future. So we use the system
+// time just after eglSwapBuffers (when GLSurfaceView.onDrawFrame is called)
+// as a approximation.
+//
+public class AnimationTime {
+ private static volatile long sTime;
+
+ // Sets current time as the animation time.
+ public static void update() {
+ sTime = SystemClock.uptimeMillis();
+ }
+
+ // Returns the animation time.
+ public static long get() {
+ return sTime;
+ }
+
+ public static long startTime() {
+ sTime = SystemClock.uptimeMillis();
+ return sTime;
+ }
+}
diff --git a/src/com/android/gallery3d/ui/BasicTexture.java b/src/com/android/gallery3d/ui/BasicTexture.java
index 8946036..6a9a17d 100644
--- a/src/com/android/gallery3d/ui/BasicTexture.java
+++ b/src/com/android/gallery3d/ui/BasicTexture.java
@@ -18,7 +18,6 @@
import com.android.gallery3d.common.Utils;
-import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
// BasicTexture is a Texture corresponds to a real GL texture.
@@ -45,7 +44,7 @@
private boolean mHasBorder;
- protected WeakReference<GLCanvas> mCanvasRef = null;
+ protected GLCanvas mCanvasRef = null;
private static WeakHashMap<BasicTexture, Object> sAllTextures
= new WeakHashMap<BasicTexture, Object>();
private static ThreadLocal sInFinalizer = new ThreadLocal();
@@ -64,9 +63,7 @@
}
protected void setAssociatedCanvas(GLCanvas canvas) {
- mCanvasRef = canvas == null
- ? null
- : new WeakReference<GLCanvas>(canvas);
+ mCanvasRef = canvas;
}
/**
@@ -133,8 +130,11 @@
// It should make sure the data is uploaded to GL memory.
abstract protected boolean onBind(GLCanvas canvas);
+ // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
+ abstract protected int getTarget();
+
public boolean isLoaded(GLCanvas canvas) {
- return mState == STATE_LOADED && mCanvasRef.get() == canvas;
+ return mState == STATE_LOADED;
}
// recycle() is called when the texture will never be used again,
@@ -153,7 +153,7 @@
}
private void freeResource() {
- GLCanvas canvas = mCanvasRef == null ? null : mCanvasRef.get();
+ GLCanvas canvas = mCanvasRef;
if (canvas != null && isLoaded(canvas)) {
canvas.unloadTexture(this);
}
@@ -182,4 +182,13 @@
}
}
}
+
+ public static void invalidateAllTextures() {
+ synchronized (sAllTextures) {
+ for (BasicTexture t : sAllTextures.keySet()) {
+ t.mState = STATE_UNLOADED;
+ t.setAssociatedCanvas(null);
+ }
+ }
+ }
}
diff --git a/src/com/android/gallery3d/ui/BitmapLoader.java b/src/com/android/gallery3d/ui/BitmapLoader.java
new file mode 100644
index 0000000..4f07cc0
--- /dev/null
+++ b/src/com/android/gallery3d/ui/BitmapLoader.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+
+// We use this class to
+// 1.) load bitmaps in background.
+// 2.) as a place holder for the loaded bitmap
+public abstract class BitmapLoader implements FutureListener<Bitmap> {
+ @SuppressWarnings("unused")
+ private static final String TAG = "BitmapLoader";
+
+ /* Transition Map:
+ * INIT -> REQUESTED, RECYCLED
+ * REQUESTED -> INIT (cancel), LOADED, ERROR, RECYCLED
+ * LOADED, ERROR -> RECYCLED
+ */
+ private static final int STATE_INIT = 0;
+ private static final int STATE_REQUESTED = 1;
+ private static final int STATE_LOADED = 2;
+ private static final int STATE_ERROR = 3;
+ private static final int STATE_RECYCLED = 4;
+
+ private int mState = STATE_INIT;
+ // mTask is not null only when a task is on the way
+ private Future<Bitmap> mTask;
+ private Bitmap mBitmap;
+
+ @Override
+ public void onFutureDone(Future<Bitmap> future) {
+ synchronized (this) {
+ mTask = null;
+ mBitmap = future.get();
+ if (mState == STATE_RECYCLED) {
+ if (mBitmap != null) {
+ recycleBitmap(mBitmap);
+ mBitmap = null;
+ }
+ return; // don't call callback
+ }
+ if (future.isCancelled() && mBitmap == null) {
+ if (mState == STATE_REQUESTED) mTask = submitBitmapTask(this);
+ return; // don't call callback
+ } else {
+ mState = mBitmap == null ? STATE_ERROR : STATE_LOADED;
+ }
+ }
+ onLoadComplete(mBitmap);
+ }
+
+ public synchronized void startLoad() {
+ if (mState == STATE_INIT) {
+ mState = STATE_REQUESTED;
+ if (mTask == null) mTask = submitBitmapTask(this);
+ }
+ }
+
+ public synchronized void cancelLoad() {
+ if (mState == STATE_REQUESTED) {
+ mState = STATE_INIT;
+ if (mTask != null) mTask.cancel();
+ }
+ }
+
+ // Recycle the loader and the bitmap
+ public synchronized void recycle() {
+ mState = STATE_RECYCLED;
+ if (mBitmap != null) {
+ recycleBitmap(mBitmap);
+ mBitmap = null;
+ }
+ if (mTask != null) mTask.cancel();
+ }
+
+ public synchronized boolean isRequestInProgress() {
+ return mState == STATE_REQUESTED;
+ }
+
+ public synchronized boolean isRecycled() {
+ return mState == STATE_RECYCLED;
+ }
+
+ public synchronized Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ abstract protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l);
+ abstract protected void recycleBitmap(Bitmap bitmap);
+ abstract protected void onLoadComplete(Bitmap bitmap);
+}
diff --git a/src/com/android/gallery3d/ui/BitmapScreenNail.java b/src/com/android/gallery3d/ui/BitmapScreenNail.java
new file mode 100644
index 0000000..7f65405
--- /dev/null
+++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.util.Log;
+
+import com.android.gallery3d.data.MediaItem;
+
+// This is a ScreenNail wraps a Bitmap. It also includes the rotation
+// information. The getWidth() and getHeight() methods return the width/height
+// before rotation.
+public class BitmapScreenNail implements ScreenNail {
+ private static final String TAG = "BitmapScreenNail";
+ private final int mWidth;
+ private final int mHeight;
+ private Bitmap mBitmap;
+ private BitmapTexture mTexture;
+
+ public BitmapScreenNail(Bitmap bitmap) {
+ mWidth = bitmap.getWidth();
+ mHeight = bitmap.getHeight();
+ mBitmap = bitmap;
+ // We create mTexture lazily, so we don't incur the cost if we don't
+ // actually need it.
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public void noDraw() {
+ }
+
+ @Override
+ public void recycle() {
+ if (mTexture != null) {
+ mTexture.recycle();
+ mTexture = null;
+ }
+ if (mBitmap != null) {
+ MediaItem.getThumbPool().recycle(mBitmap);
+ mBitmap = null;
+ }
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int width, int height) {
+ if (mTexture == null) {
+ mTexture = new BitmapTexture(mBitmap);
+ }
+ mTexture.draw(canvas, x, y, width, height);
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, RectF source, RectF dest) {
+ if (mTexture == null) {
+ mTexture = new BitmapTexture(mBitmap);
+ }
+ canvas.drawTexture(mTexture, source, dest);
+ }
+}
diff --git a/src/com/android/gallery3d/ui/BitmapTexture.java b/src/com/android/gallery3d/ui/BitmapTexture.java
index f5cb2bd..6075449 100644
--- a/src/com/android/gallery3d/ui/BitmapTexture.java
+++ b/src/com/android/gallery3d/ui/BitmapTexture.java
@@ -16,10 +16,10 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
-
import android.graphics.Bitmap;
+import com.android.gallery3d.common.Utils;
+
// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
//
// The texture does not own the Bitmap. The user should make sure the Bitmap
diff --git a/src/com/android/gallery3d/ui/BitmapTileProvider.java b/src/com/android/gallery3d/ui/BitmapTileProvider.java
index 3d4d4dc..be05b33 100644
--- a/src/com/android/gallery3d/ui/BitmapTileProvider.java
+++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java
@@ -16,16 +16,16 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.BitmapUtils;
-
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+
+import com.android.gallery3d.common.BitmapUtils;
import java.util.ArrayList;
public class BitmapTileProvider implements TileImageView.Model {
- private final Bitmap mBackup;
+ private final ScreenNail mScreenNail;
private final Bitmap[] mMipmaps;
private final Config mConfig;
private final int mImageWidth;
@@ -44,13 +44,13 @@
list.add(bitmap);
}
- mBackup = list.remove(list.size() - 1);
+ mScreenNail = new BitmapScreenNail(list.remove(list.size() - 1));
mMipmaps = list.toArray(new Bitmap[list.size()]);
mConfig = Config.ARGB_8888;
}
- public Bitmap getBackupImage() {
- return mBackup;
+ public ScreenNail getScreenNail() {
+ return mScreenNail;
}
public int getImageHeight() {
@@ -95,11 +95,9 @@
for (Bitmap bitmap : mMipmaps) {
BitmapUtils.recycleSilently(bitmap);
}
- BitmapUtils.recycleSilently(mBackup);
- }
-
- public int getRotation() {
- return 0;
+ if (mScreenNail != null) {
+ mScreenNail.recycle();
+ }
}
public boolean isFailedToLoad() {
diff --git a/src/com/android/gallery3d/ui/BoxBlurFilter.java b/src/com/android/gallery3d/ui/BoxBlurFilter.java
deleted file mode 100644
index 0497a61..0000000
--- a/src/com/android/gallery3d/ui/BoxBlurFilter.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import android.graphics.Bitmap;
-
-
-public class BoxBlurFilter {
- private static final int RED_MASK = 0xff0000;
- private static final int RED_MASK_SHIFT = 16;
- private static final int GREEN_MASK = 0x00ff00;
- private static final int GREEN_MASK_SHIFT = 8;
- private static final int BLUE_MASK = 0x0000ff;
- private static final int RADIUS = 4;
- private static final int KERNEL_SIZE = RADIUS * 2 + 1;
- private static final int NUM_COLORS = 256;
- private static final int[] KERNEL_NORM = new int[KERNEL_SIZE * NUM_COLORS];
-
- public static final int MODE_REPEAT = 1;
- public static final int MODE_CLAMP = 2;
-
- static {
- int index = 0;
- // Build a lookup table from summed to normalized kernel values.
- // The formula: KERNAL_NORM[value] = value / KERNEL_SIZE
- for (int i = 0; i < NUM_COLORS; ++i) {
- for (int j = 0; j < KERNEL_SIZE; ++j) {
- KERNEL_NORM[index++] = i;
- }
- }
- }
-
- private BoxBlurFilter() {
- }
-
- private static int sample(int x, int width, int mode) {
- if (x >= 0 && x < width) return x;
- return mode == MODE_REPEAT
- ? x < 0 ? x + width : x - width
- : x < 0 ? 0 : width - 1;
- }
-
- public static void apply(
- Bitmap bitmap, int horizontalMode, int verticalMode) {
-
- int width = bitmap.getWidth();
- int height = bitmap.getHeight();
- int data[] = new int[width * height];
- bitmap.getPixels(data, 0, width, 0, 0, width, height);
- int temp[] = new int[width * height];
- applyOneDimension(data, temp, width, height, horizontalMode);
- applyOneDimension(temp, data, height, width, verticalMode);
- bitmap.setPixels(data, 0, width, 0, 0, width, height);
- }
-
- private static void applyOneDimension(
- int[] in, int[] out, int width, int height, int mode) {
- for (int y = 0, read = 0; y < height; ++y, read += width) {
- // Evaluate the kernel for the first pixel in the row.
- int red = 0;
- int green = 0;
- int blue = 0;
- for (int i = -RADIUS; i <= RADIUS; ++i) {
- int argb = in[read + sample(i, width, mode)];
- red += (argb & RED_MASK) >> RED_MASK_SHIFT;
- green += (argb & GREEN_MASK) >> GREEN_MASK_SHIFT;
- blue += argb & BLUE_MASK;
- }
- for (int x = 0, write = y; x < width; ++x, write += height) {
- // Output the current pixel.
- out[write] = 0xFF000000
- | (KERNEL_NORM[red] << RED_MASK_SHIFT)
- | (KERNEL_NORM[green] << GREEN_MASK_SHIFT)
- | KERNEL_NORM[blue];
-
- // Slide to the next pixel, adding the new rightmost pixel and
- // subtracting the former leftmost.
- int prev = in[read + sample(x - RADIUS, width, mode)];
- int next = in[read + sample(x + RADIUS + 1, width, mode)];
- red += ((next & RED_MASK) - (prev & RED_MASK)) >> RED_MASK_SHIFT;
- green += ((next & GREEN_MASK) - (prev & GREEN_MASK)) >> GREEN_MASK_SHIFT;
- blue += (next & BLUE_MASK) - (prev & BLUE_MASK);
- }
- }
- }
-}
diff --git a/src/com/android/gallery3d/ui/CacheStorageUsageInfo.java b/src/com/android/gallery3d/ui/CacheStorageUsageInfo.java
index 3c6e157..c65d77e 100644
--- a/src/com/android/gallery3d/ui/CacheStorageUsageInfo.java
+++ b/src/com/android/gallery3d/ui/CacheStorageUsageInfo.java
@@ -16,12 +16,12 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
import android.content.Context;
import android.os.StatFs;
+import com.android.gallery3d.app.GalleryActivity;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
import java.io.File;
public class CacheStorageUsageInfo {
diff --git a/src/com/android/gallery3d/ui/CanvasTexture.java b/src/com/android/gallery3d/ui/CanvasTexture.java
index 679a4bc..a2e9e48 100644
--- a/src/com/android/gallery3d/ui/CanvasTexture.java
+++ b/src/com/android/gallery3d/ui/CanvasTexture.java
@@ -17,8 +17,8 @@
package com.android.gallery3d.ui;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
// CanvasTexture is a texture whose content is the drawing on a Canvas.
// The subclasses should override onDraw() to draw on the bitmap.
diff --git a/src/com/android/gallery3d/ui/CaptureAnimation.java b/src/com/android/gallery3d/ui/CaptureAnimation.java
new file mode 100644
index 0000000..87c054a
--- /dev/null
+++ b/src/com/android/gallery3d/ui/CaptureAnimation.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+public class CaptureAnimation {
+ // The amount of change for zooming out.
+ private static final float ZOOM_DELTA = 0.2f;
+ // Pre-calculated value for convenience.
+ private static final float ZOOM_IN_BEGIN = 1f - ZOOM_DELTA;
+
+ private static final Interpolator sZoomOutInterpolator =
+ new DecelerateInterpolator();
+ private static final Interpolator sZoomInInterpolator =
+ new AccelerateInterpolator();
+ private static final Interpolator sSlideInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ // Calculate the slide factor based on the give time fraction.
+ public static float calculateSlide(float fraction) {
+ return sSlideInterpolator.getInterpolation(fraction);
+ }
+
+ // Calculate the scale factor based on the given time fraction.
+ public static float calculateScale(float fraction) {
+ float value;
+ if (fraction <= 0.5f) {
+ // Zoom in for the beginning.
+ value = 1f - ZOOM_DELTA *
+ sZoomOutInterpolator.getInterpolation(fraction * 2);
+ } else {
+ // Zoom out for the ending.
+ value = ZOOM_IN_BEGIN + ZOOM_DELTA *
+ sZoomInInterpolator.getInterpolation((fraction - 0.5f) * 2f);
+ }
+ return value;
+ }
+}
diff --git a/src/com/android/gallery3d/ui/Config.java b/src/com/android/gallery3d/ui/Config.java
deleted file mode 100644
index 5c5b621..0000000
--- a/src/com/android/gallery3d/ui/Config.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-interface DetailsWindowConfig {
- public static final int FONT_SIZE = 18;
- public static final int PREFERRED_WIDTH = 400;
- public static final int LEFT_RIGHT_EXTRA_PADDING = 9;
- public static final int TOP_BOTTOM_EXTRA_PADDING = 9;
- public static final int LINE_SPACING = 5;
- public static final int FIRST_LINE_SPACING = 18;
-}
-
-interface TextButtonConfig {
- public static final int HORIZONTAL_PADDINGS = 16;
- public static final int VERTICAL_PADDINGS = 5;
-}
diff --git a/src/com/android/gallery3d/ui/CropView.java b/src/com/android/gallery3d/ui/CropView.java
index 71fdaab..78d5d37 100644
--- a/src/com/android/gallery3d/ui/CropView.java
+++ b/src/com/android/gallery3d/ui/CropView.java
@@ -16,11 +16,6 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.R;
-import com.android.gallery3d.anim.Animation;
-import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.common.Utils;
-
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
@@ -31,11 +26,18 @@
import android.media.FaceDetector;
import android.os.Handler;
import android.os.Message;
+import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
import android.widget.Toast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.anim.Animation;
+import com.android.gallery3d.app.GalleryActivity;
+import com.android.gallery3d.common.Utils;
+
import java.util.ArrayList;
+
import javax.microedition.khronos.opengles.GL11;
/**
@@ -166,7 +168,7 @@
@Override
public void render(GLCanvas canvas) {
AnimationController a = mAnimation;
- if (a.calculate(canvas.currentAnimationTimeMillis())) invalidate();
+ if (a.calculate(AnimationTime.get())) invalidate();
setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale());
super.render(canvas);
}
@@ -756,8 +758,7 @@
int rotation = mImageRotation;
int width = bitmap.getWidth();
int height = bitmap.getHeight();
- float scale = (float) Math.sqrt(
- (double) FACE_PIXEL_COUNT / (width * height));
+ float scale = FloatMath.sqrt((float) FACE_PIXEL_COUNT / (width * height));
// faceBitmap is a correctly rotated bitmap, as viewed by a user.
Bitmap faceBitmap;
diff --git a/src/com/android/gallery3d/ui/CustomMenu.java b/src/com/android/gallery3d/ui/CustomMenu.java
index de2367e..dd8e6ab 100644
--- a/src/com/android/gallery3d/ui/CustomMenu.java
+++ b/src/com/android/gallery3d/ui/CustomMenu.java
@@ -16,9 +16,6 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.R;
-
-import android.app.ActionBar;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
@@ -28,6 +25,8 @@
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
+import com.android.gallery3d.R;
+
import java.util.ArrayList;
public class CustomMenu implements OnMenuItemClickListener {
@@ -42,8 +41,6 @@
public DropDownMenu(Context context, Button button, int menuId,
OnMenuItemClickListener listener) {
mButton = button;
- mButton.setBackgroundDrawable(context.getResources().getDrawable(
- R.drawable.dropdown_normal_holo_dark));
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
@@ -85,38 +82,6 @@
mListener = listener;
}
- public MenuItem findMenuItem(int id) {
- MenuItem item = null;
- for (DropDownMenu menu : mMenus) {
- item = menu.findItem(id);
- if (item != null) return item;
- }
- return item;
- }
-
- public void setMenuItemAppliedEnabled(int id, boolean applied, boolean enabled,
- boolean updateTitle) {
- MenuItem item = null;
- for (DropDownMenu menu : mMenus) {
- item = menu.findItem(id);
- if (item != null) {
- item.setCheckable(true);
- item.setChecked(applied);
- item.setEnabled(enabled);
- if (updateTitle) {
- menu.setTitle(item.getTitle());
- }
- }
- }
- }
-
- public void setMenuItemVisibility(int id, boolean visibility) {
- MenuItem item = findMenuItem(id);
- if (item != null) {
- item.setVisible(visibility);
- }
- }
-
public boolean onMenuItemClick(MenuItem item) {
if (mListener != null) {
return mListener.onMenuItemClick(item);
diff --git a/src/com/android/gallery3d/ui/DetailsHelper.java b/src/com/android/gallery3d/ui/DetailsHelper.java
index 8f1cd0c..245145f 100644
--- a/src/com/android/gallery3d/ui/DetailsHelper.java
+++ b/src/com/android/gallery3d/ui/DetailsHelper.java
@@ -15,14 +15,14 @@
*/
package com.android.gallery3d.ui;
+import android.content.Context;
+import android.view.View.MeasureSpec;
+
import com.android.gallery3d.R;
import com.android.gallery3d.app.GalleryActivity;
import com.android.gallery3d.data.MediaDetails;
import com.android.gallery3d.ui.DetailsAddressResolver.AddressResolvingListener;
-import android.content.Context;
-import android.view.View.MeasureSpec;
-
public class DetailsHelper {
private static DetailsAddressResolver sAddressResolver;
private DetailsViewContainer mContainer;
diff --git a/src/com/android/gallery3d/ui/DialogDetailsView.java b/src/com/android/gallery3d/ui/DialogDetailsView.java
index c037f2a..60ced03 100644
--- a/src/com/android/gallery3d/ui/DialogDetailsView.java
+++ b/src/com/android/gallery3d/ui/DialogDetailsView.java
@@ -80,10 +80,6 @@
}
}
- public boolean isVisible() {
- return mDialog.isShowing();
- }
-
private void setDetails(MediaDetails details) {
mAdapter = new DetailsAdapter(details);
String title = String.format(
@@ -173,8 +169,10 @@
default: {
Object valueObj = detail.getValue();
// This shouldn't happen, log its key to help us diagnose the problem.
- Utils.assertTrue(valueObj != null, "%s's value is Null",
- DetailsHelper.getDetailsName(context, detail.getKey()));
+ if (valueObj == null) {
+ Utils.fail("%s's value is Null",
+ DetailsHelper.getDetailsName(context, detail.getKey()));
+ }
value = valueObj.toString();
}
}
diff --git a/src/com/android/gallery3d/ui/DisplayItem.java b/src/com/android/gallery3d/ui/DisplayItem.java
deleted file mode 100644
index 6b76ed0..0000000
--- a/src/com/android/gallery3d/ui/DisplayItem.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-public abstract class DisplayItem {
-
- protected int mBoxWidth;
- protected int mBoxHeight;
-
- // setBox() specifies the box that the DisplayItem should render into. It
- // should be called before first render(). It may be called again between
- // render() calls to change the size of the box.
- public void setBox(int width, int height) {
- mBoxWidth = width;
- mBoxHeight = height;
- }
-
- // Return values of render():
- // RENDER_MORE_PASS: more pass is needed for this item
- // RENDER_MORE_FRAME: need to render next frame (used for animation)
- public static final int RENDER_MORE_PASS = 1;
- public static final int RENDER_MORE_FRAME = 2;
-
- public abstract int render(GLCanvas canvas, int pass);
-
- public abstract long getIdentity();
-
- public int getRotation() {
- return 0;
- }
-}
diff --git a/src/com/android/gallery3d/ui/DrawableTexture.java b/src/com/android/gallery3d/ui/DrawableTexture.java
deleted file mode 100644
index 5c3964d..0000000
--- a/src/com/android/gallery3d/ui/DrawableTexture.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-
-// DrawableTexture is a texture whose content is from a Drawable.
-public class DrawableTexture extends CanvasTexture {
-
- private final Drawable mDrawable;
-
- public DrawableTexture(Drawable drawable, int width, int height) {
- super(width, height);
- mDrawable = drawable;
- }
-
- @Override
- protected void onDraw(Canvas canvas, Bitmap backing) {
- mDrawable.setBounds(0, 0, mWidth, mHeight);
- mDrawable.draw(canvas);
- }
-}
diff --git a/src/com/android/gallery3d/ui/EdgeEffect.java b/src/com/android/gallery3d/ui/EdgeEffect.java
index b2d83f5..362b8fe 100644
--- a/src/com/android/gallery3d/ui/EdgeEffect.java
+++ b/src/com/android/gallery3d/ui/EdgeEffect.java
@@ -16,14 +16,14 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.R;
-
import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Rect;
-import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import com.android.gallery3d.R;
+
// This is copied from android.widget.EdgeEffect with some small modifications:
// (1) Copy the images (overscroll_{edge|glow}.png) to local resources.
// (2) Use "GLCanvas" instead of "Canvas" for draw()'s parameter.
@@ -179,7 +179,7 @@
* back toward the edge reached to initiate the effect.
*/
public void onPull(float deltaDistance) {
- final long now = AnimationUtils.currentAnimationTimeMillis();
+ final long now = AnimationTime.get();
if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
return;
}
@@ -244,7 +244,7 @@
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = 0.f;
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = AnimationTime.get();
mDuration = RECEDE_TIME;
}
@@ -262,7 +262,7 @@
mState = STATE_ABSORB;
velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = AnimationTime.get();
mDuration = 0.1f + (velocity * 0.03f);
// The edge should always be at least partially visible, regardless
@@ -343,7 +343,7 @@
}
private void update() {
- final long time = AnimationUtils.currentAnimationTimeMillis();
+ final long time = AnimationTime.get();
final float t = Math.min((time - mStartTime) / mDuration, 1.f);
final float interp = mInterpolator.getInterpolation(t);
@@ -357,7 +357,7 @@
switch (mState) {
case STATE_ABSORB:
mState = STATE_RECEDE;
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = AnimationTime.get();
mDuration = RECEDE_TIME;
mEdgeAlphaStart = mEdgeAlpha;
@@ -373,7 +373,7 @@
break;
case STATE_PULL:
mState = STATE_PULL_DECAY;
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = AnimationTime.get();
mDuration = PULL_DECAY_TIME;
mEdgeAlphaStart = mEdgeAlpha;
diff --git a/src/com/android/gallery3d/ui/EdgeView.java b/src/com/android/gallery3d/ui/EdgeView.java
index db6a45c..bf97108 100644
--- a/src/com/android/gallery3d/ui/EdgeView.java
+++ b/src/com/android/gallery3d/ui/EdgeView.java
@@ -23,6 +23,7 @@
public class EdgeView extends GLView {
private static final String TAG = "EdgeView";
+ public static final int INVALID_DIRECTION = -1;
public static final int TOP = 0;
public static final int LEFT = 1;
public static final int BOTTOM = 2;
diff --git a/src/com/android/gallery3d/ui/ExtTexture.java b/src/com/android/gallery3d/ui/ExtTexture.java
new file mode 100644
index 0000000..d8267d1
--- /dev/null
+++ b/src/com/android/gallery3d/ui/ExtTexture.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
+
+// ExtTexture is a texture whose content comes from a external texture.
+// Before drawing, setSize() should be called.
+public class ExtTexture extends BasicTexture {
+
+ private static int[] sTextureId = new int[1];
+ private static float[] sCropRect = new float[4];
+ private int mTarget;
+
+ public ExtTexture(int target) {
+ GLId.glGenTextures(1, sTextureId, 0);
+ mId = sTextureId[0];
+ mTarget = target;
+ }
+
+ private void uploadToCanvas(GLCanvas canvas) {
+ GL11 gl = canvas.getGLInstance();
+
+ int width = getWidth();
+ int height = getHeight();
+ // Define a vertically flipped crop rectangle for OES_draw_texture.
+ // The four values in sCropRect are: left, bottom, width, and
+ // height. Negative value of width or height means flip.
+ sCropRect[0] = 0;
+ sCropRect[1] = height;
+ sCropRect[2] = width;
+ sCropRect[3] = -height;
+
+ // Set texture parameters.
+ gl.glBindTexture(mTarget, mId);
+ gl.glTexParameterfv(mTarget,
+ GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
+ gl.glTexParameteri(mTarget,
+ GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
+ gl.glTexParameteri(mTarget,
+ GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
+ gl.glTexParameterf(mTarget,
+ GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
+ gl.glTexParameterf(mTarget,
+ GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
+
+ setAssociatedCanvas(canvas);
+ mState = UploadedTexture.STATE_LOADED;
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ if (!isLoaded(canvas)) {
+ uploadToCanvas(canvas);
+ }
+
+ return true;
+ }
+
+ @Override
+ public int getTarget() {
+ return mTarget;
+ }
+
+ public boolean isOpaque() {
+ return true;
+ }
+
+ @Override
+ public void yield() {
+ // we cannot free the texture because we have no backup.
+ }
+}
diff --git a/src/com/android/gallery3d/ui/FadeInTexture.java b/src/com/android/gallery3d/ui/FadeInTexture.java
index 1c44b5a..648bdcf 100644
--- a/src/com/android/gallery3d/ui/FadeInTexture.java
+++ b/src/com/android/gallery3d/ui/FadeInTexture.java
@@ -16,40 +16,20 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
-
-import android.os.SystemClock;
-
// FadeInTexture is a texture which begins with a color, then gradually animates
// into a given texture.
-public class FadeInTexture implements Texture {
+public class FadeInTexture extends FadeTexture implements Texture {
+ @SuppressWarnings("unused")
private static final String TAG = "FadeInTexture";
- // The duration of the animation in milliseconds
- private static final int DURATION = 180;
-
- private final BasicTexture mTexture;
private final int mColor;
- private final long mStartTime;
- private final int mWidth;
- private final int mHeight;
- private final boolean mIsOpaque;
- private boolean mIsAnimating;
public FadeInTexture(int color, BasicTexture texture) {
+ super(texture);
mColor = color;
- mTexture = texture;
- mWidth = mTexture.getWidth();
- mHeight = mTexture.getHeight();
- mIsOpaque = mTexture.isOpaque();
- mStartTime = now();
- mIsAnimating = true;
}
- public void draw(GLCanvas canvas, int x, int y) {
- draw(canvas, x, y, mWidth, mHeight);
- }
-
+ @Override
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
if (isAnimating()) {
canvas.drawMixed(mTexture, mColor, getRatio(), x, y, w, h);
@@ -57,34 +37,4 @@
mTexture.draw(canvas, x, y, w, h);
}
}
-
- public boolean isOpaque() {
- return mIsOpaque;
- }
-
- public int getWidth() {
- return mWidth;
- }
-
- public int getHeight() {
- return mHeight;
- }
-
- public boolean isAnimating() {
- if (mIsAnimating) {
- if (now() - mStartTime >= DURATION) {
- mIsAnimating = false;
- }
- }
- return mIsAnimating;
- }
-
- private float getRatio() {
- float r = (float)(now() - mStartTime) / DURATION;
- return Utils.clamp(1.0f - r, 0.0f, 1.0f);
- }
-
- private long now() {
- return SystemClock.uptimeMillis();
- }
}
diff --git a/src/com/android/gallery3d/ui/FadeOutTexture.java b/src/com/android/gallery3d/ui/FadeOutTexture.java
new file mode 100644
index 0000000..c438977
--- /dev/null
+++ b/src/com/android/gallery3d/ui/FadeOutTexture.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+// FadeOutTexture is a texture which begins with a given texture, then gradually animates
+// into fading out totally.
+public class FadeOutTexture extends FadeTexture implements Texture {
+ @SuppressWarnings("unused")
+ private static final String TAG = "FadeOutTexture";
+
+ public FadeOutTexture(BasicTexture texture) {
+ super(texture);
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+ if (isAnimating()) {
+ canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
+ canvas.setAlpha(getRatio());
+ mTexture.draw(canvas, x, y, w, h);
+ canvas.restore();
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/ui/FadeTexture.java b/src/com/android/gallery3d/ui/FadeTexture.java
new file mode 100644
index 0000000..ad0d358
--- /dev/null
+++ b/src/com/android/gallery3d/ui/FadeTexture.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import com.android.gallery3d.common.Utils;
+
+// FadeTexture is a texture which fades the given texture along the time.
+public abstract class FadeTexture implements Texture {
+ @SuppressWarnings("unused")
+ private static final String TAG = "FadeTexture";
+
+ // The duration of the fading animation in milliseconds
+ public static final int DURATION = 180;
+
+ protected final BasicTexture mTexture;
+ private final long mStartTime;
+ private final int mWidth;
+ private final int mHeight;
+ private final boolean mIsOpaque;
+ private boolean mIsAnimating;
+
+ public FadeTexture(BasicTexture texture) {
+ mTexture = texture;
+ mWidth = mTexture.getWidth();
+ mHeight = mTexture.getHeight();
+ mIsOpaque = mTexture.isOpaque();
+ mStartTime = now();
+ mIsAnimating = true;
+ }
+
+ public void draw(GLCanvas canvas, int x, int y) {
+ draw(canvas, x, y, mWidth, mHeight);
+ }
+
+ /**
+ * Subclasses should implement how to fade the texture.
+ */
+ public abstract void draw(GLCanvas canvas, int x, int y, int w, int h);
+
+ public boolean isOpaque() {
+ return mIsOpaque;
+ }
+
+ public int getWidth() {
+ return mWidth;
+ }
+
+ public int getHeight() {
+ return mHeight;
+ }
+
+ public boolean isAnimating() {
+ if (mIsAnimating) {
+ if (now() - mStartTime >= DURATION) {
+ mIsAnimating = false;
+ }
+ }
+ return mIsAnimating;
+ }
+
+ protected float getRatio() {
+ float r = (float)(now() - mStartTime) / DURATION;
+ return Utils.clamp(1.0f - r, 0.0f, 1.0f);
+ }
+
+ private long now() {
+ return AnimationTime.get();
+ }
+}
diff --git a/src/com/android/gallery3d/ui/FilmStripView.java b/src/com/android/gallery3d/ui/FilmStripView.java
deleted file mode 100644
index e6ed49b..0000000
--- a/src/com/android/gallery3d/ui/FilmStripView.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.anim.AlphaAnimation;
-import com.android.gallery3d.app.AlbumDataAdapter;
-import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.data.Path;
-
-import android.content.Context;
-import android.view.MotionEvent;
-import android.view.View.MeasureSpec;
-
-public class FilmStripView extends GLView implements ScrollBarView.Listener,
- UserInteractionListener {
- @SuppressWarnings("unused")
- private static final String TAG = "FilmStripView";
-
- private static final int HIDE_ANIMATION_DURATION = 300; // 0.3 sec
-
- public interface Listener {
- // Returns false if it cannot jump to the specified index at this time.
- boolean onSlotSelected(int slotIndex);
- }
-
- private int mTopMargin, mMidMargin, mBottomMargin;
- private int mContentSize, mBarSize, mGripSize;
- private AlbumView mAlbumView;
- private ScrollBarView mScrollBarView;
- private AlbumDataAdapter mAlbumDataAdapter;
- private StripDrawer mStripDrawer;
- private Listener mListener;
- private UserInteractionListener mUIListener;
- private NinePatchTexture mBackgroundTexture;
-
- // The layout of FileStripView is
- // topMargin
- // ----+----+
- // / +----+--\
- // contentSize | | thumbSize
- // \ +----+--/
- // ----+----+
- // midMargin
- // ----+----+
- // / +----+--\
- // barSize | | gripSize
- // \ +----+--/
- // ----+----+
- // bottomMargin
- public FilmStripView(GalleryActivity activity, MediaSet mediaSet,
- int topMargin, int midMargin, int bottomMargin, int contentSize,
- int thumbSize, int barSize, int gripSize, int gripWidth) {
- mTopMargin = topMargin;
- mMidMargin = midMargin;
- mBottomMargin = bottomMargin;
- mContentSize = contentSize;
- mBarSize = barSize;
- mGripSize = gripSize;
-
- mStripDrawer = new StripDrawer((Context) activity);
- SlotView.Spec spec = new SlotView.Spec();
- spec.slotWidth = thumbSize;
- spec.slotHeight = thumbSize;
- mAlbumView = new AlbumView(activity, spec, thumbSize);
- mAlbumView.setOverscrollEffect(SlotView.OVERSCROLL_NONE);
- mAlbumView.setSelectionDrawer(mStripDrawer);
- mAlbumView.setListener(new SlotView.SimpleListener() {
- @Override
- public void onDown(int index) {
- FilmStripView.this.onDown(index);
- }
- @Override
- public void onUp() {
- FilmStripView.this.onUp();
- }
- @Override
- public void onSingleTapUp(int slotIndex) {
- FilmStripView.this.onSingleTapUp(slotIndex);
- }
- @Override
- public void onLongTap(int slotIndex) {
- FilmStripView.this.onLongTap(slotIndex);
- }
- @Override
- public void onScrollPositionChanged(int position, int total) {
- FilmStripView.this.onScrollPositionChanged(position, total);
- }
- });
- mAlbumView.setUserInteractionListener(this);
- mAlbumDataAdapter = new AlbumDataAdapter(activity, mediaSet);
- addComponent(mAlbumView);
- mScrollBarView = new ScrollBarView(activity.getAndroidContext(),
- mGripSize, gripWidth);
- mScrollBarView.setListener(this);
- addComponent(mScrollBarView);
-
- mAlbumView.setModel(mAlbumDataAdapter);
- mBackgroundTexture = new NinePatchTexture(activity.getAndroidContext(),
- R.drawable.navstrip_translucent);
- }
-
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
- public void setUserInteractionListener(UserInteractionListener listener) {
- mUIListener = listener;
- }
-
- public void show() {
- if (getVisibility() == GLView.VISIBLE) return;
- startAnimation(null);
- setVisibility(GLView.VISIBLE);
- }
-
- public void hide() {
- if (getVisibility() == GLView.INVISIBLE) return;
- AlphaAnimation animation = new AlphaAnimation(1, 0);
- animation.setDuration(HIDE_ANIMATION_DURATION);
- startAnimation(animation);
- setVisibility(GLView.INVISIBLE);
- }
-
- @Override
- protected void onVisibilityChanged(int visibility) {
- super.onVisibilityChanged(visibility);
- if (visibility == GLView.VISIBLE) {
- onUserInteraction();
- }
- }
-
- @Override
- protected void onMeasure(int widthSpec, int heightSpec) {
- int height = mTopMargin + mContentSize + mMidMargin + mBarSize + mBottomMargin;
- MeasureHelper.getInstance(this)
- .setPreferredContentSize(MeasureSpec.getSize(widthSpec), height)
- .measure(widthSpec, heightSpec);
- }
-
- @Override
- protected void onLayout(
- boolean changed, int left, int top, int right, int bottom) {
- if (!changed) return;
- mAlbumView.layout(0, mTopMargin, right - left, mTopMargin + mContentSize);
- int barStart = mTopMargin + mContentSize + mMidMargin;
- mScrollBarView.layout(0, barStart, right - left, barStart + mBarSize);
- int width = right - left;
- int height = bottom - top;
- }
-
- @Override
- protected boolean onTouch(MotionEvent event) {
- // consume all touch events on the "gray area", so they don't go to
- // the photo view below. (otherwise you can scroll the picture through
- // it).
- return true;
- }
-
- @Override
- protected boolean dispatchTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- onUserInteractionBegin();
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- onUserInteractionEnd();
- break;
- }
-
- return super.dispatchTouchEvent(event);
- }
-
- @Override
- protected void render(GLCanvas canvas) {
- mBackgroundTexture.draw(canvas, 0, 0, getWidth(), getHeight());
- super.render(canvas);
- }
-
- private void onDown(int index) {
- MediaItem item = mAlbumDataAdapter.get(index);
- Path path = (item == null) ? null : item.getPath();
- mStripDrawer.setPressedPath(path);
- mAlbumView.invalidate();
- }
-
- private void onUp() {
- mStripDrawer.setPressedPath(null);
- mAlbumView.invalidate();
- }
-
- private void onSingleTapUp(int slotIndex) {
- if (mListener.onSlotSelected(slotIndex)) {
- mAlbumView.setFocusIndex(slotIndex);
- }
- }
-
- private void onLongTap(int slotIndex) {
- onSingleTapUp(slotIndex);
- }
-
- private void onScrollPositionChanged(int position, int total) {
- mScrollBarView.setContentPosition(position, total);
- }
-
- // Called by AlbumView
- @Override
- public void onUserInteractionBegin() {
- mUIListener.onUserInteractionBegin();
- }
-
- // Called by AlbumView
- @Override
- public void onUserInteractionEnd() {
- mUIListener.onUserInteractionEnd();
- }
-
- // Called by AlbumView
- @Override
- public void onUserInteraction() {
- mUIListener.onUserInteraction();
- }
-
- // Called by ScrollBarView
- @Override
- public void onScrollBarPositionChanged(int position) {
- mAlbumView.setScrollPosition(position);
- }
-
- public void setFocusIndex(int slotIndex) {
- mAlbumView.setFocusIndex(slotIndex);
- mAlbumView.makeSlotVisible(slotIndex);
- }
-
- public void setStartIndex(int slotIndex) {
- mAlbumView.setStartIndex(slotIndex);
- }
-
- public void pause() {
- mAlbumView.pause();
- mAlbumDataAdapter.pause();
- }
-
- public void resume() {
- mAlbumView.resume();
- mAlbumDataAdapter.resume();
- }
-}
diff --git a/src/com/android/gallery3d/ui/FlingScroller.java b/src/com/android/gallery3d/ui/FlingScroller.java
index fbe27ce..1cddcdb 100644
--- a/src/com/android/gallery3d/ui/FlingScroller.java
+++ b/src/com/android/gallery3d/ui/FlingScroller.java
@@ -16,7 +16,6 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
// This is a customized version of Scroller, with a interface similar to
// android.widget.Scroller. It does fling only, not scroll.
diff --git a/src/com/android/gallery3d/ui/GLCanvas.java b/src/com/android/gallery3d/ui/GLCanvas.java
index 88c02f3..9b8053f 100644
--- a/src/com/android/gallery3d/ui/GLCanvas.java
+++ b/src/com/android/gallery3d/ui/GLCanvas.java
@@ -37,14 +37,6 @@
// Clear the drawing buffers. This should only be used by GLRoot.
public void clearBuffer();
- // This is the time value used to calculate the animation in the current
- // frame. The "set" function should only called by GLRoot, and the
- // "time" parameter must be nonnegative.
- public void setCurrentAnimationTimeMillis(long time);
- public long currentAnimationTimeMillis();
-
- public void setBlendEnabled(boolean enabled);
-
// Sets and gets the current alpha, alpha must be in [0, 1].
public void setAlpha(float alpha);
public float getAlpha();
@@ -54,26 +46,21 @@
// Change the current transform matrix.
public void translate(float x, float y, float z);
+ public void translate(float x, float y);
public void scale(float sx, float sy, float sz);
public void rotate(float angle, float x, float y, float z);
public void multiplyMatrix(float[] mMatrix, int offset);
- // Modifies the current clip with the specified rectangle.
- // (current clip) = (current clip) intersect (specified rectangle).
- // Returns true if the result clip is non-empty.
- public boolean clipRect(int left, int top, int right, int bottom);
-
- // Pushes the configuration state (matrix, alpha, and clip) onto
+ // Pushes the configuration state (matrix, and alpha) onto
// a private stack.
- public int save();
+ public void save();
// Same as save(), but only save those specified in saveFlags.
- public int save(int saveFlags);
+ public void save(int saveFlags);
public static final int SAVE_FLAG_ALL = 0xFFFFFFFF;
- public static final int SAVE_FLAG_CLIP = 0x01;
- public static final int SAVE_FLAG_ALPHA = 0x02;
- public static final int SAVE_FLAG_MATRIX = 0x04;
+ public static final int SAVE_FLAG_ALPHA = 0x01;
+ public static final int SAVE_FLAG_MATRIX = 0x02;
// Pops from the top of the stack as current configuration state (matrix,
// alpha, and clip). This call balances a previous call to save(), and is
@@ -98,26 +85,19 @@
public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
int uvBuffer, int indexBuffer, int indexCount);
- // Draws a texture to the specified rectangle. The "alpha" parameter
- // overrides the current drawing alpha value.
- public void drawTexture(BasicTexture texture,
- int x, int y, int width, int height, float alpha);
-
- // Draws a the source rectangle part of the texture to the target rectangle.
+ // Draws the source rectangle part of the texture to the target rectangle.
public void drawTexture(BasicTexture texture, RectF source, RectF target);
+ // Draw a texture with a specified texture transform.
+ public void drawTexture(BasicTexture texture, float[] mTextureTransform,
+ int x, int y, int w, int h);
+
// Draw two textures to the specified rectangle. The actual texture used is
// from * (1 - ratio) + to * ratio
// The two textures must have the same size.
- public void drawMixed(BasicTexture from, BasicTexture to,
- float ratio, int x, int y, int w, int h);
-
public void drawMixed(BasicTexture from, int toColor,
float ratio, int x, int y, int w, int h);
- // Return a texture copied from the specified rectangle.
- public BasicTexture copyTexture(int x, int y, int width, int height);
-
// Gets the underlying GL instance. This is used only when direct access to
// GL is needed.
public GL11 getGLInstance();
@@ -135,4 +115,6 @@
// called in the GL thread.
public void deleteRecycledResources();
+ // Dump statistics information and clear the counters. For debug only.
+ public void dumpStatisticsAndClear();
}
diff --git a/src/com/android/gallery3d/ui/GLCanvasImpl.java b/src/com/android/gallery3d/ui/GLCanvasImpl.java
index 612c7c4..f822951 100644
--- a/src/com/android/gallery3d/ui/GLCanvasImpl.java
+++ b/src/com/android/gallery3d/ui/GLCanvasImpl.java
@@ -16,18 +16,19 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.IntArray;
-
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLU;
import android.opengl.Matrix;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.IntArray;
+
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
-import java.util.Stack;
+import java.util.ArrayList;
+
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;
@@ -51,8 +52,9 @@
private final float mMatrixValues[] = new float[16];
private final float mTextureMatrixValues[] = new float[16];
- // mapPoints needs 10 input and output numbers.
- private final float mMapPointsBuffer[] = new float[10];
+ // The results of mapPoints are stored in this buffer, and the order is
+ // x1, y1, x2, y2.
+ private final float mMapPointsBuffer[] = new float[4];
private final float mTextureColor[] = new float[4];
@@ -60,12 +62,9 @@
private final GLState mGLState;
- private long mAnimationTime;
-
private float mAlpha;
- private final Rect mClipRect = new Rect();
- private final Stack<ConfigState> mRestoreStack =
- new Stack<ConfigState>();
+ private final ArrayList<ConfigState> mRestoreStack =
+ new ArrayList<ConfigState>();
private ConfigState mRecycledRestoreAction;
private final RectF mDrawTextureSourceRect = new RectF();
@@ -107,28 +106,23 @@
Matrix.translateM(matrix, 0, 0, mHeight, 0);
Matrix.scaleM(matrix, 0, 1, -1, 1);
- mClipRect.set(0, 0, width, height);
gl.glScissor(0, 0, width, height);
}
- public long currentAnimationTimeMillis() {
- return mAnimationTime;
- }
-
public void setAlpha(float alpha) {
Utils.assertTrue(alpha >= 0 && alpha <= 1);
mAlpha = alpha;
}
+ public float getAlpha() {
+ return mAlpha;
+ }
+
public void multiplyAlpha(float alpha) {
Utils.assertTrue(alpha >= 0 && alpha <= 1);
mAlpha *= alpha;
}
- public float getAlpha() {
- return mAlpha;
- }
-
private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
}
@@ -142,7 +136,7 @@
xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0);
int[] name = new int[1];
- gl.glGenBuffers(1, name, 0);
+ GLId.glGenBuffers(1, name, 0);
mBoxCoords = name[0];
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords);
@@ -168,10 +162,9 @@
mGLState.setColorMode(paint.getColor(), mAlpha);
mGLState.setLineWidth(paint.getLineWidth());
- mGLState.setLineSmooth(paint.getAntiAlias());
saveTransform();
- translate(x, y, 0);
+ translate(x, y);
scale(width, height, 1);
gl.glLoadMatrixf(mMatrixValues, 0);
@@ -186,10 +179,9 @@
mGLState.setColorMode(paint.getColor(), mAlpha);
mGLState.setLineWidth(paint.getLineWidth());
- mGLState.setLineSmooth(paint.getAntiAlias());
saveTransform();
- translate(x1, y1, 0);
+ translate(x1, y1);
scale(x2 - x1, y2 - y1, 1);
gl.glLoadMatrixf(mMatrixValues, 0);
@@ -204,7 +196,7 @@
GL11 gl = mGL;
saveTransform();
- translate(x, y, 0);
+ translate(x, y);
scale(width, height, 1);
gl.glLoadMatrixf(mMatrixValues, 0);
@@ -218,11 +210,23 @@
Matrix.translateM(mMatrixValues, 0, x, y, z);
}
+ // This is a faster version of translate(x, y, z) because
+ // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
+ // (3) we unroll the loop
+ public void translate(float x, float y) {
+ float[] m = mMatrixValues;
+ m[12] += m[0] * x + m[4] * y;
+ m[13] += m[1] * x + m[5] * y;
+ m[14] += m[2] * x + m[6] * y;
+ m[15] += m[3] * x + m[7] * y;
+ }
+
public void scale(float sx, float sy, float sz) {
Matrix.scaleM(mMatrixValues, 0, sx, sy, sz);
}
public void rotate(float angle, float x, float y, float z) {
+ if (angle == 0) return;
float[] temp = mTempMatrix;
Matrix.setRotateM(temp, 0, angle, x, y, z);
Matrix.multiplyMM(temp, 16, mMatrixValues, 0, temp, 0);
@@ -239,7 +243,7 @@
GL11 gl = mGL;
saveTransform();
- translate(x, y, 0);
+ translate(x, y);
scale(width, height, 1);
gl.glLoadMatrixf(mMatrixValues, 0);
@@ -263,7 +267,7 @@
setTextureCoords(0, 0, 1, 1);
saveTransform();
- translate(x, y, 0);
+ translate(x, y);
mGL.glLoadMatrixf(mMatrixValues, 0);
@@ -285,55 +289,26 @@
mCountDrawMesh++;
}
- private float[] mapPoints(float matrix[], int x1, int y1, int x2, int y2) {
- float[] point = mMapPointsBuffer;
- int srcOffset = 6;
- point[srcOffset] = x1;
- point[srcOffset + 1] = y1;
- point[srcOffset + 2] = 0;
- point[srcOffset + 3] = 1;
+ // Transforms two points by the given matrix m. The result
+ // {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned.
+ private float[] mapPoints(float m[], int x1, int y1, int x2, int y2) {
+ float[] r = mMapPointsBuffer;
- int resultOffset = 0;
- Matrix.multiplyMV(point, resultOffset, matrix, 0, point, srcOffset);
- point[resultOffset] /= point[resultOffset + 3];
- point[resultOffset + 1] /= point[resultOffset + 3];
+ // Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused.
+ float x3 = m[0] * x1 + m[4] * y1 + m[12];
+ float y3 = m[1] * x1 + m[5] * y1 + m[13];
+ float w3 = m[3] * x1 + m[7] * y1 + m[15];
+ r[0] = x3 / w3;
+ r[1] = y3 / w3;
- // map the second point
- point[srcOffset] = x2;
- point[srcOffset + 1] = y2;
- resultOffset = 2;
- Matrix.multiplyMV(point, resultOffset, matrix, 0, point, srcOffset);
- point[resultOffset] /= point[resultOffset + 3];
- point[resultOffset + 1] /= point[resultOffset + 3];
+ // Same for x2 y2.
+ float x4 = m[0] * x2 + m[4] * y2 + m[12];
+ float y4 = m[1] * x2 + m[5] * y2 + m[13];
+ float w4 = m[3] * x2 + m[7] * y2 + m[15];
+ r[2] = x4 / w4;
+ r[3] = y4 / w4;
- return point;
- }
-
- public boolean clipRect(int left, int top, int right, int bottom) {
- float point[] = mapPoints(mMatrixValues, left, top, right, bottom);
-
- // mMatrix could be a rotation matrix. In this case, we need to find
- // the boundaries after rotation. (only handle 90 * n degrees)
- if (point[0] > point[2]) {
- left = (int) point[2];
- right = (int) point[0];
- } else {
- left = (int) point[0];
- right = (int) point[2];
- }
- if (point[1] > point[3]) {
- top = (int) point[3];
- bottom = (int) point[1];
- } else {
- top = (int) point[1];
- bottom = (int) point[3];
- }
- Rect clip = mClipRect;
-
- boolean intersect = clip.intersect(left, top, right, bottom);
- if (!intersect) clip.set(0, 0, 0, 0);
- mGL.glScissor(clip.left, clip.top, clip.width(), clip.height());
- return intersect;
+ return r;
}
private void drawBoundTexture(
@@ -357,10 +332,10 @@
// draw the rect from bottom-left to top-right
float points[] = mapPoints(
mMatrixValues, x, y + height, x + width, y);
- x = Math.round(points[0]);
- y = Math.round(points[1]);
- width = Math.round(points[2]) - x;
- height = Math.round(points[3]) - y;
+ x = (int) (points[0] + 0.5f);
+ y = (int) (points[1] + 0.5f);
+ width = (int) (points[2] + 0.5f) - x;
+ height = (int) (points[3] + 0.5f) - y;
if (width > 0 && height > 0) {
((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height);
mCountTextureOES++;
@@ -373,11 +348,7 @@
drawTexture(texture, x, y, width, height, mAlpha);
}
- public void setBlendEnabled(boolean enabled) {
- mBlendEnabled = enabled;
- }
-
- public void drawTexture(BasicTexture texture,
+ private void drawTexture(BasicTexture texture,
int x, int y, int width, int height, float alpha) {
if (width <= 0 || height <= 0) return;
@@ -406,6 +377,16 @@
textureRect(target.left, target.top, target.width(), target.height());
}
+ public void drawTexture(BasicTexture texture, float[] mTextureTransform,
+ int x, int y, int w, int h) {
+ mGLState.setBlendEnabled(mBlendEnabled
+ && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
+ if (!bindTexture(texture)) return;
+ setTextureCoords(mTextureTransform);
+ mGLState.setTextureAlpha(mAlpha);
+ textureRect(x, y, w, h);
+ }
+
// This function changes the source coordinate to the texture coordinates.
// It also clips the source and target coordinates if it is beyond the
// bound of the texture.
@@ -442,15 +423,11 @@
drawMixed(from, toColor, ratio, x, y, w, h, mAlpha);
}
- public void drawMixed(BasicTexture from, BasicTexture to,
- float ratio, int x, int y, int w, int h) {
- drawMixed(from, to, ratio, x, y, w, h, mAlpha);
- }
-
private boolean bindTexture(BasicTexture texture) {
if (!texture.onBind(this)) return false;
- mGLState.setTexture2DEnabled(true);
- mGL.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId());
+ int target = texture.getTarget();
+ mGLState.setTextureTarget(target);
+ mGL.glBindTexture(target, texture.getId());
return true;
}
@@ -464,8 +441,8 @@
private void drawMixed(BasicTexture from, int toColor,
float ratio, int x, int y, int width, int height, float alpha) {
-
- if (ratio <= 0) {
+ // change from 0 to 0.01f to prevent getting divided by zero below
+ if (ratio <= 0.01f) {
drawTexture(from, x, y, width, height, alpha);
return;
} else if (ratio >= 1) {
@@ -482,30 +459,26 @@
//
// The formula we want:
// alpha * ((1 - ratio) * from + ratio * to)
+ //
// The formula that GL supports is in the form of:
- // combo * (modulate * from) + (1 - combo) * to
+ // combo * from + (1 - combo) * to * scale
//
- // So, we have combo = 1 - alpha * ratio
- // and modulate = alpha * (1f - ratio) / combo
+ // So, we have combo = alpha * (1 - ratio)
+ // and scale = alpha * ratio / (1 - combo)
//
- float comboRatio = 1 - alpha * ratio;
-
- // handle the case that (1 - comboRatio) == 0
- if (alpha < OPAQUE_ALPHA) {
- mGLState.setTextureAlpha(alpha * (1f - ratio) / comboRatio);
- } else {
- mGLState.setTextureAlpha(1f);
- }
+ float combo = alpha * (1 - ratio);
+ float scale = alpha * ratio / (1 - combo);
// Interpolate the RGB and alpha values between both textures.
mGLState.setTexEnvMode(GL11.GL_COMBINE);
+
// Specify the interpolation factor via the alpha component of
// GL_TEXTURE_ENV_COLORs.
// RGB component are get from toColor and will used as SRC1
- float colorAlpha = (float) (toColor >>> 24) / (0xff * 0xff);
- setTextureColor(((toColor >>> 16) & 0xff) * colorAlpha,
- ((toColor >>> 8) & 0xff) * colorAlpha,
- (toColor & 0xff) * colorAlpha, comboRatio);
+ float colorScale = scale * (toColor >>> 24) / (0xff * 0xff);
+ setTextureColor(((toColor >>> 16) & 0xff) * colorScale,
+ ((toColor >>> 8) & 0xff) * colorScale,
+ (toColor & 0xff) * colorScale, combo);
gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0);
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE);
@@ -527,84 +500,6 @@
mGLState.setTexEnvMode(GL11.GL_REPLACE);
}
- private void drawMixed(BasicTexture from, BasicTexture to,
- float ratio, int x, int y, int width, int height, float alpha) {
-
- if (ratio <= 0) {
- drawTexture(from, x, y, width, height, alpha);
- return;
- } else if (ratio >= 1) {
- drawTexture(to, x, y, width, height, alpha);
- return;
- }
-
- // In the current implementation the two textures must have the
- // same size.
- Utils.assertTrue(from.getWidth() == to.getWidth()
- && from.getHeight() == to.getHeight());
-
- mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
- || !to.isOpaque() || alpha < OPAQUE_ALPHA));
-
- final GL11 gl = mGL;
- if (!bindTexture(from)) return;
-
- //
- // The formula we want:
- // alpha * ((1 - ratio) * from + ratio * to)
- // The formula that GL supports is in the form of:
- // combo * (modulate * from) + (1 - combo) * to
- //
- // So, we have combo = 1 - alpha * ratio
- // and modulate = alpha * (1f - ratio) / combo
- //
- float comboRatio = 1 - alpha * ratio;
-
- // handle the case that (1 - comboRatio) == 0
- if (alpha < OPAQUE_ALPHA) {
- mGLState.setTextureAlpha(alpha * (1f - ratio) / comboRatio);
- } else {
- mGLState.setTextureAlpha(1f);
- }
-
- gl.glActiveTexture(GL11.GL_TEXTURE1);
- if (!bindTexture(to)) {
- // Disable TEXTURE1.
- gl.glDisable(GL11.GL_TEXTURE_2D);
- // Switch back to the default texture unit.
- gl.glActiveTexture(GL11.GL_TEXTURE0);
- return;
- }
- gl.glEnable(GL11.GL_TEXTURE_2D);
-
- // Interpolate the RGB and alpha values between both textures.
- mGLState.setTexEnvMode(GL11.GL_COMBINE);
- gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE);
- gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE);
-
- // Specify the interpolation factor via the alpha component of
- // GL_TEXTURE_ENV_COLORs.
- // We don't use the RGB color, so just give them 0s.
- setTextureColor(0, 0, 0, comboRatio);
- gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0);
-
- // Wire up the interpolation factor for RGB.
- gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT);
- gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA);
-
- // Wire up the interpolation factor for alpha.
- gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT);
- gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA);
-
- // Draw the combined texture.
- drawBoundTexture(to, x, y, width, height);
-
- // Disable TEXTURE1.
- gl.glDisable(GL11.GL_TEXTURE_2D);
- // Switch back to the default texture unit.
- gl.glActiveTexture(GL11.GL_TEXTURE0);
- }
-
// TODO: the code only work for 2D should get fixed for 3D or removed
private static final int MSKEW_X = 4;
private static final int MSKEW_Y = 1;
@@ -619,48 +514,13 @@
|| matrix[MSCALE_Y] > eps;
}
- public BasicTexture copyTexture(int x, int y, int width, int height) {
-
- if (isMatrixRotatedOrFlipped(mMatrixValues)) {
- throw new IllegalArgumentException("cannot support rotated matrix");
- }
- float points[] = mapPoints(mMatrixValues, x, y + height, x + width, y);
- x = (int) points[0];
- y = (int) points[1];
- width = (int) points[2] - x;
- height = (int) points[3] - y;
-
- GL11 gl = mGL;
-
- RawTexture texture = RawTexture.newInstance(this);
- gl.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId());
- texture.setSize(width, height);
-
- int[] cropRect = {0, 0, width, height};
- gl.glTexParameteriv(GL11.GL_TEXTURE_2D,
- GL11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, 0);
- gl.glTexParameteri(GL11.GL_TEXTURE_2D,
- GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
- gl.glTexParameteri(GL11.GL_TEXTURE_2D,
- GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
- gl.glTexParameterf(GL11.GL_TEXTURE_2D,
- GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
- gl.glTexParameterf(GL11.GL_TEXTURE_2D,
- GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
- gl.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0,
- GL11.GL_RGB, x, y, texture.getTextureWidth(),
- texture.getTextureHeight(), 0);
-
- return texture;
- }
-
private static class GLState {
private final GL11 mGL;
private int mTexEnvMode = GL11.GL_REPLACE;
private float mTextureAlpha = 1.0f;
- private boolean mTexture2DEnabled = true;
+ private int mTextureTarget = 0;
private boolean mBlendEnabled = true;
private float mLineWidth = 1.0f;
private boolean mLineSmooth = false;
@@ -705,16 +565,6 @@
mGL.glLineWidth(width);
}
- public void setLineSmooth(boolean enabled) {
- if (mLineSmooth == enabled) return;
- mLineSmooth = enabled;
- if (enabled) {
- mGL.glEnable(GL11.GL_LINE_SMOOTH);
- } else {
- mGL.glDisable(GL11.GL_LINE_SMOOTH);
- }
- }
-
public void setTextureAlpha(float alpha) {
if (mTextureAlpha == alpha) return;
mTextureAlpha = alpha;
@@ -735,7 +585,7 @@
// again in setTextureAlpha(float) later.
mTextureAlpha = -1.0f;
- setTexture2DEnabled(false);
+ setTextureTarget(0);
float prealpha = (color >>> 24) * alpha * 65535f / 255f / 255f;
mGL.glColor4x(
@@ -745,13 +595,15 @@
Math.round(255 * prealpha));
}
- public void setTexture2DEnabled(boolean enabled) {
- if (mTexture2DEnabled == enabled) return;
- mTexture2DEnabled = enabled;
- if (enabled) {
- mGL.glEnable(GL11.GL_TEXTURE_2D);
- } else {
- mGL.glDisable(GL11.GL_TEXTURE_2D);
+ // target is a value like GL_TEXTURE_2D. If target = 0, texturing is disabled.
+ public void setTextureTarget(int target) {
+ if (mTextureTarget == target) return;
+ if (mTextureTarget != 0) {
+ mGL.glDisable(mTextureTarget);
+ }
+ mTextureTarget = target;
+ if (mTextureTarget != 0) {
+ mGL.glEnable(mTextureTarget);
}
}
@@ -770,11 +622,6 @@
return mGL;
}
- public void setCurrentAnimationTimeMillis(long time) {
- Utils.assertTrue(time >= 0);
- mAnimationTime = time;
- }
-
public void clearBuffer() {
mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
}
@@ -796,6 +643,12 @@
mGL.glMatrixMode(GL11.GL_MODELVIEW);
}
+ private void setTextureCoords(float[] mTextureTransform) {
+ mGL.glMatrixMode(GL11.GL_TEXTURE);
+ mGL.glLoadMatrixf(mTextureTransform, 0);
+ mGL.glMatrixMode(GL11.GL_MODELVIEW);
+ }
+
// unloadTexture and deleteBuffer can be called from the finalizer thread,
// so we synchronized on the mUnboundTextures object.
public boolean unloadTexture(BasicTexture t) {
@@ -816,23 +669,23 @@
synchronized (mUnboundTextures) {
IntArray ids = mUnboundTextures;
if (ids.size() > 0) {
- mGL.glDeleteTextures(ids.size(), ids.getInternalArray(), 0);
+ GLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0);
ids.clear();
}
ids = mDeleteBuffers;
if (ids.size() > 0) {
- mGL.glDeleteBuffers(ids.size(), ids.getInternalArray(), 0);
+ GLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0);
ids.clear();
}
}
}
- public int save() {
- return save(SAVE_FLAG_ALL);
+ public void save() {
+ save(SAVE_FLAG_ALL);
}
- public int save(int saveFlags) {
+ public void save(int saveFlags) {
ConfigState config = obtainRestoreConfig();
if ((saveFlags & SAVE_FLAG_ALPHA) != 0) {
@@ -841,11 +694,6 @@
config.mAlpha = -1;
}
- if ((saveFlags & SAVE_FLAG_CLIP) != 0) {
- config.mRect.set(mClipRect);
- } else {
- config.mRect.left = Integer.MAX_VALUE;
- }
if ((saveFlags & SAVE_FLAG_MATRIX) != 0) {
System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16);
@@ -853,13 +701,12 @@
config.mMatrix[0] = Float.NEGATIVE_INFINITY;
}
- mRestoreStack.push(config);
- return mRestoreStack.size() - 1;
+ mRestoreStack.add(config);
}
public void restore() {
if (mRestoreStack.isEmpty()) throw new IllegalStateException();
- ConfigState config = mRestoreStack.pop();
+ ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1);
config.restore(this);
freeRestoreConfig(config);
}
@@ -886,12 +733,6 @@
public void restore(GLCanvasImpl canvas) {
if (mAlpha >= 0) canvas.setAlpha(mAlpha);
- if (mRect.left != Integer.MAX_VALUE) {
- Rect rect = mRect;
- canvas.mClipRect.set(rect);
- canvas.mGL.glScissor(
- rect.left, rect.top, rect.width(), rect.height());
- }
if (mMatrix[0] != Float.NEGATIVE_INFINITY) {
System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16);
}
diff --git a/src/com/android/gallery3d/ui/GLId.java b/src/com/android/gallery3d/ui/GLId.java
new file mode 100644
index 0000000..689cf19
--- /dev/null
+++ b/src/com/android/gallery3d/ui/GLId.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+// This mimics corresponding GL functions.
+public class GLId {
+ static int sNextId = 1;
+
+ public synchronized static void glGenTextures(int n, int[] textures, int offset) {
+ while (n-- > 0) {
+ textures[offset + n] = sNextId++;
+ }
+ }
+
+ public synchronized static void glGenBuffers(int n, int[] buffers, int offset) {
+ while (n-- > 0) {
+ buffers[offset + n] = sNextId++;
+ }
+ }
+
+ public synchronized static void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
+ gl.glDeleteTextures(n, textures, offset);
+ }
+
+ public synchronized static void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
+ gl.glDeleteBuffers(n, buffers, offset);
+ }
+
+ public synchronized static void glDeleteFramebuffers(
+ GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
+ gl11ep.glDeleteFramebuffersOES(n, buffers, offset);
+ }
+}
diff --git a/src/com/android/gallery3d/ui/GLPaint.java b/src/com/android/gallery3d/ui/GLPaint.java
index 9f7b6f1..eb75cc5 100644
--- a/src/com/android/gallery3d/ui/GLPaint.java
+++ b/src/com/android/gallery3d/ui/GLPaint.java
@@ -20,20 +20,9 @@
public class GLPaint {
- public static final int FLAG_ANTI_ALIAS = 0x01;
-
- private int mFlags = 0;
private float mLineWidth = 1f;
private int mColor = 0;
- public int getFlags() {
- return mFlags;
- }
-
- public void setFlags(int flags) {
- mFlags = flags;
- }
-
public void setColor(int color) {
mColor = color;
}
@@ -50,16 +39,4 @@
public float getLineWidth() {
return mLineWidth;
}
-
- public void setAntiAlias(boolean enabled) {
- if (enabled) {
- mFlags |= FLAG_ANTI_ALIAS;
- } else {
- mFlags &= ~FLAG_ANTI_ALIAS;
- }
- }
-
- public boolean getAntiAlias(){
- return (mFlags & FLAG_ANTI_ALIAS) != 0;
- }
}
diff --git a/src/com/android/gallery3d/ui/GLRoot.java b/src/com/android/gallery3d/ui/GLRoot.java
index 24e5794..fe040ba 100644
--- a/src/com/android/gallery3d/ui/GLRoot.java
+++ b/src/com/android/gallery3d/ui/GLRoot.java
@@ -20,15 +20,17 @@
public interface GLRoot {
+ // Listener will be called when GL is idle AND before each frame.
+ // Mainly used for uploading textures.
public static interface OnGLIdleListener {
- public boolean onGLIdle(GLRoot root, GLCanvas canvas);
+ public boolean onGLIdle(
+ GLCanvas canvas, boolean renderRequested);
}
public void addOnGLIdleListener(OnGLIdleListener listener);
public void registerLaunchedAnimation(CanvasAnimation animation);
public void requestRender();
public void requestLayoutContentPane();
- public boolean hasStencil();
public void lockRenderThread();
public void unlockRenderThread();
diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java
index f2140bf..0268ef9 100644
--- a/src/com/android/gallery3d/ui/GLRootView.java
+++ b/src/com/android/gallery3d/ui/GLRootView.java
@@ -16,11 +16,6 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.anim.CanvasAnimation;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.GalleryUtils;
-
-import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -28,12 +23,17 @@
import android.os.Process;
import android.os.SystemClock;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.view.MotionEvent;
+import com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.Profile;
+
+import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.concurrent.locks.ReentrantLock;
+
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
@@ -59,14 +59,16 @@
private static final boolean DEBUG_DRAWING_STAT = false;
+ private static final boolean DEBUG_PROFILE = false;
+ private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
+
private static final int FLAG_INITIALIZED = 1;
private static final int FLAG_NEED_LAYOUT = 2;
private GL11 mGL;
- private GLCanvasImpl mCanvas;
+ private GLCanvas mCanvas;
private GLView mContentView;
- private DisplayMetrics mDisplayMetrics;
private int mFlags = FLAG_NEED_LAYOUT;
private volatile boolean mRenderRequested = false;
@@ -80,14 +82,13 @@
private final ArrayList<CanvasAnimation> mAnimations =
new ArrayList<CanvasAnimation>();
- private final LinkedList<OnGLIdleListener> mIdleListeners =
- new LinkedList<OnGLIdleListener>();
+ private final ArrayDeque<OnGLIdleListener> mIdleListeners =
+ new ArrayDeque<OnGLIdleListener>();
private final IdleRunner mIdleRunner = new IdleRunner();
private final ReentrantLock mRenderLock = new ReentrantLock();
- private static final int TARGET_FRAME_TIME = 16;
private long mLastDrawFinishTime;
private boolean mInDownState = false;
@@ -107,15 +108,6 @@
//setDebugFlags(DEBUG_CHECK_GL_ERROR);
}
- public GalleryEGLConfigChooser getEGLConfigChooser() {
- return mEglConfigChooser;
- }
-
- @Override
- public boolean hasStencil() {
- return getEGLConfigChooser().getStencilBits() > 0;
- }
-
@Override
public void registerLaunchedAnimation(CanvasAnimation animation) {
// Register the newly launched animation so that we can set the start
@@ -155,10 +147,6 @@
}
}
- public GLView getContentPane() {
- return mContentView;
- }
-
@Override
public void requestRender() {
if (DEBUG_INVALIDATE) {
@@ -219,10 +207,10 @@
}
mGL = gl;
mCanvas = new GLCanvasImpl(gl);
- if (!DEBUG_FPS) {
- setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
- } else {
+ if (DEBUG_FPS || DEBUG_PROFILE) {
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ } else {
+ setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
@@ -237,6 +225,11 @@
+ ", gl10: " + gl1.toString());
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
GalleryUtils.setRenderThread();
+ BasicTexture.invalidateAllTextures();
+ if (DEBUG_PROFILE) {
+ Log.d(TAG, "Start profiling");
+ Profile.enable(20); // take a sample every 20ms
+ }
GL11 gl = (GL11) gl1;
Utils.assertTrue(mGL == gl);
@@ -261,21 +254,33 @@
@Override
public void onDrawFrame(GL10 gl) {
+ AnimationTime.update();
+ long t0;
+ if (DEBUG_PROFILE_SLOW_ONLY) {
+ Profile.hold();
+ t0 = System.nanoTime();
+ }
mRenderLock.lock();
try {
onDrawFrameLocked(gl);
} finally {
mRenderLock.unlock();
}
- long end = SystemClock.uptimeMillis();
- if (mLastDrawFinishTime != 0) {
- long wait = mLastDrawFinishTime + TARGET_FRAME_TIME - end;
- if (wait > 0) {
- SystemClock.sleep(wait);
+ if (DEBUG_PROFILE_SLOW_ONLY) {
+ long t = System.nanoTime();
+ long durationInMs = (t - mLastDrawFinishTime) / 1000000;
+ long durationDrawInMs = (t - t0) / 1000000;
+ mLastDrawFinishTime = t;
+
+ if (durationInMs > 34) { // 34ms -> we skipped at least 2 frames
+ Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
+ durationInMs + ") -----");
+ Profile.commit();
+ } else {
+ Profile.drop();
}
}
- mLastDrawFinishTime = SystemClock.uptimeMillis();
}
private void onDrawFrameLocked(GL10 gl) {
@@ -300,13 +305,12 @@
gl.glScissor(clip.left, clip.top, clip.width(), clip.height());
}
- mCanvas.setCurrentAnimationTimeMillis(SystemClock.uptimeMillis());
if (mContentView != null) {
mContentView.render(mCanvas);
}
if (!mAnimations.isEmpty()) {
- long now = SystemClock.uptimeMillis();
+ long now = AnimationTime.get();
for (int i = 0, n = mAnimations.size(); i < n; i++) {
mAnimations.get(i).setStartTime(now);
}
@@ -318,9 +322,7 @@
}
synchronized (mIdleListeners) {
- if (!mRenderRequested && !mIdleListeners.isEmpty()) {
- mIdleRunner.enable();
- }
+ if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
}
if (DEBUG_INVALIDATE) {
@@ -335,6 +337,7 @@
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
+ AnimationTime.update();
int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_UP) {
@@ -356,19 +359,6 @@
}
}
- public DisplayMetrics getDisplayMetrics() {
- if (mDisplayMetrics == null) {
- mDisplayMetrics = new DisplayMetrics();
- ((Activity) getContext()).getWindowManager()
- .getDefaultDisplay().getMetrics(mDisplayMetrics);
- }
- return mDisplayMetrics;
- }
-
- public GLCanvas getCanvas() {
- return mCanvas;
- }
-
private class IdleRunner implements Runnable {
// true if the idle runner is in the queue
private boolean mActive = false;
@@ -378,19 +368,18 @@
OnGLIdleListener listener;
synchronized (mIdleListeners) {
mActive = false;
- if (mRenderRequested) return;
if (mIdleListeners.isEmpty()) return;
listener = mIdleListeners.removeFirst();
}
mRenderLock.lock();
try {
- if (!listener.onGLIdle(GLRootView.this, mCanvas)) return;
+ if (!listener.onGLIdle(mCanvas, mRenderRequested)) return;
} finally {
mRenderLock.unlock();
}
synchronized (mIdleListeners) {
mIdleListeners.addLast(listener);
- enable();
+ if (!mRenderRequested) enable();
}
}
@@ -411,4 +400,15 @@
public void unlockRenderThread() {
mRenderLock.unlock();
}
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (DEBUG_PROFILE) {
+ Log.d(TAG, "Stop profiling");
+ Profile.disableAll();
+ Profile.dumpToFile("/sdcard/gallery.prof");
+ Profile.reset();
+ }
+ }
}
diff --git a/src/com/android/gallery3d/ui/GLView.java b/src/com/android/gallery3d/ui/GLView.java
index 7491a6f..45471f9 100644
--- a/src/com/android/gallery3d/ui/GLView.java
+++ b/src/com/android/gallery3d/ui/GLView.java
@@ -16,13 +16,13 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.anim.CanvasAnimation;
-import com.android.gallery3d.common.Utils;
-
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.MotionEvent;
+import com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.common.Utils;
+
import java.util.ArrayList;
// GLView is a UI component. It can render to a GLCanvas and accept touch
@@ -229,12 +229,12 @@
int xoffset = component.mBounds.left - mScrollX;
int yoffset = component.mBounds.top - mScrollY;
- canvas.translate(xoffset, yoffset, 0);
+ canvas.translate(xoffset, yoffset);
CanvasAnimation anim = component.mAnimation;
if (anim != null) {
canvas.save(anim.getCanvasSaveFlags());
- if (anim.calculate(canvas.currentAnimationTimeMillis())) {
+ if (anim.calculate(AnimationTime.get())) {
invalidate();
} else {
component.mAnimation = null;
@@ -243,7 +243,7 @@
}
component.render(canvas);
if (anim != null) canvas.restore();
- canvas.translate(-xoffset, -yoffset, 0);
+ canvas.translate(-xoffset, -yoffset);
}
protected boolean onTouch(MotionEvent event) {
@@ -303,14 +303,6 @@
return mPaddings;
}
- public void setPaddings(Rect paddings) {
- mPaddings.set(paddings);
- }
-
- public void setPaddings(int left, int top, int right, int bottom) {
- mPaddings.set(left, top, right, bottom);
- }
-
public void layout(int left, int top, int right, int bottom) {
boolean sizeChanged = setBounds(left, top, right, bottom);
if (sizeChanged) {
diff --git a/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java b/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java
index 1d50d43..0d5643f 100644
--- a/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java
+++ b/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java
@@ -30,7 +30,6 @@
class GalleryEGLConfigChooser implements EGLConfigChooser {
private static final String TAG = "GalleryEGLConfigChooser";
- private int mStencilBits;
private final int mConfigSpec[] = new int[] {
EGL10.EGL_RED_SIZE, 5,
@@ -40,10 +39,6 @@
EGL10.EGL_NONE
};
- public int getStencilBits() {
- return mStencilBits;
- }
-
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] numConfig = new int[1];
if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, numConfig)) {
@@ -94,7 +89,6 @@
if (result == null) result = configs[0];
egl.eglGetConfigAttrib(
display, result, EGL10.EGL_STENCIL_SIZE, value);
- mStencilBits = value[0];
logConfig(egl, display, result);
return result;
}
diff --git a/src/com/android/gallery3d/ui/GestureRecognizer.java b/src/com/android/gallery3d/ui/GestureRecognizer.java
new file mode 100644
index 0000000..4a17d43
--- /dev/null
+++ b/src/com/android/gallery3d/ui/GestureRecognizer.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+// This class aggregates three gesture detectors: GestureDetector,
+// ScaleGestureDetector, and DownUpDetector.
+public class GestureRecognizer {
+ private static final String TAG = "GestureRecognizer";
+
+ public interface Listener {
+ boolean onSingleTapUp(float x, float y);
+ boolean onDoubleTap(float x, float y);
+ boolean onScroll(float dx, float dy);
+ boolean onFling(float velocityX, float velocityY);
+ boolean onScaleBegin(float focusX, float focusY);
+ boolean onScale(float focusX, float focusY, float scale);
+ void onScaleEnd();
+ void onDown();
+ void onUp();
+ }
+
+ private final GestureDetector mGestureDetector;
+ private final ScaleGestureDetector mScaleDetector;
+ private final DownUpDetector mDownUpDetector;
+ private final Listener mListener;
+
+ public GestureRecognizer(Context context, Listener listener) {
+ mListener = listener;
+ mGestureDetector = new GestureDetector(context, new MyGestureListener(),
+ null, true /* ignoreMultitouch */);
+ mScaleDetector = new ScaleGestureDetector(
+ context, new MyScaleListener());
+ mDownUpDetector = new DownUpDetector(new MyDownUpListener());
+ }
+
+ public void onTouchEvent(MotionEvent event) {
+ mGestureDetector.onTouchEvent(event);
+ mScaleDetector.onTouchEvent(event);
+ mDownUpDetector.onTouchEvent(event);
+ }
+
+ public boolean isDown() {
+ return mDownUpDetector.isDown();
+ }
+
+ public void cancelScale() {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent cancelEvent = MotionEvent.obtain(
+ now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ mScaleDetector.onTouchEvent(cancelEvent);
+ cancelEvent.recycle();
+ }
+
+ private class MyGestureListener
+ extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return mListener.onSingleTapUp(e.getX(), e.getY());
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ return mListener.onDoubleTap(e.getX(), e.getY());
+ }
+
+ @Override
+ public boolean onScroll(
+ MotionEvent e1, MotionEvent e2, float dx, float dy) {
+ return mListener.onScroll(dx, dy);
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return mListener.onFling(velocityX, velocityY);
+ }
+ }
+
+ private class MyScaleListener
+ extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return mListener.onScaleBegin(
+ detector.getFocusX(), detector.getFocusY());
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ return mListener.onScale(detector.getFocusX(),
+ detector.getFocusY(), detector.getScaleFactor());
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mListener.onScaleEnd();
+ }
+ }
+
+ private class MyDownUpListener implements DownUpDetector.DownUpListener {
+ @Override
+ public void onDown(MotionEvent e) {
+ mListener.onDown();
+ }
+
+ @Override
+ public void onUp(MotionEvent e) {
+ mListener.onUp();
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/ui/GridDrawer.java b/src/com/android/gallery3d/ui/GridDrawer.java
deleted file mode 100644
index e8e072d..0000000
--- a/src/com/android/gallery3d/ui/GridDrawer.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.data.Path;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.text.Layout;
-
-public class GridDrawer extends IconDrawer {
- private Texture mImportLabel;
- private int mGridWidth;
- private final SelectionManager mSelectionManager;
- private final Context mContext;
- private final int IMPORT_FONT_SIZE = 14;
- private final int IMPORT_FONT_COLOR = Color.WHITE;
- private final int IMPORT_LABEL_MARGIN = 10;
- private boolean mSelectionMode;
-
- public GridDrawer(Context context, SelectionManager selectionManager) {
- super(context);
- mContext = context;
- mSelectionManager = selectionManager;
- }
-
- @Override
- public void prepareDrawing() {
- mSelectionMode = mSelectionManager.inSelectionMode();
- }
-
- @Override
- public void draw(GLCanvas canvas, Texture content, int width,
- int height, int rotation, Path path,
- int dataSourceType, int mediaType, boolean isPanorama,
- int labelBackgroundHeight, boolean wantCache, boolean isCaching) {
-
- int x = -width / 2;
- int y = -height / 2;
-
- drawWithRotation(canvas, content, x, y, width, height, rotation);
-
- if (((rotation / 90) & 0x01) == 1) {
- int temp = width;
- width = height;
- height = temp;
- x = -width / 2;
- y = -height / 2;
- }
-
- drawMediaTypeOverlay(canvas, mediaType, isPanorama, x, y, width, height);
- drawLabelBackground(canvas, width, height, labelBackgroundHeight);
- drawIcon(canvas, width, height, dataSourceType);
- if (dataSourceType == DATASOURCE_TYPE_MTP) {
- drawImportLabel(canvas, width, height);
- }
-
- if (mSelectionManager.isPressedPath(path)) {
- drawPressedFrame(canvas, x, y, width, height);
- } else if (mSelectionMode && mSelectionManager.isItemSelected(path)) {
- drawSelectedFrame(canvas, x, y, width, height);
- }
- }
-
- // Draws the "click to import" label at the center of the frame
- private void drawImportLabel(GLCanvas canvas, int width, int height) {
- if (mImportLabel == null || mGridWidth != width) {
- mGridWidth = width;
- mImportLabel = MultiLineTexture.newInstance(
- mContext.getString(R.string.click_import),
- width - 2 * IMPORT_LABEL_MARGIN,
- IMPORT_FONT_SIZE, IMPORT_FONT_COLOR,
- Layout.Alignment.ALIGN_CENTER);
- }
- int w = mImportLabel.getWidth();
- int h = mImportLabel.getHeight();
- mImportLabel.draw(canvas, -w / 2, -h / 2);
- }
-
- @Override
- public void drawFocus(GLCanvas canvas, int width, int height) {
- }
-}
diff --git a/src/com/android/gallery3d/ui/HighlightDrawer.java b/src/com/android/gallery3d/ui/HighlightDrawer.java
deleted file mode 100644
index f6a4695..0000000
--- a/src/com/android/gallery3d/ui/HighlightDrawer.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.gallery3d.ui;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.data.Path;
-
-import android.content.Context;
-
-public class HighlightDrawer extends IconDrawer {
- private SelectionManager mSelectionManager;
- private Path mHighlightItem;
-
- public HighlightDrawer(Context context, SelectionManager selectionManager) {
- super(context);
- mSelectionManager = selectionManager;
- }
-
- public void setHighlightItem(Path item) {
- mHighlightItem = item;
- }
-
- @Override
- public void draw(GLCanvas canvas, Texture content, int width,
- int height, int rotation, Path path,
- int dataSourceType, int mediaType, boolean isPanorama,
- int labelBackgroundHeight, boolean wantCache, boolean isCaching) {
- int x = -width / 2;
- int y = -height / 2;
-
- drawWithRotation(canvas, content, x, y, width, height, rotation);
-
- if (((rotation / 90) & 0x01) == 1) {
- int temp = width;
- width = height;
- height = temp;
- x = -width / 2;
- y = -height / 2;
- }
-
- drawMediaTypeOverlay(canvas, mediaType, isPanorama, x, y, width, height);
- drawLabelBackground(canvas, width, height, labelBackgroundHeight);
- drawIcon(canvas, width, height, dataSourceType);
-
- if (mSelectionManager.isPressedPath(path)) {
- drawPressedFrame(canvas, x, y, width, height);
- } else if (path == mHighlightItem) {
- drawSelectedFrame(canvas, x, y, width, height);
- }
- }
-}
diff --git a/src/com/android/gallery3d/ui/Icon.java b/src/com/android/gallery3d/ui/Icon.java
deleted file mode 100644
index c710859..0000000
--- a/src/com/android/gallery3d/ui/Icon.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import android.content.Context;
-import android.graphics.Rect;
-
-public class Icon extends GLView {
- private final BasicTexture mIcon;
-
- // The width and height requested by the user.
- private int mReqWidth;
- private int mReqHeight;
-
- public Icon(Context context, int iconId, int width, int height) {
- this(context, new ResourceTexture(context, iconId), width, height);
- }
-
- public Icon(Context context, BasicTexture icon, int width, int height) {
- mIcon = icon;
- mReqWidth = width;
- mReqHeight = height;
- }
-
- @Override
- protected void onMeasure(int widthSpec, int heightSpec) {
- MeasureHelper.getInstance(this)
- .setPreferredContentSize(mReqWidth, mReqHeight)
- .measure(widthSpec, heightSpec);
- }
-
- @Override
- protected void render(GLCanvas canvas) {
- Rect p = mPaddings;
-
- int width = getWidth() - p.left - p.right;
- int height = getHeight() - p.top - p.bottom;
-
- // Draw the icon in the center of the space
- int xoffset = p.left + (width - mReqWidth) / 2;
- int yoffset = p.top + (height - mReqHeight) / 2;
-
- mIcon.draw(canvas, xoffset, yoffset, mReqWidth, mReqHeight);
- }
-}
diff --git a/src/com/android/gallery3d/ui/IconDrawer.java b/src/com/android/gallery3d/ui/IconDrawer.java
deleted file mode 100644
index 25440bc..0000000
--- a/src/com/android/gallery3d/ui/IconDrawer.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.gallery3d.ui;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.data.MediaObject;
-
-import android.content.Context;
-
-public abstract class IconDrawer extends SelectionDrawer {
- private static final String TAG = "IconDrawer";
- private static final int LABEL_BACKGROUND_COLOR = 0x99000000; // 60% black
-
- private final ResourceTexture mLocalSetIcon;
- private final ResourceTexture mCameraIcon;
- private final ResourceTexture mPicasaIcon;
- private final ResourceTexture mMtpIcon;
- private final NinePatchTexture mFramePressed;
- private final NinePatchTexture mFrameSelected;
- private final NinePatchTexture mDarkStrip;
- private final NinePatchTexture mPanoramaBorder;
- private final Texture mVideoOverlay;
- private final Texture mVideoPlayIcon;
- private final int mIconSize;
-
- public static class IconDimension {
- int x;
- int y;
- int width;
- int height;
- }
-
- public IconDrawer(Context context) {
- mLocalSetIcon = new ResourceTexture(context, R.drawable.frame_overlay_gallery_folder);
- mCameraIcon = new ResourceTexture(context, R.drawable.frame_overlay_gallery_camera);
- mPicasaIcon = new ResourceTexture(context, R.drawable.frame_overlay_gallery_picasa);
- mMtpIcon = new ResourceTexture(context, R.drawable.frame_overlay_gallery_ptp);
- mVideoOverlay = new ResourceTexture(context, R.drawable.ic_video_thumb);
- mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_gallery_play);
- mPanoramaBorder = new NinePatchTexture(context, R.drawable.ic_pan_thumb);
- mFramePressed = new NinePatchTexture(context, R.drawable.grid_pressed);
- mFrameSelected = new NinePatchTexture(context, R.drawable.grid_selected);
- mDarkStrip = new NinePatchTexture(context, R.drawable.dark_strip);
- mIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.albumset_icon_size);
- }
-
- @Override
- public void prepareDrawing() {
- }
-
- protected IconDimension drawIcon(GLCanvas canvas, int width, int height,
- int dataSourceType) {
- ResourceTexture icon = getIcon(dataSourceType);
-
- if (icon != null) {
- IconDimension id = getIconDimension(icon, width, height);
- icon.draw(canvas, id.x, id.y, id.width, id.height);
- return id;
- }
- return null;
- }
-
- protected ResourceTexture getIcon(int dataSourceType) {
- ResourceTexture icon = null;
- switch (dataSourceType) {
- case DATASOURCE_TYPE_LOCAL:
- icon = mLocalSetIcon;
- break;
- case DATASOURCE_TYPE_PICASA:
- icon = mPicasaIcon;
- break;
- case DATASOURCE_TYPE_CAMERA:
- icon = mCameraIcon;
- break;
- case DATASOURCE_TYPE_MTP:
- icon = mMtpIcon;
- break;
- default:
- break;
- }
-
- return icon;
- }
-
- protected IconDimension getIconDimension(ResourceTexture icon, int width,
- int height) {
- IconDimension id = new IconDimension();
- float scale = (float) mIconSize / icon.getWidth();
- id.width = Math.round(scale * icon.getWidth());
- id.height = Math.round(scale * icon.getHeight());
- id.x = -width / 2;
- id.y = (height + 1) / 2 - id.height;
- return id;
- }
-
- protected void drawMediaTypeOverlay(GLCanvas canvas, int mediaType,
- boolean isPanorama, int x, int y, int width, int height) {
- if (mediaType == MediaObject.MEDIA_TYPE_VIDEO) {
- drawVideoOverlay(canvas, x, y, width, height);
- }
- if (isPanorama) {
- drawPanoramaBorder(canvas, x, y, width, height);
- }
- }
-
- protected void drawVideoOverlay(GLCanvas canvas, int x, int y,
- int width, int height) {
- // Scale the video overlay to the height of the thumbnail and put it
- // on the left side.
- float scale = (float) height / mVideoOverlay.getHeight();
- int w = Math.round(scale * mVideoOverlay.getWidth());
- int h = Math.round(scale * mVideoOverlay.getHeight());
- mVideoOverlay.draw(canvas, x, y, w, h);
-
- int side = Math.min(width, height) / 6;
- mVideoPlayIcon.draw(canvas, -side / 2, -side / 2, side, side);
- }
-
- protected void drawPanoramaBorder(GLCanvas canvas, int x, int y,
- int width, int height) {
- float scale = (float) width / mPanoramaBorder.getWidth();
- int w = Math.round(scale * mPanoramaBorder.getWidth());
- int h = Math.round(scale * mPanoramaBorder.getHeight());
- // draw at the top
- mPanoramaBorder.draw(canvas, x, y, w, h);
- // draw at the bottom
- mPanoramaBorder.draw(canvas, x, y + width - h, w, h);
- }
-
- protected void drawLabelBackground(GLCanvas canvas, int width, int height,
- int drawLabelBackground) {
- int x = -width / 2;
- int y = (height + 1) / 2 - drawLabelBackground;
- drawFrame(canvas, mDarkStrip, x, y, width, drawLabelBackground);
- }
-
- protected void drawPressedFrame(GLCanvas canvas, int x, int y, int width,
- int height) {
- drawFrame(canvas, mFramePressed, x, y, width, height);
- }
-
- protected void drawSelectedFrame(GLCanvas canvas, int x, int y, int width,
- int height) {
- drawFrame(canvas, mFrameSelected, x, y, width, height);
- }
-
- @Override
- public void drawFocus(GLCanvas canvas, int width, int height) {
- }
-}
diff --git a/src/com/android/gallery3d/ui/ImportCompleteListener.java b/src/com/android/gallery3d/ui/ImportCompleteListener.java
index 5c52ea1..2450881 100644
--- a/src/com/android/gallery3d/ui/ImportCompleteListener.java
+++ b/src/com/android/gallery3d/ui/ImportCompleteListener.java
@@ -16,16 +16,14 @@
package com.android.gallery3d.ui;
+import android.os.Bundle;
+import android.widget.Toast;
+
import com.android.gallery3d.R;
import com.android.gallery3d.app.AlbumPage;
import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.data.Path;
import com.android.gallery3d.util.MediaSetUtils;
-import android.content.Context;
-import android.os.Bundle;
-import android.widget.Toast;
-
public class ImportCompleteListener implements MenuExecutor.ProgressListener {
private GalleryActivity mActivity;
diff --git a/src/com/android/gallery3d/ui/ManageCacheDrawer.java b/src/com/android/gallery3d/ui/ManageCacheDrawer.java
index e75fe9a..ba31fc6 100644
--- a/src/com/android/gallery3d/ui/ManageCacheDrawer.java
+++ b/src/com/android/gallery3d/ui/ManageCacheDrawer.java
@@ -16,13 +16,16 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.R;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.Path;
-
import android.content.Context;
-public class ManageCacheDrawer extends IconDrawer {
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.GalleryActivity;
+import com.android.gallery3d.data.DataSourceType;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.ui.AlbumSetSlidingWindow.AlbumSetEntry;
+
+public class ManageCacheDrawer extends AlbumSetSlotRenderer {
private final ResourceTexture mCheckedItem;
private final ResourceTexture mUnCheckedItem;
private final SelectionManager mSelectionManager;
@@ -33,12 +36,16 @@
private final int mCachePinSize;
private final int mCachePinMargin;
- public ManageCacheDrawer(Context context, SelectionManager selectionManager,
- int cachePinSize, int cachePinMargin) {
- super(context);
- mCheckedItem = new ResourceTexture(context, R.drawable.btn_make_offline_normal_on_holo_dark);
- mUnCheckedItem = new ResourceTexture(context, R.drawable.btn_make_offline_normal_off_holo_dark);
- mLocalAlbumIcon = new ResourceTexture(context, R.drawable.btn_make_offline_disabled_on_holo_dark);
+ public ManageCacheDrawer(GalleryActivity activity, SelectionManager selectionManager,
+ SlotView slotView, LabelSpec labelSpec, int cachePinSize, int cachePinMargin) {
+ super(activity, selectionManager, slotView, labelSpec);
+ Context context = (Context) activity;
+ mCheckedItem = new ResourceTexture(
+ context, R.drawable.btn_make_offline_normal_on_holo_dark);
+ mUnCheckedItem = new ResourceTexture(
+ context, R.drawable.btn_make_offline_normal_off_holo_dark);
+ mLocalAlbumIcon = new ResourceTexture(
+ context, R.drawable.btn_make_offline_disabled_on_holo_dark);
String cachingLabel = context.getString(R.string.caching_label);
mCachingText = StringTexture.newInstance(cachingLabel, 12, 0xffffffff);
mSelectionManager = selectionManager;
@@ -46,61 +53,42 @@
mCachePinMargin = cachePinMargin;
}
- @Override
- public void prepareDrawing() {
- }
-
private static boolean isLocal(int dataSourceType) {
- return dataSourceType != DATASOURCE_TYPE_PICASA;
+ return dataSourceType != DataSourceType.TYPE_PICASA;
}
@Override
- public void draw(GLCanvas canvas, Texture content, int width,
- int height, int rotation, Path path,
- int dataSourceType, int mediaType, boolean isPanorama,
- int labelBackgroundHeight, boolean wantCache, boolean isCaching) {
+ public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) {
+ AlbumSetEntry entry = mDataWindow.get(index);
- boolean selected = mSelectionManager.isItemSelected(path);
+ boolean wantCache = entry.cacheFlag == MediaSet.CACHE_FLAG_FULL;
+ boolean isCaching = wantCache && (
+ entry.cacheStatus != MediaSet.CACHE_STATUS_CACHED_FULL);
+ boolean selected = mSelectionManager.isItemSelected(entry.setPath);
boolean chooseToCache = wantCache ^ selected;
- boolean available = isLocal(dataSourceType) || chooseToCache;
+ boolean available = isLocal(entry.sourceType) || chooseToCache;
- int x = -width / 2;
- int y = -height / 2;
+ int renderRequestFlags = 0;
if (!available) {
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
canvas.multiplyAlpha(0.6f);
}
+ renderRequestFlags |= renderContent(canvas, entry, width, height);
+ if (!available) canvas.restore();
- drawWithRotation(canvas, content, x, y, width, height, rotation);
+ renderRequestFlags |= renderLabel(canvas, entry, width, height);
- if (!available) {
- canvas.restore();
- }
+ drawCachingPin(canvas, entry.setPath,
+ entry.sourceType, isCaching, chooseToCache, width, height);
- if (((rotation / 90) & 0x01) == 1) {
- int temp = width;
- width = height;
- height = temp;
- x = -width / 2;
- y = -height / 2;
- }
-
- drawMediaTypeOverlay(canvas, mediaType, isPanorama, x, y, width, height);
- drawLabelBackground(canvas, width, height, labelBackgroundHeight);
- drawIcon(canvas, width, height, dataSourceType);
- drawCachingPin(canvas, path, dataSourceType, isCaching, chooseToCache,
- width, height);
-
- if (mSelectionManager.isPressedPath(path)) {
- drawPressedFrame(canvas, x, y, width, height);
- }
+ renderRequestFlags |= renderOverlay(canvas, index, entry, width, height);
+ return renderRequestFlags;
}
private void drawCachingPin(GLCanvas canvas, Path path, int dataSourceType,
boolean isCaching, boolean chooseToCache, int width, int height) {
-
- ResourceTexture icon = null;
+ ResourceTexture icon;
if (isLocal(dataSourceType)) {
icon = mLocalAlbumIcon;
} else if (chooseToCache) {
@@ -109,26 +97,16 @@
icon = mUnCheckedItem;
}
- int w = mCachePinSize;
- int h = mCachePinSize;
- int right = (width + 1) / 2;
- int bottom = (height + 1) / 2;
- int x = right - w - mCachePinMargin;
- int y = bottom - h - mCachePinMargin;
-
- icon.draw(canvas, x, y, w, h);
+ // show the icon in right bottom
+ int s = mCachePinSize;
+ int m = mCachePinMargin;
+ icon.draw(canvas, width - m - s, height - s, s, s);
if (isCaching) {
- int textWidth = mCachingText.getWidth();
- int textHeight = mCachingText.getHeight();
- // Align the center of the text to the center of the pin icon
- x = right - mCachePinMargin - (textWidth + mCachePinSize) / 2;
- y = bottom - textHeight;
- mCachingText.draw(canvas, x, y);
+ int w = mCachingText.getWidth();
+ int h = mCachingText.getHeight();
+ // Show the caching text in bottom center
+ mCachingText.draw(canvas, (width - w) / 2, height - h);
}
}
-
- @Override
- public void drawFocus(GLCanvas canvas, int width, int height) {
- }
}
diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java
index 918feea..a0f3449 100644
--- a/src/com/android/gallery3d/ui/MenuExecutor.java
+++ b/src/com/android/gallery3d/ui/MenuExecutor.java
@@ -17,8 +17,11 @@
package com.android.gallery3d.ui;
import android.app.Activity;
+import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
@@ -172,10 +175,9 @@
return ids.get(0);
}
- public boolean onMenuClicked(MenuItem menuItem, ProgressListener listener) {
+ private void onMenuClicked(int action, ProgressListener listener) {
int title;
DataManager manager = mActivity.getDataManager();
- int action = menuItem.getItemId();
switch (action) {
case R.id.action_select_all:
if (mSelectionManager.inSelectAllMode()) {
@@ -183,14 +185,12 @@
} else {
mSelectionManager.selectAll();
}
- return true;
case R.id.action_crop: {
Path path = getSingleSelectedPath();
String mimeType = getMimeType(manager.getMediaType(path));
Intent intent = new Intent(CropImage.ACTION_CROP)
.setDataAndType(manager.getContentUri(path), mimeType);
((Activity) mActivity).startActivity(intent);
- return true;
}
case R.id.action_setas: {
Path path = getSingleSelectedPath();
@@ -203,9 +203,8 @@
Activity activity = (Activity) mActivity;
activity.startActivity(Intent.createChooser(
intent, activity.getString(R.string.set_as)));
- return true;
}
- case R.id.action_confirm_delete:
+ case R.id.action_delete:
title = R.string.delete;
break;
case R.id.action_rotate_cw:
@@ -224,10 +223,27 @@
title = R.string.Import;
break;
default:
- return false;
+ return;
}
startAction(action, title, listener);
- return true;
+ }
+
+ public void onMenuClicked(MenuItem menuItem, boolean needsConfirm,
+ final ProgressListener listener) {
+ final int action = menuItem.getItemId();
+
+ if (needsConfirm) {
+ new AlertDialog.Builder(mActivity.getAndroidContext())
+ .setMessage(R.string.confirm_action)
+ .setPositiveButton(R.string.confirm, new OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ onMenuClicked(action, listener);
+ }
+ })
+ .setNegativeButton(R.string.cancel, null).create().show();
+ } else {
+ onMenuClicked(action, listener);
+ }
}
public void startAction(int action, int title, ProgressListener listener) {
@@ -257,7 +273,7 @@
long startTime = System.currentTimeMillis();
switch (cmd) {
- case R.id.action_confirm_delete:
+ case R.id.action_delete:
manager.delete(path);
break;
case R.id.action_rotate_cw:
diff --git a/src/com/android/gallery3d/ui/NinePatchTexture.java b/src/com/android/gallery3d/ui/NinePatchTexture.java
index 15b057a..957229e 100644
--- a/src/com/android/gallery3d/ui/NinePatchTexture.java
+++ b/src/com/android/gallery3d/ui/NinePatchTexture.java
@@ -16,18 +16,17 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
-
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
+import com.android.gallery3d.common.Utils;
+
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
-import java.util.LinkedHashMap;
-import java.util.Map;
+
import javax.microedition.khronos.opengles.GL11;
// NinePatchTexture is a texture backed by a NinePatch resource.
@@ -39,8 +38,8 @@
@SuppressWarnings("unused")
private static final String TAG = "NinePatchTexture";
private NinePatchChunk mChunk;
- private MyCacheMap<Long, NinePatchInstance> mInstanceCache =
- new MyCacheMap<Long, NinePatchInstance>();
+ private SmallCache<NinePatchInstance> mInstanceCache
+ = new SmallCache<NinePatchInstance>();
public NinePatchTexture(Context context, int resId) {
super(context, resId);
@@ -77,39 +76,77 @@
return mChunk;
}
- private static class MyCacheMap<K, V> extends LinkedHashMap<K, V> {
- private int CACHE_SIZE = 16;
- private V mJustRemoved;
+ // This is a simple cache for a small number of things. Linear search
+ // is used because the cache is small. It also tries to remove less used
+ // item when the cache is full by moving the often-used items to the front.
+ private static class SmallCache<V> {
+ private static final int CACHE_SIZE = 16;
+ private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
+ private int[] mKey = new int[CACHE_SIZE];
+ private V[] mValue = (V[]) new Object[CACHE_SIZE];
+ private int mCount; // number of items in this cache
- public MyCacheMap() {
- super(4, 0.75f, true);
- }
-
- @Override
- protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
- if (size() > CACHE_SIZE) {
- mJustRemoved = eldest.getValue();
- return true;
+ // Puts a value into the cache. If the cache is full, also returns
+ // a less used item, otherwise returns null.
+ public V put(int key, V value) {
+ if (mCount == CACHE_SIZE) {
+ V old = mValue[CACHE_SIZE - 1]; // remove the last item
+ mKey[CACHE_SIZE - 1] = key;
+ mValue[CACHE_SIZE - 1] = value;
+ return old;
+ } else {
+ mKey[mCount] = key;
+ mValue[mCount] = value;
+ mCount++;
+ return null;
}
- return false;
}
- public V getJustRemoved() {
- V result = mJustRemoved;
- mJustRemoved = null;
- return result;
+ public V get(int key) {
+ for (int i = 0; i < mCount; i++) {
+ if (mKey[i] == key) {
+ // Move the accessed item one position to the front, so it
+ // will less likely to be removed when cache is full. Only
+ // do this if the cache is starting to get full.
+ if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
+ int tmpKey = mKey[i];
+ mKey[i] = mKey[i - 1];
+ mKey[i - 1] = tmpKey;
+
+ V tmpValue = mValue[i];
+ mValue[i] = mValue[i - 1];
+ mValue[i - 1] = tmpValue;
+ }
+ return mValue[i];
+ }
+ }
+ return null;
+ }
+
+ public void clear() {
+ for (int i = 0; i < mCount; i++) {
+ mValue[i] = null; // make sure it's can be garbage-collected.
+ }
+ mCount = 0;
+ }
+
+ public int size() {
+ return mCount;
+ }
+
+ public V valueAt(int i) {
+ return mValue[i];
}
}
private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
- long key = w;
- key = (key << 32) | h;
+ int key = w;
+ key = (key << 16) | h;
NinePatchInstance instance = mInstanceCache.get(key);
if (instance == null) {
instance = new NinePatchInstance(this, w, h);
- mInstanceCache.put(key, instance);
- NinePatchInstance removed = mInstanceCache.getJustRemoved();
+ NinePatchInstance removed = mInstanceCache.put(key, instance);
if (removed != null) {
removed.recycle(canvas);
}
@@ -132,9 +169,11 @@
@Override
public void recycle() {
super.recycle();
- GLCanvas canvas = mCanvasRef == null ? null : mCanvasRef.get();
+ GLCanvas canvas = mCanvasRef;
if (canvas == null) return;
- for (NinePatchInstance instance : mInstanceCache.values()) {
+ int n = mInstanceCache.size();
+ for (int i = 0; i < n; i++) {
+ NinePatchInstance instance = mInstanceCache.valueAt(i);
instance.recycle(canvas);
}
mInstanceCache.clear();
@@ -359,7 +398,7 @@
private void prepareBuffers(GLCanvas canvas) {
mBufferNames = new int[3];
GL11 gl = canvas.getGLInstance();
- gl.glGenBuffers(3, mBufferNames, 0);
+ GLId.glGenBuffers(3, mBufferNames, 0);
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBufferNames[0]);
gl.glBufferData(GL11.GL_ARRAY_BUFFER,
diff --git a/src/com/android/gallery3d/ui/OnSelectedListener.java b/src/com/android/gallery3d/ui/OnSelectedListener.java
deleted file mode 100644
index 2cc5809..0000000
--- a/src/com/android/gallery3d/ui/OnSelectedListener.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-public interface OnSelectedListener {
- public void onSelected(GLView source);
-}
diff --git a/src/com/android/gallery3d/ui/Paper.java b/src/com/android/gallery3d/ui/Paper.java
index ecc4150..3b67a04 100644
--- a/src/com/android/gallery3d/ui/Paper.java
+++ b/src/com/android/gallery3d/ui/Paper.java
@@ -16,17 +16,12 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.ui.PositionRepository.Position;
-import com.android.gallery3d.util.GalleryUtils;
-
+import android.graphics.Rect;
import android.opengl.Matrix;
-import android.os.SystemClock;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
-import javax.microedition.khronos.opengles.GL11;
-import javax.microedition.khronos.opengles.GL11ExtensionPack;
+import com.android.gallery3d.common.Utils;
// This class does the overscroll effect.
class Paper {
@@ -70,11 +65,10 @@
mHeight = height;
}
- public float[] getTransform(Position target, Position base,
- float scrollX, float scrollY) {
+ public float[] getTransform(Rect rect, float scrollX) {
float left = mAnimationLeft.getValue();
float right = mAnimationRight.getValue();
- float screenX = target.x - scrollX;
+ float screenX = rect.centerX() - scrollX;
// We linearly interpolate the value [left, right] for the screenX
// range int [-1/4, 5/4]*mWidth. So if part of the thumbnail is outside
// the screen, we still get some transform.
@@ -87,10 +81,9 @@
float degrees =
(1 / (1 + (float) Math.exp(-t * ROTATE_FACTOR)) - 0.5f) * 2 * -45;
Matrix.setIdentityM(mMatrix, 0);
- Matrix.translateM(mMatrix, 0, mMatrix, 0, base.x, base.y, base.z);
+ Matrix.translateM(mMatrix, 0, mMatrix, 0, rect.centerX(), rect.centerY(), 0);
Matrix.rotateM(mMatrix, 0, degrees, 0, 1, 0);
- Matrix.translateM(mMatrix, 0, mMatrix, 0,
- target.x - base.x, target.y - base.y, target.z - base.z);
+ Matrix.translateM(mMatrix, 0, mMatrix, 0, -rect.width() / 2, -rect.height() / 2, 0);
return mMatrix;
}
}
@@ -113,7 +106,6 @@
private final Interpolator mInterpolator;
private int mState;
- private long mAnimationStartTime;
private float mValue;
private float mValueStart;
@@ -185,6 +177,6 @@
}
private long now() {
- return SystemClock.uptimeMillis();
+ return AnimationTime.get();
}
}
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index 217a290..66941be 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -16,12 +16,6 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.ui.PositionRepository.Position;
-
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -29,47 +23,77 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Message;
-import android.os.SystemClock;
-import android.view.GestureDetector;
import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
import android.view.animation.AccelerateInterpolator;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.GalleryActivity;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.util.RangeArray;
+import com.android.gallery3d.util.RangeBoolArray;
+
+import java.util.Arrays;
+
public class PhotoView extends GLView {
@SuppressWarnings("unused")
private static final String TAG = "PhotoView";
+ private static final int PLACEHOLDER_COLOR = 0xFF222222;
public static final int INVALID_SIZE = -1;
+ public static final long INVALID_DATA_VERSION =
+ MediaObject.INVALID_DATA_VERSION;
- private static final int MSG_TRANSITION_COMPLETE = 1;
- private static final int MSG_SHOW_LOADING = 2;
- private static final int MSG_CANCEL_EXTRA_SCALING = 3;
+ public static class Size {
+ public int width;
+ public int height;
+ }
+
+ public interface Model extends TileImageView.Model {
+ public void next();
+ public void previous();
+
+ // Returns the size for the specified picture. If the size information is
+ // not avaiable, width = height = 0.
+ public void getImageSize(int offset, Size size);
+
+ // Returns the rotation for the specified picture.
+ public int getImageRotation(int offset);
+
+ // This amends the getScreenNail() method of TileImageView.Model to get
+ // ScreenNail at previous (negative offset) or next (positive offset)
+ // positions. Returns null if the specified ScreenNail is unavailable.
+ public ScreenNail getScreenNail(int offset);
+
+ // Set this to true if we need the model to provide full images.
+ public void setNeedFullImage(boolean enabled);
+ }
+
+ public interface PhotoTapListener {
+ public void onSingleTapUp(int x, int y);
+ }
+
+ private static final int MSG_SHOW_LOADING = 1;
+ private static final int MSG_CANCEL_EXTRA_SCALING = 2;
+ private static final int MSG_SWITCH_FOCUS = 3;
+ private static final int MSG_CAPTURE_ANIMATION_DONE = 4;
private static final long DELAY_SHOW_LOADING = 250; // 250ms;
- private static final int TRANS_NONE = 0;
- private static final int TRANS_SWITCH_NEXT = 3;
- private static final int TRANS_SWITCH_PREVIOUS = 4;
-
- public static final int TRANS_SLIDE_IN_RIGHT = 1;
- public static final int TRANS_SLIDE_IN_LEFT = 2;
- public static final int TRANS_OPEN_ANIMATION = 5;
-
private static final int LOADING_INIT = 0;
private static final int LOADING_TIMEOUT = 1;
private static final int LOADING_COMPLETE = 2;
private static final int LOADING_FAIL = 3;
- private static final int ENTRY_PREVIOUS = 0;
- private static final int ENTRY_NEXT = 1;
-
- private static final int IMAGE_GAP = 96;
- private static final int SWITCH_THRESHOLD = 256;
+ private static final int MOVE_THRESHOLD = 256;
private static final float SWIPE_THRESHOLD = 300f;
private static final float DEFAULT_TEXT_SIZE = 20;
private static float TRANSITION_SCALE_FACTOR = 0.74f;
+ // whether we want to apply card deck effect in page mode.
+ private static final boolean CARD_EFFECT = true;
+
// Used to calculate the scaling factor for the fading animation.
private ZInterpolator mScaleInterpolator = new ZInterpolator(0.5f);
@@ -77,16 +101,18 @@
private AccelerateInterpolator mAlphaInterpolator =
new AccelerateInterpolator(0.9f);
- public interface PhotoTapListener {
- public void onSingleTapUp(int x, int y);
- }
+ // We keep this many previous ScreenNails. (also this many next ScreenNails)
+ public static final int SCREEN_NAIL_MAX = 3;
- // the previous/next image entries
- private final ScreenNailEntry mScreenNails[] = new ScreenNailEntry[2];
+ // The picture entries, the valid index is from -SCREEN_NAIL_MAX to
+ // SCREEN_NAIL_MAX.
+ private final RangeArray<Picture> mPictures =
+ new RangeArray<Picture>(-SCREEN_NAIL_MAX, SCREEN_NAIL_MAX);
- private final ScaleGestureDetector mScaleDetector;
- private final GestureDetector mGestureDetector;
- private final DownUpDetector mDownUpDetector;
+ private final long mDataVersion[] = new long[2 * SCREEN_NAIL_MAX + 1];
+ private final int mFromIndex[] = new int[2 * SCREEN_NAIL_MAX + 1];
+
+ private final GestureRecognizer mGestureRecognizer;
private PhotoTapListener mPhotoTapListener;
@@ -95,8 +121,7 @@
private Model mModel;
private StringTexture mLoadingText;
private StringTexture mNoThumbnailText;
- private int mTransitionMode = TRANS_NONE;
- private final TileImageView mTileView;
+ private TileImageView mTileView;
private EdgeView mEdgeView;
private Texture mVideoPlayIcon;
@@ -107,15 +132,28 @@
private int mLoadingState = LOADING_COMPLETE;
- private int mImageRotation;
-
- private Path mOpenedItemPath;
- private GalleryActivity mActivity;
private Point mImageCenter = new Point();
private boolean mCancelExtraScalingPending;
+ private boolean mFilmMode = false;
+
+ // [mPrevBound, mNextBound] is the range of index for all pictures in the
+ // model, if we assume the index of current focused picture is 0. So if
+ // there are some previous pictures, mPrevBound < 0, and if there are some
+ // next pictures, mNextBound > 0.
+ private int mPrevBound;
+ private int mNextBound;
+
+ // This variable prevents us doing snapback until its values goes to 0. This
+ // happens if the user gesture is still in progress or we are in a capture
+ // animation.
+ // HOLD_TOUCH_DOWN_FROM_CAMERA is an extra flag set together with
+ // HOLD_TOUCH_DOWN if the touch down starts from camera preview.
+ private int mHolding;
+ private static final int HOLD_TOUCH_DOWN = 1;
+ private static final int HOLD_TOUCH_DOWN_FROM_CAMERA = 2;
+ private static final int HOLD_CAPTURE_ANIMATION = 4;
public PhotoView(GalleryActivity activity) {
- mActivity = activity;
mTileView = new TileImageView(activity);
addComponent(mTileView);
Context context = activity.getAndroidContext();
@@ -129,154 +167,88 @@
context.getString(R.string.no_thumbnail),
DEFAULT_TEXT_SIZE, Color.WHITE);
- mHandler = new SynchronizedHandler(activity.getGLRoot()) {
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_TRANSITION_COMPLETE: {
- onTransitionComplete();
- break;
- }
- case MSG_SHOW_LOADING: {
- if (mLoadingState == LOADING_INIT) {
- // We don't need the opening animation
- mOpenedItemPath = null;
+ mHandler = new MyHandler(activity.getGLRoot());
- mLoadingSpinner.startAnimation();
- mLoadingState = LOADING_TIMEOUT;
- invalidate();
+ mGestureRecognizer = new GestureRecognizer(
+ context, new MyGestureListener());
+
+ mPositionController = new PositionController(context,
+ new PositionController.Listener() {
+ public void invalidate() {
+ PhotoView.this.invalidate();
+ }
+ public boolean isHolding() {
+ // We want the film mode change happen as soon as
+ // possible even if the touch is still down.
+ if ((mHolding & HOLD_TOUCH_DOWN_FROM_CAMERA) != 0) {
+ return false;
+ } else {
+ return mHolding != 0;
}
- break;
}
- case MSG_CANCEL_EXTRA_SCALING: {
- cancelScaleGesture();
- mPositionController.setExtraScalingRange(false);
- mCancelExtraScalingPending = false;
- break;
+ public void onPull(int offset, int direction) {
+ mEdgeView.onPull(offset, direction);
}
- default: throw new AssertionError(message.what);
- }
- }
- };
-
- mGestureDetector = new GestureDetector(context,
- new MyGestureListener(), null, true /* ignoreMultitouch */);
- mScaleDetector = new ScaleGestureDetector(context, new MyScaleListener());
- mDownUpDetector = new DownUpDetector(new MyDownUpListener());
-
- for (int i = 0, n = mScreenNails.length; i < n; ++i) {
- mScreenNails[i] = new ScreenNailEntry();
- }
-
- mPositionController = new PositionController(this, context, mEdgeView);
+ public void onRelease() {
+ mEdgeView.onRelease();
+ }
+ public void onAbsorb(int velocity, int direction) {
+ mEdgeView.onAbsorb(velocity, direction);
+ }
+ });
mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_control_play);
+ Arrays.fill(mDataVersion, INVALID_DATA_VERSION);
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
+ if (i == 0) {
+ mPictures.put(i, new FullPicture());
+ } else {
+ mPictures.put(i, new ScreenNailPicture(i));
+ }
+ }
}
-
public void setModel(Model model) {
- if (mModel == model) return;
mModel = model;
- mTileView.setModel(model);
- if (model != null) notifyOnNewImage();
+ mTileView.setModel(mModel);
}
- public void setPhotoTapListener(PhotoTapListener listener) {
- mPhotoTapListener = listener;
- }
-
- private void setTileViewPosition(int centerX, int centerY, float scale) {
- TileImageView t = mTileView;
-
- // Calculate the move-out progress value.
- RectF bounds = mPositionController.getImageBounds();
- int left = Math.round(bounds.left);
- int right = Math.round(bounds.right);
- int width = getWidth();
- float progress = calculateMoveOutProgress(left, right, width);
- progress = Utils.clamp(progress, -1f, 1f);
-
- // We only want to apply the fading animation if the scrolling movement
- // is to the right.
- if (progress < 0) {
- if (right - left < width) {
- // If the picture is narrower than the view, keep it at the center
- // of the view.
- centerX = mPositionController.getImageWidth() / 2;
- } else {
- // If the picture is wider than the view (it's zoomed-in), keep
- // the left edge of the object align the the left edge of the view.
- centerX = Math.round(width / 2f / scale);
- }
- scale *= getScrollScale(progress);
- t.setAlpha(getScrollAlpha(progress));
+ class MyHandler extends SynchronizedHandler {
+ public MyHandler(GLRoot root) {
+ super(root);
}
- // set the position of the tile view
- int inverseX = mPositionController.getImageWidth() - centerX;
- int inverseY = mPositionController.getImageHeight() - centerY;
- int rotation = mImageRotation;
- switch (rotation) {
- case 0: t.setPosition(centerX, centerY, scale, 0); break;
- case 90: t.setPosition(centerY, inverseX, scale, 90); break;
- case 180: t.setPosition(inverseX, inverseY, scale, 180); break;
- case 270: t.setPosition(inverseY, centerX, scale, 270); break;
- default: throw new IllegalArgumentException(String.valueOf(rotation));
- }
- }
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_SHOW_LOADING: {
+ if (mLoadingState == LOADING_INIT) {
+ // We don't need the opening animation
+ mPositionController.setOpenAnimationRect(null);
- public void setPosition(int centerX, int centerY, float scale) {
- setTileViewPosition(centerX, centerY, scale);
- layoutScreenNails();
- }
-
- private void updateScreenNailEntry(int which, ImageData data) {
- if (mTransitionMode == TRANS_SWITCH_NEXT
- || mTransitionMode == TRANS_SWITCH_PREVIOUS) {
- // ignore screen nail updating during switching
- return;
- }
- ScreenNailEntry entry = mScreenNails[which];
- if (data == null) {
- entry.set(false, null, 0);
- } else {
- entry.set(true, data.bitmap, data.rotation);
- }
- }
-
- // -1 previous, 0 current, 1 next
- public void notifyImageInvalidated(int which) {
- switch (which) {
- case -1: {
- updateScreenNailEntry(
- ENTRY_PREVIOUS, mModel.getPreviousImage());
- layoutScreenNails();
- invalidate();
- break;
- }
- case 1: {
- updateScreenNailEntry(ENTRY_NEXT, mModel.getNextImage());
- layoutScreenNails();
- invalidate();
- break;
- }
- case 0: {
- // mImageWidth and mImageHeight will get updated
- mTileView.notifyModelInvalidated();
- mTileView.setAlpha(1.0f);
-
- mImageRotation = mModel.getImageRotation();
- if (((mImageRotation / 90) & 1) == 0) {
- mPositionController.setImageSize(
- mTileView.mImageWidth, mTileView.mImageHeight);
- } else {
- mPositionController.setImageSize(
- mTileView.mImageHeight, mTileView.mImageWidth);
+ mLoadingSpinner.startAnimation();
+ mLoadingState = LOADING_TIMEOUT;
+ invalidate();
+ }
+ break;
}
- updateLoadingState();
- break;
+ case MSG_CANCEL_EXTRA_SCALING: {
+ mGestureRecognizer.cancelScale();
+ mPositionController.setExtraScalingRange(false);
+ mCancelExtraScalingPending = false;
+ break;
+ }
+ case MSG_SWITCH_FOCUS: {
+ switchFocus();
+ break;
+ }
+ case MSG_CAPTURE_ANIMATION_DONE: {
+ captureAnimationDone();
+ break;
+ }
+ default: throw new AssertionError(message.what);
}
}
- }
+ };
private void updateLoadingState() {
// Possible transitions of mLoadingState:
@@ -284,12 +256,14 @@
// TIMEOUT --> COMPLETE, FAIL, INIT
// COMPLETE --> INIT
// FAIL --> INIT
- if (mModel.getLevelCount() != 0 || mModel.getBackupImage() != null) {
+ if (mModel.getLevelCount() != 0 || mModel.getScreenNail() != null) {
mHandler.removeMessages(MSG_SHOW_LOADING);
mLoadingState = LOADING_COMPLETE;
} else if (mModel.isFailedToLoad()) {
mHandler.removeMessages(MSG_SHOW_LOADING);
mLoadingState = LOADING_FAIL;
+ // We don't want the opening animation after loading failure
+ mPositionController.setOpenAnimationRect(null);
} else if (mLoadingState != LOADING_INIT) {
mLoadingState = LOADING_INIT;
mHandler.removeMessages(MSG_SHOW_LOADING);
@@ -298,534 +272,281 @@
}
}
- public void notifyModelInvalidated() {
- if (mModel == null) {
- updateScreenNailEntry(ENTRY_PREVIOUS, null);
- updateScreenNailEntry(ENTRY_NEXT, null);
- } else {
- updateScreenNailEntry(ENTRY_PREVIOUS, mModel.getPreviousImage());
- updateScreenNailEntry(ENTRY_NEXT, mModel.getNextImage());
- }
- layoutScreenNails();
+ ////////////////////////////////////////////////////////////////////////////
+ // Data/Image change notifications
+ ////////////////////////////////////////////////////////////////////////////
- if (mModel == null) {
- mTileView.notifyModelInvalidated();
- mTileView.setAlpha(1.0f);
- mImageRotation = 0;
- mPositionController.setImageSize(0, 0);
- updateLoadingState();
- } else {
- notifyImageInvalidated(0);
- }
- }
+ public void notifyDataChange(long[] versions, int prevBound, int nextBound) {
+ mPrevBound = prevBound;
+ mNextBound = nextBound;
- @Override
- protected boolean onTouch(MotionEvent event) {
- mGestureDetector.onTouchEvent(event);
- mScaleDetector.onTouchEvent(event);
- mDownUpDetector.onTouchEvent(event);
- return true;
- }
-
- @Override
- protected void onLayout(
- boolean changeSize, int left, int top, int right, int bottom) {
- mTileView.layout(left, top, right, bottom);
- mEdgeView.layout(left, top, right, bottom);
- if (changeSize) {
- mPositionController.setViewSize(getWidth(), getHeight());
- for (ScreenNailEntry entry : mScreenNails) {
- entry.updateDrawingSize();
- }
- }
- }
-
- private static int gapToSide(int imageWidth, int viewWidth) {
- return Math.max(0, (viewWidth - imageWidth) / 2);
- }
-
- /*
- * Here is how we layout the screen nails
- *
- * previous current next
- * ___________ ________________ __________
- * | _______ | | __________ | | ______ |
- * | | | | | | right->| | | | | |
- * | | |<-------->|<--left | | | | | |
- * | |_______| | | | |__________| | | |______| |
- * |___________| | |________________| |__________|
- * | <--> gapToSide()
- * |
- * IMAGE_GAP + Max(previous.gapToSide(), current.gapToSide)
- */
- private void layoutScreenNails() {
- int width = getWidth();
- int height = getHeight();
-
- // Use the image width in AC, since we may fake the size if the
- // image is unavailable
- RectF bounds = mPositionController.getImageBounds();
- int left = Math.round(bounds.left);
- int right = Math.round(bounds.right);
- int gap = gapToSide(right - left, width);
-
- // layout the previous image
- ScreenNailEntry entry = mScreenNails[ENTRY_PREVIOUS];
-
- if (entry.isEnabled()) {
- entry.layoutRightEdgeAt(left - (
- IMAGE_GAP + Math.max(gap, entry.gapToSide())));
- }
-
- // layout the next image
- entry = mScreenNails[ENTRY_NEXT];
- if (entry.isEnabled()) {
- entry.layoutLeftEdgeAt(right + (
- IMAGE_GAP + Math.max(gap, entry.gapToSide())));
- }
- }
-
- @Override
- protected void render(GLCanvas canvas) {
- PositionController p = mPositionController;
- boolean drawScreenNail = (mTransitionMode != TRANS_SLIDE_IN_LEFT
- && mTransitionMode != TRANS_SLIDE_IN_RIGHT
- && mTransitionMode != TRANS_OPEN_ANIMATION);
-
- // Draw the next photo
- if (drawScreenNail) {
- ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT];
- if (nextNail.mVisible) nextNail.draw(canvas, true);
- }
-
- // Draw the current photo
- if (mLoadingState == LOADING_COMPLETE) {
- super.render(canvas);
- }
-
- // If the photo is loaded, draw the message/icon at the center of it,
- // otherwise draw the message/icon at the center of the view.
- if (mLoadingState == LOADING_COMPLETE) {
- mTileView.getImageCenter(mImageCenter);
- renderMessage(canvas, mImageCenter.x, mImageCenter.y);
- } else {
- renderMessage(canvas, getWidth() / 2, getHeight() / 2);
- }
-
- // Draw the previous photo
- if (drawScreenNail) {
- ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS];
- if (prevNail.mVisible) prevNail.draw(canvas, false);
- }
-
- if (mPositionController.advanceAnimation()) invalidate();
- }
-
- private void renderMessage(GLCanvas canvas, int x, int y) {
- // Draw the progress spinner and the text below it
- //
- // (x, y) is where we put the center of the spinner.
- // s is the size of the video play icon, and we use s to layout text
- // because we want to keep the text at the same place when the video
- // play icon is shown instead of the spinner.
- int w = getWidth();
- int h = getHeight();
- int s = Math.min(getWidth(), getHeight()) / 6;
-
- if (mLoadingState == LOADING_TIMEOUT) {
- StringTexture m = mLoadingText;
- ProgressSpinner r = mLoadingSpinner;
- r.draw(canvas, x - r.getWidth() / 2, y - r.getHeight() / 2);
- m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
- invalidate(); // we need to keep the spinner rotating
- } else if (mLoadingState == LOADING_FAIL) {
- StringTexture m = mNoThumbnailText;
- m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
- }
-
- // Draw the video play icon (in the place where the spinner was)
- if (mShowVideoPlayIcon
- && mLoadingState != LOADING_INIT
- && mLoadingState != LOADING_TIMEOUT) {
- mVideoPlayIcon.draw(canvas, x - s / 2, y - s / 2, s, s);
- }
- }
-
- private void stopCurrentSwipingIfNeeded() {
- // Enable fast sweeping
- if (mTransitionMode == TRANS_SWITCH_NEXT) {
- mTransitionMode = TRANS_NONE;
- mPositionController.stopAnimation();
- switchToNextImage();
- } else if (mTransitionMode == TRANS_SWITCH_PREVIOUS) {
- mTransitionMode = TRANS_NONE;
- mPositionController.stopAnimation();
- switchToPreviousImage();
- }
- }
-
- private boolean swipeImages(float velocity) {
- if (mTransitionMode != TRANS_NONE
- && mTransitionMode != TRANS_SWITCH_NEXT
- && mTransitionMode != TRANS_SWITCH_PREVIOUS) return false;
-
- ScreenNailEntry next = mScreenNails[ENTRY_NEXT];
- ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS];
-
- int width = getWidth();
-
- // If we are at the edge of the current photo and the sweeping velocity
- // exceeds the threshold, switch to next / previous image.
- PositionController controller = mPositionController;
- boolean isMinimal = controller.isAtMinimalScale();
-
- if (velocity < -SWIPE_THRESHOLD &&
- (isMinimal || controller.isAtRightEdge())) {
- stopCurrentSwipingIfNeeded();
- if (next.isEnabled()) {
- mTransitionMode = TRANS_SWITCH_NEXT;
- controller.startHorizontalSlide(next.mOffsetX - width / 2);
- return true;
- }
- } else if (velocity > SWIPE_THRESHOLD &&
- (isMinimal || controller.isAtLeftEdge())) {
- stopCurrentSwipingIfNeeded();
- if (prev.isEnabled()) {
- mTransitionMode = TRANS_SWITCH_PREVIOUS;
- controller.startHorizontalSlide(prev.mOffsetX - width / 2);
- return true;
- }
- }
-
- return false;
- }
-
- public boolean snapToNeighborImage() {
- if (mTransitionMode != TRANS_NONE) return false;
-
- ScreenNailEntry next = mScreenNails[ENTRY_NEXT];
- ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS];
-
- int width = getWidth();
- PositionController controller = mPositionController;
-
- RectF bounds = controller.getImageBounds();
- int left = Math.round(bounds.left);
- int right = Math.round(bounds.right);
- int threshold = SWITCH_THRESHOLD + gapToSide(right - left, width);
-
- // If we have moved the picture a lot, switching.
- if (next.isEnabled() && threshold < width - right) {
- mTransitionMode = TRANS_SWITCH_NEXT;
- controller.startHorizontalSlide(next.mOffsetX - width / 2);
- return true;
- }
- if (prev.isEnabled() && threshold < left) {
- mTransitionMode = TRANS_SWITCH_PREVIOUS;
- controller.startHorizontalSlide(prev.mOffsetX - width / 2);
- return true;
- }
-
- return false;
- }
-
- private boolean mIgnoreUpEvent = false;
-
- private class MyGestureListener
- extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onScroll(
- MotionEvent e1, MotionEvent e2, float dx, float dy) {
- if (mTransitionMode != TRANS_NONE) return true;
-
- ScreenNailEntry next = mScreenNails[ENTRY_NEXT];
- ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS];
-
- mPositionController.startScroll(dx, dy, next.isEnabled(),
- prev.isEnabled());
- return true;
- }
-
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- if (mPhotoTapListener != null) {
- mPhotoTapListener.onSingleTapUp((int) e.getX(), (int) e.getY());
- }
- return true;
- }
-
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- if (swipeImages(velocityX)) {
- mIgnoreUpEvent = true;
- } else if (mTransitionMode != TRANS_NONE) {
- // do nothing
- } else if (mPositionController.fling(velocityX, velocityY)) {
- mIgnoreUpEvent = true;
- }
- return true;
- }
-
- @Override
- public boolean onDoubleTap(MotionEvent e) {
- if (mTransitionMode != TRANS_NONE) return true;
- PositionController controller = mPositionController;
- float scale = controller.getCurrentScale();
- // onDoubleTap happened on the second ACTION_DOWN.
- // We need to ignore the next UP event.
- mIgnoreUpEvent = true;
- if (scale <= 1.0f || controller.isAtMinimalScale()) {
- controller.zoomIn(
- e.getX(), e.getY(), Math.max(1.5f, scale * 1.5f));
- } else {
- controller.resetToFullView();
- }
- return true;
- }
- }
-
- private class MyScaleListener
- extends ScaleGestureDetector.SimpleOnScaleGestureListener {
-
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- float scale = detector.getScaleFactor();
- if (Float.isNaN(scale) || Float.isInfinite(scale)
- || mTransitionMode != TRANS_NONE) return true;
- boolean outOfRange = mPositionController.scaleBy(scale,
- detector.getFocusX(), detector.getFocusY());
- if (outOfRange) {
- if (!mCancelExtraScalingPending) {
- mHandler.sendEmptyMessageDelayed(
- MSG_CANCEL_EXTRA_SCALING, 700);
- mPositionController.setExtraScalingRange(true);
- mCancelExtraScalingPending = true;
- }
- } else {
- if (mCancelExtraScalingPending) {
- mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING);
- mPositionController.setExtraScalingRange(false);
- mCancelExtraScalingPending = false;
- }
- }
- return true;
- }
-
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- if (mTransitionMode != TRANS_NONE) return false;
- mPositionController.beginScale(
- detector.getFocusX(), detector.getFocusY());
- return true;
- }
-
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- mPositionController.endScale();
- snapToNeighborImage();
- }
- }
-
- private void cancelScaleGesture() {
- long now = SystemClock.uptimeMillis();
- MotionEvent cancelEvent = MotionEvent.obtain(
- now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
- mScaleDetector.onTouchEvent(cancelEvent);
- cancelEvent.recycle();
- }
-
- public boolean jumpTo(int index) {
- if (mTransitionMode != TRANS_NONE) return false;
- mModel.jumpTo(index);
- return true;
- }
-
- public void notifyOnNewImage() {
- mPositionController.setImageSize(0, 0);
- }
-
- public void startSlideInAnimation(int direction) {
- PositionController a = mPositionController;
- a.stopAnimation();
- switch (direction) {
- case TRANS_SLIDE_IN_LEFT:
- case TRANS_SLIDE_IN_RIGHT: {
- mTransitionMode = direction;
- a.startSlideInAnimation(direction);
+ // Check if the data version actually changed.
+ boolean changed = false;
+ int N = 2 * SCREEN_NAIL_MAX + 1;
+ for (int i = 0; i < N; i++) {
+ if (versions[i] != mDataVersion[i]) {
+ changed = true;
break;
}
- default: throw new IllegalArgumentException(String.valueOf(direction));
+ }
+ if (!changed) return;
+
+ // Create the mFromIndex array, which records the index where the picture
+ // come from. The value Integer.MAX_VALUE means it's a new picture.
+ for (int i = 0; i < N; i++) {
+ long v = versions[i];
+ if (v == INVALID_DATA_VERSION) {
+ mFromIndex[i] = Integer.MAX_VALUE;
+ continue;
+ }
+
+ // Try to find the same version number in the old array
+ int j;
+ for (j = 0; j < N; j++) {
+ if (mDataVersion[j] == v) {
+ break;
+ }
+ }
+ mFromIndex[i] = (j < N) ? j - SCREEN_NAIL_MAX : Integer.MAX_VALUE;
+ }
+
+ // Copy the new data version
+ for (int i = 0; i < N; i++) {
+ mDataVersion[i] = versions[i];
+ }
+
+ // Move the boxes
+ mPositionController.moveBox(mFromIndex, mPrevBound < 0, mNextBound > 0);
+
+ // Update the ScreenNails.
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
+ mPictures.get(i).reload();
+ }
+
+ invalidate();
+ }
+
+ public void notifyImageChange(int index) {
+ mPictures.get(index).reload();
+ invalidate();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Pictures
+ ////////////////////////////////////////////////////////////////////////////
+
+ private interface Picture {
+ void reload();
+ void draw(GLCanvas canvas, Rect r);
+ void setScreenNail(ScreenNail s);
+ boolean isCamera(); // whether the picture is a camera preview
+ };
+
+ private boolean isCameraScreenNail(ScreenNail s) {
+ return s != null && !(s instanceof BitmapScreenNail);
+ }
+
+ class FullPicture implements Picture {
+ private int mRotation;
+ private boolean mIsCamera;
+ private boolean mWasCenter;
+
+ public void FullPicture(TileImageView tileView) {
+ mTileView = tileView;
+ }
+
+ @Override
+ public void reload() {
+ // mImageWidth and mImageHeight will get updated
+ mTileView.notifyModelInvalidated();
+ mTileView.setAlpha(1.0f);
+
+ mRotation = mModel.getImageRotation(0);
+ int w = mTileView.mImageWidth;
+ int h = mTileView.mImageHeight;
+ mPositionController.setImageSize(0,
+ getRotated(mRotation, w, h),
+ getRotated(mRotation, h, w));
+
+ setScreenNail(mModel.getScreenNail(0));
+ updateLoadingState();
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, Rect r) {
+ if (mLoadingState == LOADING_COMPLETE) {
+ setTileViewPosition(r);
+ PhotoView.super.render(canvas);
+ }
+ renderMessage(canvas, r.centerX(), r.centerY());
+
+ boolean isCenter = r.centerX() == getWidth() / 2;
+
+ // We want to have following transitions:
+ // (1) Move camera preview out of its place: switch to film mode
+ // (2) Move camera preview into its place: switch to page mode
+ // The extra mWasCenter check makes sure (1) does not apply if in
+ // page mode, we move _to_ the camera preview from another picture.
+ if ((mHolding & ~(HOLD_TOUCH_DOWN | HOLD_TOUCH_DOWN_FROM_CAMERA)) == 0) {
+ if (mWasCenter && !isCenter && mIsCamera && !mFilmMode) {
+ setFilmMode(true);
+ } else if (mIsCamera && isCenter && mFilmMode) {
+ setFilmMode(false);
+ }
+ }
+ mWasCenter = isCenter;
+ }
+
+ @Override
+ public void setScreenNail(ScreenNail s) {
+ mIsCamera = isCameraScreenNail(s);
+ mTileView.setScreenNail(s);
+ }
+
+ @Override
+ public boolean isCamera() {
+ return mIsCamera;
+ }
+
+ private void setTileViewPosition(Rect r) {
+ TileImageView t = mTileView;
+
+ // Find out the bitmap coordinates of the center of the view
+ int imageW = mPositionController.getImageWidth();
+ int imageH = mPositionController.getImageHeight();
+ float scale = mPositionController.getImageScale();
+ int viewW = getWidth();
+ int viewH = getHeight();
+ int centerX = (int) (imageW / 2f +
+ (viewW / 2f - r.exactCenterX()) / scale + 0.5f);
+ int centerY = (int) (imageH / 2f +
+ (viewH / 2f - r.exactCenterY()) / scale + 0.5f);
+
+ boolean wantsCardEffect = CARD_EFFECT && !mFilmMode
+ && !mPictures.get(-1).isCamera();
+ if (wantsCardEffect) {
+ // Calculate the move-out progress value.
+ int left = r.left;
+ int right = r.right;
+ float progress = calculateMoveOutProgress(left, right, viewW);
+ progress = Utils.clamp(progress, -1f, 1f);
+
+ // We only want to apply the fading animation if the scrolling
+ // movement is to the right.
+ if (progress < 0) {
+ if (right - left < viewW) {
+ // If the picture is narrower than the view, keep it at
+ // the center of the view.
+ centerX = imageW / 2;
+ } else {
+ // If the picture is wider than the view (it's
+ // zoomed-in), keep the left edge of the object align
+ // the the left edge of the view.
+ centerX = Math.round(viewW / 2f / scale);
+ }
+ scale *= getScrollScale(progress);
+ t.setAlpha(getScrollAlpha(progress));
+ }
+ }
+
+ // set the position of the tile view
+ int inverseX = imageW - centerX;
+ int inverseY = imageH - centerY;
+ int rotation = mRotation;
+ switch (rotation) {
+ case 0: t.setPosition(centerX, centerY, scale, 0); break;
+ case 90: t.setPosition(centerY, inverseX, scale, 90); break;
+ case 180: t.setPosition(inverseX, inverseY, scale, 180); break;
+ case 270: t.setPosition(inverseY, centerX, scale, 270); break;
+ default:
+ throw new IllegalArgumentException(String.valueOf(rotation));
+ }
+ }
+
+ private void renderMessage(GLCanvas canvas, int x, int y) {
+ // Draw the progress spinner and the text below it
+ //
+ // (x, y) is where we put the center of the spinner.
+ // s is the size of the video play icon, and we use s to layout text
+ // because we want to keep the text at the same place when the video
+ // play icon is shown instead of the spinner.
+ int w = getWidth();
+ int h = getHeight();
+ int s = Math.min(getWidth(), getHeight()) / 6;
+
+ if (mLoadingState == LOADING_TIMEOUT) {
+ StringTexture m = mLoadingText;
+ ProgressSpinner p = mLoadingSpinner;
+ p.draw(canvas, x - p.getWidth() / 2, y - p.getHeight() / 2);
+ m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
+ invalidate(); // we need to keep the spinner rotating
+ } else if (mLoadingState == LOADING_FAIL) {
+ StringTexture m = mNoThumbnailText;
+ m.draw(canvas, x - m.getWidth() / 2, y + s / 2 + 5);
+ }
+
+ // Draw a debug indicator showing which picture has focus (index ==
+ // 0).
+ // canvas.fillRect(x - 10, y - 10, 20, 20, 0x80FF00FF);
+
+ // Draw the video play icon (in the place where the spinner was)
+ if (mShowVideoPlayIcon
+ && mLoadingState != LOADING_INIT
+ && mLoadingState != LOADING_TIMEOUT) {
+ mVideoPlayIcon.draw(canvas, x - s / 2, y - s / 2, s, s);
+ }
}
}
- private class MyDownUpListener implements DownUpDetector.DownUpListener {
- public void onDown(MotionEvent e) {
+ private class ScreenNailPicture implements Picture {
+ private int mIndex;
+ private int mRotation;
+ private ScreenNail mScreenNail;
+ private Size mSize = new Size();
+ private boolean mIsCamera;
+
+ public ScreenNailPicture(int index) {
+ mIndex = index;
}
- public void onUp(MotionEvent e) {
- mEdgeView.onRelease();
+ @Override
+ public void reload() {
+ setScreenNail(mModel.getScreenNail(mIndex));
+ }
- if (mIgnoreUpEvent) {
- mIgnoreUpEvent = false;
+ @Override
+ public void draw(GLCanvas canvas, Rect r) {
+ if (mScreenNail == null) {
+ // Draw a placeholder rectange if there will be a picture in
+ // this position.
+ if (mIndex >= mPrevBound && mIndex <= mNextBound) {
+ canvas.fillRect(r.left, r.top, r.width(), r.height(),
+ PLACEHOLDER_COLOR);
+ }
return;
}
- if (!snapToNeighborImage() && mTransitionMode == TRANS_NONE) {
- mPositionController.up();
+ if (r.left >= getWidth() || r.right <= 0 ||
+ r.top >= getHeight() || r.bottom <= 0) {
+ mScreenNail.noDraw();
+ return;
}
- }
- }
- private void switchToNextImage() {
- // We update the texture here directly to prevent texture uploading.
- ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS];
- ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT];
- mTileView.invalidateTiles();
- if (prevNail.mTexture != null) prevNail.mTexture.recycle();
- prevNail.mTexture = mTileView.mBackupImage;
- mTileView.mBackupImage = nextNail.mTexture;
- nextNail.mTexture = null;
- mModel.next();
- }
-
- private void switchToPreviousImage() {
- // We update the texture here directly to prevent texture uploading.
- ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS];
- ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT];
- mTileView.invalidateTiles();
- if (nextNail.mTexture != null) nextNail.mTexture.recycle();
- nextNail.mTexture = mTileView.mBackupImage;
- mTileView.mBackupImage = prevNail.mTexture;
- nextNail.mTexture = null;
- mModel.previous();
- }
-
- public void notifyTransitionComplete() {
- mHandler.sendEmptyMessage(MSG_TRANSITION_COMPLETE);
- }
-
- private void onTransitionComplete() {
- int mode = mTransitionMode;
- mTransitionMode = TRANS_NONE;
-
- if (mModel == null) return;
- if (mode == TRANS_SWITCH_NEXT) {
- switchToNextImage();
- } else if (mode == TRANS_SWITCH_PREVIOUS) {
- switchToPreviousImage();
- }
- }
-
- public boolean isDown() {
- return mDownUpDetector.isDown();
- }
-
- public static interface Model extends TileImageView.Model {
- public void next();
- public void previous();
- public void jumpTo(int index);
- public int getImageRotation();
-
- // Return null if the specified image is unavailable.
- public ImageData getNextImage();
- public ImageData getPreviousImage();
- }
-
- public static class ImageData {
- public int rotation;
- public Bitmap bitmap;
-
- public ImageData(Bitmap bitmap, int rotation) {
- this.bitmap = bitmap;
- this.rotation = rotation;
- }
- }
-
- private static int getRotated(int degree, int original, int theother) {
- return ((degree / 90) & 1) == 0 ? original : theother;
- }
-
- private class ScreenNailEntry {
- private boolean mVisible;
- private boolean mEnabled;
-
- private int mRotation;
- private int mDrawWidth;
- private int mDrawHeight;
- private int mOffsetX;
-
- private BitmapTexture mTexture;
-
- public void set(boolean enabled, Bitmap bitmap, int rotation) {
- mEnabled = enabled;
- mRotation = rotation;
- if (bitmap == null) {
- if (mTexture != null) mTexture.recycle();
- mTexture = null;
- } else {
- if (mTexture != null) {
- if (mTexture.getBitmap() != bitmap) {
- mTexture.recycle();
- mTexture = new BitmapTexture(bitmap);
- }
- } else {
- mTexture = new BitmapTexture(bitmap);
- }
- updateDrawingSize();
- }
- }
-
- public void layoutRightEdgeAt(int x) {
- mVisible = x > 0;
- mOffsetX = x - getRotated(
- mRotation, mDrawWidth, mDrawHeight) / 2;
- }
-
- public void layoutLeftEdgeAt(int x) {
- mVisible = x < getWidth();
- mOffsetX = x + getRotated(
- mRotation, mDrawWidth, mDrawHeight) / 2;
- }
-
- public int gapToSide() {
- return ((mRotation / 90) & 1) != 0
- ? PhotoView.gapToSide(mDrawHeight, getWidth())
- : PhotoView.gapToSide(mDrawWidth, getWidth());
- }
-
- public void updateDrawingSize() {
- if (mTexture == null) return;
-
- int width = mTexture.getWidth();
- int height = mTexture.getHeight();
-
- // Calculate the initial scale that will used by PositionController
- // (usually fit-to-screen)
- float s = ((mRotation / 90) & 0x01) == 0
- ? mPositionController.getMinimalScale(width, height)
- : mPositionController.getMinimalScale(height, width);
-
- mDrawWidth = Math.round(width * s);
- mDrawHeight = Math.round(height * s);
- }
-
- public boolean isEnabled() {
- return mEnabled;
- }
-
- public void draw(GLCanvas canvas, boolean applyFadingAnimation) {
- if (mTexture == null) return;
+ boolean wantsCardEffect = CARD_EFFECT && !mFilmMode
+ && (mIndex > 0) && !mPictures.get(0).isCamera();
int w = getWidth();
- int x = applyFadingAnimation ? w / 2 : mOffsetX;
- int y = getHeight() / 2;
+ int drawW = getRotated(mRotation, r.width(), r.height());
+ int drawH = getRotated(mRotation, r.height(), r.width());
+ int cx = wantsCardEffect ? w / 2 : r.centerX();
+ int cy = r.centerY();
int flags = GLCanvas.SAVE_FLAG_MATRIX;
- if (applyFadingAnimation) flags |= GLCanvas.SAVE_FLAG_ALPHA;
+ if (wantsCardEffect) flags |= GLCanvas.SAVE_FLAG_ALPHA;
canvas.save(flags);
- canvas.translate(x, y, 0);
- if (applyFadingAnimation) {
- float progress = (float) (x - mOffsetX) / w;
+ canvas.translate(cx, cy);
+ if (wantsCardEffect) {
+ float progress = (float) (w / 2 - r.centerX()) / w;
+ progress = Utils.clamp(progress, -1, 1);
float alpha = getScrollAlpha(progress);
float scale = getScrollScale(progress);
canvas.multiplyAlpha(alpha);
@@ -834,13 +555,436 @@
if (mRotation != 0) {
canvas.rotate(mRotation, 0, 0, 1);
}
- canvas.translate(-x, -y, 0);
- mTexture.draw(canvas, x - mDrawWidth / 2, y - mDrawHeight / 2,
- mDrawWidth, mDrawHeight);
+ mScreenNail.draw(canvas, -drawW / 2, -drawH / 2, drawW, drawH);
canvas.restore();
}
+
+ @Override
+ public void setScreenNail(ScreenNail s) {
+ if (mScreenNail == s) return;
+ mScreenNail = s;
+ mIsCamera = isCameraScreenNail(s);
+ mRotation = mModel.getImageRotation(mIndex);
+
+ int w = 0, h = 0;
+ if (mScreenNail != null) {
+ w = s.getWidth();
+ h = s.getHeight();
+ } else if (mModel != null) {
+ // If we don't have ScreenNail available, we can still try to
+ // get the size information of it.
+ mModel.getImageSize(mIndex, mSize);
+ w = mSize.width;
+ h = mSize.height;
+ }
+
+ if (w != 0 && h != 0) {
+ mPositionController.setImageSize(mIndex,
+ getRotated(mRotation, w, h),
+ getRotated(mRotation, h, w));
+ }
+ }
+
+ @Override
+ public boolean isCamera() {
+ return mIsCamera;
+ }
}
+ private static int getRotated(int degree, int original, int theother) {
+ return (degree % 180 == 0) ? original : theother;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Gestures Handling
+ ////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected boolean onTouch(MotionEvent event) {
+ mGestureRecognizer.onTouchEvent(event);
+ return true;
+ }
+
+ private class MyGestureListener implements GestureRecognizer.Listener {
+ private boolean mIgnoreUpEvent = false;
+ // If we can change mode for this scale gesture.
+ private boolean mCanChangeMode;
+ // If we have changed the mode in this scaling gesture.
+ private boolean mModeChanged;
+
+ @Override
+ public boolean onSingleTapUp(float x, float y) {
+ if (mFilmMode) {
+ setFilmMode(false);
+ return true;
+ }
+
+ if (mPhotoTapListener != null) {
+ mPhotoTapListener.onSingleTapUp((int) x, (int) y);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTap(float x, float y) {
+ PositionController controller = mPositionController;
+ float scale = controller.getImageScale();
+ // onDoubleTap happened on the second ACTION_DOWN.
+ // We need to ignore the next UP event.
+ mIgnoreUpEvent = true;
+ if (scale <= 1.0f || controller.isAtMinimalScale()) {
+ controller.zoomIn(x, y, Math.max(1.5f, scale * 1.5f));
+ } else {
+ controller.resetToFullView();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(float dx, float dy) {
+ mPositionController.startScroll(-dx, -dy);
+ return true;
+ }
+
+ @Override
+ public boolean onFling(float velocityX, float velocityY) {
+ if (swipeImages(velocityX, velocityY)) {
+ mIgnoreUpEvent = true;
+ } else if (mPositionController.fling(velocityX, velocityY)) {
+ mIgnoreUpEvent = true;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onScaleBegin(float focusX, float focusY) {
+ mPositionController.beginScale(focusX, focusY);
+ // We can change mode if we are in film mode, or we are in page
+ // mode and at minimal scale.
+ mCanChangeMode = mFilmMode
+ || mPositionController.isAtMinimalScale();
+ mModeChanged = false;
+ return true;
+ }
+
+ @Override
+ public boolean onScale(float focusX, float focusY, float scale) {
+ if (Float.isNaN(scale) || Float.isInfinite(scale)) return false;
+ int outOfRange = mPositionController.scaleBy(scale, focusX, focusY);
+
+ // We allow only one mode change in a scaling gesture.
+ if (mCanChangeMode && !mModeChanged) {
+ if ((outOfRange < 0 && !mFilmMode) ||
+ (outOfRange > 0 && mFilmMode)) {
+ setFilmMode(!mFilmMode);
+ mModeChanged = true;
+ return true;
+ }
+ }
+
+ if (outOfRange != 0 && !mModeChanged) {
+ startExtraScalingIfNeeded();
+ } else {
+ stopExtraScalingIfNeeded();
+ }
+ return true;
+ }
+
+ private void startExtraScalingIfNeeded() {
+ if (!mCancelExtraScalingPending) {
+ mHandler.sendEmptyMessageDelayed(
+ MSG_CANCEL_EXTRA_SCALING, 700);
+ mPositionController.setExtraScalingRange(true);
+ mCancelExtraScalingPending = true;
+ }
+ }
+
+ private void stopExtraScalingIfNeeded() {
+ if (mCancelExtraScalingPending) {
+ mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING);
+ mPositionController.setExtraScalingRange(false);
+ mCancelExtraScalingPending = false;
+ }
+ }
+
+ @Override
+ public void onScaleEnd() {
+ mPositionController.endScale();
+ }
+
+ @Override
+ public void onDown() {
+ mHolding |= HOLD_TOUCH_DOWN;
+ if (mPictures.get(0).isCamera()) {
+ mHolding |= HOLD_TOUCH_DOWN_FROM_CAMERA;
+ }
+ }
+
+ @Override
+ public void onUp() {
+ mHolding &= ~(HOLD_TOUCH_DOWN | HOLD_TOUCH_DOWN_FROM_CAMERA);
+ mEdgeView.onRelease();
+
+ if (mIgnoreUpEvent) {
+ mIgnoreUpEvent = false;
+ return;
+ }
+
+ snapback();
+ }
+ }
+
+ private void setFilmMode(boolean enabled) {
+ if (mFilmMode == enabled) return;
+ mFilmMode = enabled;
+ mPositionController.setFilmMode(mFilmMode);
+ mModel.setNeedFullImage(!enabled);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Framework events
+ ////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void onLayout(
+ boolean changeSize, int left, int top, int right, int bottom) {
+ mTileView.layout(left, top, right, bottom);
+ mEdgeView.layout(left, top, right, bottom);
+ if (changeSize) {
+ mPositionController.setViewSize(getWidth(), getHeight());
+ }
+ }
+
+ public void pause() {
+ mPositionController.skipAnimation();
+ mTileView.freeTextures();
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
+ mPictures.get(i).setScreenNail(null);
+ }
+ }
+
+ public void resume() {
+ mTileView.prepareTextures();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Rendering
+ ////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void render(GLCanvas canvas) {
+ // Draw next photos
+ for (int i = 1; i <= SCREEN_NAIL_MAX; i++) {
+ Rect r = mPositionController.getPosition(i);
+ mPictures.get(i).draw(canvas, r);
+ // In page mode, we draw only one next photo.
+ if (!mFilmMode) break;
+ }
+
+ // Draw current photo
+ mPictures.get(0).draw(canvas, mPositionController.getPosition(0));
+
+ // Draw previous photos
+ for (int i = -1; i >= -SCREEN_NAIL_MAX; i--) {
+ Rect r = mPositionController.getPosition(i);
+ mPictures.get(i).draw(canvas, r);
+ // In page mode, we draw only one previous photo.
+ if (!mFilmMode) break;
+ }
+
+ mPositionController.advanceAnimation();
+ checkFocusSwitching();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Film mode focus switching
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Runs in GL thread.
+ private void checkFocusSwitching() {
+ if (!mFilmMode) return;
+ if (mHandler.hasMessages(MSG_SWITCH_FOCUS)) return;
+ if (switchPosition() != 0) {
+ mHandler.sendEmptyMessage(MSG_SWITCH_FOCUS);
+ }
+ }
+
+ // Runs in main thread.
+ private void switchFocus() {
+ if (mHolding != 0) return;
+ switch (switchPosition()) {
+ case -1:
+ switchToPrevImage();
+ break;
+ case 1:
+ switchToNextImage();
+ break;
+ }
+ }
+
+ // Returns -1 if we should switch focus to the previous picture, +1 if we
+ // should switch to the next, 0 otherwise.
+ private int switchPosition() {
+ Rect curr = mPositionController.getPosition(0);
+ int center = getWidth() / 2;
+
+ if (curr.left > center && mPrevBound < 0) {
+ Rect prev = mPositionController.getPosition(-1);
+ int currDist = curr.left - center;
+ int prevDist = center - prev.right;
+ if (prevDist < currDist) {
+ return -1;
+ }
+ } else if (curr.right < center && mNextBound > 0) {
+ Rect next = mPositionController.getPosition(1);
+ int currDist = center - curr.right;
+ int nextDist = next.left - center;
+ if (nextDist < currDist) {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Page mode focus switching
+ //
+ // We slide image to the next one or the previous one in two cases: 1: If
+ // the user did a fling gesture with enough velocity. 2 If the user has
+ // moved the picture a lot.
+ ////////////////////////////////////////////////////////////////////////////
+
+ private boolean swipeImages(float velocityX, float velocityY) {
+ if (mFilmMode) return false;
+
+ // Avoid swiping images if we're possibly flinging to view the
+ // zoomed in picture vertically.
+ PositionController controller = mPositionController;
+ boolean isMinimal = controller.isAtMinimalScale();
+ int edges = controller.getImageAtEdges();
+ if (!isMinimal && Math.abs(velocityY) > Math.abs(velocityX))
+ if ((edges & PositionController.IMAGE_AT_TOP_EDGE) == 0
+ || (edges & PositionController.IMAGE_AT_BOTTOM_EDGE) == 0)
+ return false;
+
+ // If we are at the edge of the current photo and the sweeping velocity
+ // exceeds the threshold, slide to the next / previous image.
+ if (velocityX < -SWIPE_THRESHOLD && (isMinimal
+ || (edges & PositionController.IMAGE_AT_RIGHT_EDGE) != 0)) {
+ return slideToNextPicture();
+ } else if (velocityX > SWIPE_THRESHOLD && (isMinimal
+ || (edges & PositionController.IMAGE_AT_LEFT_EDGE) != 0)) {
+ return slideToPrevPicture();
+ }
+
+ return false;
+ }
+
+ private void snapback() {
+ if (mHolding != 0) return;
+ if (!snapToNeighborImage()) {
+ mPositionController.snapback();
+ }
+ }
+
+ private boolean snapToNeighborImage() {
+ if (mFilmMode) return false;
+
+ Rect r = mPositionController.getPosition(0);
+ int viewW = getWidth();
+ int threshold = MOVE_THRESHOLD + gapToSide(r.width(), viewW);
+
+ // If we have moved the picture a lot, switching.
+ if (viewW - r.right > threshold) {
+ return slideToNextPicture();
+ } else if (r.left > threshold) {
+ return slideToPrevPicture();
+ }
+
+ return false;
+ }
+
+ private boolean slideToNextPicture() {
+ if (mNextBound <= 0) return false;
+ switchToNextImage();
+ mPositionController.startHorizontalSlide();
+ return true;
+ }
+
+ private boolean slideToPrevPicture() {
+ if (mPrevBound >= 0) return false;
+ switchToPrevImage();
+ mPositionController.startHorizontalSlide();
+ return true;
+ }
+
+ private static int gapToSide(int imageWidth, int viewWidth) {
+ return Math.max(0, (viewWidth - imageWidth) / 2);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Focus switching
+ ////////////////////////////////////////////////////////////////////////////
+
+ private void switchToNextImage() {
+ mModel.next();
+ }
+
+ private void switchToPrevImage() {
+ mModel.previous();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Opening Animation
+ ////////////////////////////////////////////////////////////////////////////
+
+ public void setOpenAnimationRect(Rect rect) {
+ mPositionController.setOpenAnimationRect(rect);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Capture Animation
+ ////////////////////////////////////////////////////////////////////////////
+
+ public boolean switchWithCaptureAnimation(int offset) {
+ GLRoot root = getGLRoot();
+ root.lockRenderThread();
+ try {
+ return switchWithCaptureAnimationLocked(offset);
+ } finally {
+ root.unlockRenderThread();
+ }
+ }
+
+ private boolean switchWithCaptureAnimationLocked(int offset) {
+ if (mFilmMode) return false;
+ if (mHolding != 0) return true;
+ if (offset == 1) {
+ if (mNextBound <= 0) return false;
+ switchToNextImage();
+ mPositionController.startCaptureAnimationSlide(-1);
+ } else if (offset == -1) {
+ if (mPrevBound >= 0) return false;
+ switchToPrevImage();
+ mPositionController.startCaptureAnimationSlide(1);
+ } else {
+ return false;
+ }
+ mHolding |= HOLD_CAPTURE_ANIMATION;
+ mHandler.sendEmptyMessageDelayed(MSG_CAPTURE_ANIMATION_DONE, 800);
+ return true;
+ }
+
+ private void captureAnimationDone() {
+ mHolding &= ~HOLD_CAPTURE_ANIMATION;
+ snapback();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Card deck effect calculation
+ ////////////////////////////////////////////////////////////////////////////
+
// Returns the scrolling progress value for an object moving out of a
// view. The progress value measures how much the object has moving out of
// the view. The object currently displays in [left, right), and the view is
@@ -917,44 +1061,15 @@
}
}
- public void pause() {
- mPositionController.skipAnimation();
- mTransitionMode = TRANS_NONE;
- mTileView.freeTextures();
- for (ScreenNailEntry entry : mScreenNails) {
- entry.set(false, null, 0);
- }
- }
+ ////////////////////////////////////////////////////////////////////////////
+ // Simple public utilities
+ ////////////////////////////////////////////////////////////////////////////
- public void resume() {
- mTileView.prepareTextures();
- }
-
- public void setOpenedItem(Path itemPath) {
- mOpenedItemPath = itemPath;
+ public void setPhotoTapListener(PhotoTapListener listener) {
+ mPhotoTapListener = listener;
}
public void showVideoPlayIcon(boolean show) {
mShowVideoPlayIcon = show;
}
-
- // Returns the position saved by the previous page.
- public Position retrieveSavedPosition() {
- if (mOpenedItemPath != null) {
- Position position = PositionRepository
- .getInstance(mActivity).get(Long.valueOf(
- System.identityHashCode(mOpenedItemPath)));
- mOpenedItemPath = null;
- return position;
- }
- return null;
- }
-
- public void openAnimationStarted() {
- mTransitionMode = TRANS_OPEN_ANIMATION;
- }
-
- public boolean isInTransition() {
- return mTransitionMode != TRANS_NONE;
- }
}
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index 2068446..e6c132b 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -16,353 +16,449 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.ui.PositionRepository.Position;
-import com.android.gallery3d.util.GalleryUtils;
-
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.RectF;
-import android.os.Message;
-import android.os.SystemClock;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.widget.Scroller;
+import android.graphics.Rect;
+import android.util.Log;
+import android.widget.OverScroller;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.RangeArray;
+import com.android.gallery3d.util.RangeIntArray;
class PositionController {
private static final String TAG = "PositionController";
- private long mAnimationStartTime = NO_ANIMATION;
+
+ public static final int IMAGE_AT_LEFT_EDGE = 1;
+ public static final int IMAGE_AT_RIGHT_EDGE = 2;
+ public static final int IMAGE_AT_TOP_EDGE = 4;
+ public static final int IMAGE_AT_BOTTOM_EDGE = 8;
+
+ // Special values for animation time.
private static final long NO_ANIMATION = -1;
private static final long LAST_ANIMATION = -2;
- private int mAnimationKind;
- private float mAnimationDuration;
- private final static int ANIM_KIND_SCROLL = 0;
- private final static int ANIM_KIND_SCALE = 1;
- private final static int ANIM_KIND_SNAPBACK = 2;
- private final static int ANIM_KIND_SLIDE = 3;
- private final static int ANIM_KIND_ZOOM = 4;
- private final static int ANIM_KIND_FLING = 5;
+ private static final int ANIM_KIND_SCROLL = 0;
+ private static final int ANIM_KIND_SCALE = 1;
+ private static final int ANIM_KIND_SNAPBACK = 2;
+ private static final int ANIM_KIND_SLIDE = 3;
+ private static final int ANIM_KIND_ZOOM = 4;
+ private static final int ANIM_KIND_OPENING = 5;
+ private static final int ANIM_KIND_FLING = 6;
+ private static final int ANIM_KIND_CAPTURE = 7;
// Animation time in milliseconds. The order must match ANIM_KIND_* above.
- private final static int ANIM_TIME[] = {
+ private static final int ANIM_TIME[] = {
0, // ANIM_KIND_SCROLL
50, // ANIM_KIND_SCALE
600, // ANIM_KIND_SNAPBACK
400, // ANIM_KIND_SLIDE
300, // ANIM_KIND_ZOOM
+ 600, // ANIM_KIND_OPENING
0, // ANIM_KIND_FLING (the duration is calculated dynamically)
+ 800, // ANIM_KIND_CAPTURE
};
// We try to scale up the image to fill the screen. But in order not to
// scale too much for small icons, we limit the max up-scaling factor here.
private static final float SCALE_LIMIT = 4;
- private static final int sHorizontalSlack = GalleryUtils.dpToPixel(12);
- private static final float SCALE_MIN_EXTRA = 0.6f;
+ // For user's gestures, we give a temporary extra scaling range which goes
+ // above or below the usual scaling limits.
+ private static final float SCALE_MIN_EXTRA = 0.7f;
private static final float SCALE_MAX_EXTRA = 1.4f;
- private PhotoView mViewer;
- private EdgeView mEdgeView;
- private int mImageW, mImageH;
- private int mViewW, mViewH;
-
- // The X, Y are the coordinate on bitmap which shows on the center of
- // the view. We always keep the mCurrent{X,Y,Scale} sync with the actual
- // values used currently.
- private int mCurrentX, mFromX, mToX;
- private int mCurrentY, mFromY, mToY;
- private float mCurrentScale, mFromScale, mToScale;
-
- // The focus point of the scaling gesture (in bitmap coordinates).
- private int mFocusBitmapX;
- private int mFocusBitmapY;
- private boolean mInScale;
-
- // The minimum and maximum scale we allow.
- private float mScaleMin, mScaleMax = SCALE_LIMIT;
+ // Setting this true makes the extra scaling range permanent (until this is
+ // set to false again).
private boolean mExtraScalingRange = false;
- // This is used by the fling animation
- private FlingScroller mScroller;
+ // Film Mode v.s. Page Mode: in film mode we show smaller pictures.
+ private boolean mFilmMode = false;
- // The bound of the stable region, see the comments above
- // calculateStableBound() for details.
+ // These are the limits for width / height of the picture in film mode.
+ private static final float FILM_MODE_PORTRAIT_HEIGHT = 0.48f;
+ private static final float FILM_MODE_PORTRAIT_WIDTH = 0.7f;
+ private static final float FILM_MODE_LANDSCAPE_HEIGHT = 0.7f;
+ private static final float FILM_MODE_LANDSCAPE_WIDTH = 0.7f;
+
+ // In addition to the focused box (index == 0). We also keep information
+ // about this many boxes on each side.
+ private static final int BOX_MAX = PhotoView.SCREEN_NAIL_MAX;
+
+ private static final int IMAGE_GAP = GalleryUtils.dpToPixel(16);
+ private static final int HORIZONTAL_SLACK = GalleryUtils.dpToPixel(12);
+
+ private Listener mListener;
+ private volatile Rect mOpenAnimationRect;
+ private int mViewW = 640;
+ private int mViewH = 480;;
+
+ // A scaling guesture is in progress.
+ private boolean mInScale;
+ // The focus point of the scaling gesture, relative to the center of the
+ // picture in bitmap pixels.
+ private float mFocusX, mFocusY;
+
+ // whether there is a previous/next picture.
+ private boolean mHasPrev, mHasNext;
+
+ // This is used by the fling animation (page mode).
+ private FlingScroller mPageScroller;
+
+ // This is used by the fling animation (film mode).
+ private OverScroller mFilmScroller;
+
+ // The bound of the stable region that the focused box can stay, see the
+ // comments above calculateStableBound() for details.
private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
- // Assume the image size is the same as view size before we know the actual
- // size of image.
- private boolean mUseViewSize = true;
+ //
+ // ___________________________________________________________
+ // | _____ _____ _____ _____ _____ |
+ // | | | | | | | | | | | |
+ // | | Box | | Box | | Box*| | Box | | Box | |
+ // | |_____|.....|_____|.....|_____|.....|_____|.....|_____| |
+ // | Gap Gap Gap Gap |
+ // |___________________________________________________________|
+ //
+ // <-- Platform -->
+ //
+ // The focused box (Box*) centers at mPlatform.mCurrentX
- private RectF mTempRect = new RectF();
- private float[] mTempPoints = new float[8];
+ private Platform mPlatform = new Platform();
+ private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
+ // The gap at the right of a Box i is at index i. The gap at the left of a
+ // Box i is at index i - 1.
+ private RangeArray<Gap> mGaps = new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
- public PositionController(PhotoView viewer, Context context,
- EdgeView edgeView) {
- mViewer = viewer;
- mEdgeView = edgeView;
- mScroller = new FlingScroller();
+ // These are only used during moveBox().
+ private RangeArray<Box> mTempBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
+ private RangeArray<Gap> mTempGaps =
+ new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
+
+ // The output of the PositionController. Available throught getPosition().
+ private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
+
+ public interface Listener {
+ void invalidate();
+ boolean isHolding();
+
+ // EdgeView
+ void onPull(int offset, int direction);
+ void onRelease();
+ void onAbsorb(int velocity, int direction);
}
- public void setImageSize(int width, int height) {
+ public PositionController(Context context, Listener listener) {
+ mListener = listener;
+ mPageScroller = new FlingScroller();
+ mFilmScroller = new OverScroller(context);
- // If no image available, use view size.
+ // Initialize the areas.
+ initPlatform();
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ mBoxes.put(i, new Box());
+ initBox(i);
+ mRects.put(i, new Rect());
+ }
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ mGaps.put(i, new Gap());
+ initGap(i);
+ }
+ }
+
+ public void setOpenAnimationRect(Rect r) {
+ mOpenAnimationRect = r;
+ }
+
+ public void setViewSize(int viewW, int viewH) {
+ if (viewW == mViewW && viewH == mViewH) return;
+
+ mViewW = viewW;
+ mViewH = viewH;
+ initPlatform();
+
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ setBoxSize(i, viewW, viewH, true);
+ }
+
+ updateScaleAndGapLimit();
+ snapAndRedraw();
+ }
+
+ public void setImageSize(int index, int width, int height) {
if (width == 0 || height == 0) {
- mUseViewSize = true;
- mImageW = mViewW;
- mImageH = mViewH;
- mCurrentX = mImageW / 2;
- mCurrentY = mImageH / 2;
- mCurrentScale = 1;
- mScaleMin = 1;
- mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
+ initBox(index);
+ } else if (!setBoxSize(index, width, height, false)) {
return;
}
- mUseViewSize = false;
-
- float ratio = Math.min(
- (float) mImageW / width, (float) mImageH / height);
-
- // See the comment above translate() for details.
- mCurrentX = translate(mCurrentX, mImageW, width, ratio);
- mCurrentY = translate(mCurrentY, mImageH, height, ratio);
- mCurrentScale = mCurrentScale * ratio;
-
- mFromX = translate(mFromX, mImageW, width, ratio);
- mFromY = translate(mFromY, mImageH, height, ratio);
- mFromScale = mFromScale * ratio;
-
- mToX = translate(mToX, mImageW, width, ratio);
- mToY = translate(mToY, mImageH, height, ratio);
- mToScale = mToScale * ratio;
-
- mFocusBitmapX = translate(mFocusBitmapX, mImageW, width, ratio);
- mFocusBitmapY = translate(mFocusBitmapY, mImageH, height, ratio);
-
- mImageW = width;
- mImageH = height;
-
- mScaleMin = getMinimalScale(mImageW, mImageH);
-
- // Start animation from the saved position if we have one.
- Position position = mViewer.retrieveSavedPosition();
- if (position != null) {
- // The animation starts from 240 pixels and centers at the image
- // at the saved position.
- float scale = 240f / Math.min(width, height);
- mCurrentX = Math.round((mViewW / 2f - position.x) / scale) + mImageW / 2;
- mCurrentY = Math.round((mViewH / 2f - position.y) / scale) + mImageH / 2;
- mCurrentScale = scale;
- mViewer.openAnimationStarted();
- startSnapback();
- } else if (mAnimationStartTime == NO_ANIMATION) {
- mCurrentScale = Utils.clamp(mCurrentScale, mScaleMin, mScaleMax);
- }
- mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
+ updateScaleAndGapLimit();
+ startOpeningAnimationIfNeeded();
+ snapAndRedraw();
}
- public void zoomIn(float tapX, float tapY, float targetScale) {
- if (targetScale > mScaleMax) targetScale = mScaleMax;
+ // Returns false if the box size doesn't change.
+ private boolean setBoxSize(int i, int width, int height, boolean isViewSize) {
+ Box b = mBoxes.get(i);
+ boolean wasViewSize = b.mUseViewSize;
- // Convert the tap position to image coordinate
- int tempX = Math.round((tapX - mViewW / 2) / mCurrentScale + mCurrentX);
- int tempY = Math.round((tapY - mViewH / 2) / mCurrentScale + mCurrentY);
+ // If we already have an image size, we don't want to use the view size.
+ if (!wasViewSize && isViewSize) return false;
+
+ b.mUseViewSize = isViewSize;
+
+ if (width == b.mImageW && height == b.mImageH) {
+ return false;
+ }
+
+ // The ratio of the old size and the new size.
+ float ratio = Math.min(
+ (float) b.mImageW / width, (float) b.mImageH / height);
+
+ // If this is the first time we receive an image size, we change the
+ // scale directly. Otherwise adjust the scales by a ratio, and snapback
+ // will animate the scale into the min/max bounds if necessary.
+ if (wasViewSize && !isViewSize) {
+ b.mCurrentScale = getMinimalScale(width, height);
+ b.mAnimationStartTime = NO_ANIMATION;
+ } else {
+ b.mCurrentScale *= ratio;
+ b.mFromScale *= ratio;
+ b.mToScale *= ratio;
+ }
+
+ b.mImageW = width;
+ b.mImageH = height;
+
+ if (i == 0) {
+ mFocusX /= ratio;
+ mFocusY /= ratio;
+ }
+
+ return true;
+ }
+
+ private void startOpeningAnimationIfNeeded() {
+ if (mOpenAnimationRect == null) return;
+ Box b = mBoxes.get(0);
+ if (b.mUseViewSize) return;
+
+ // Start animation from the saved rectangle if we have one.
+ Rect r = mOpenAnimationRect;
+ mOpenAnimationRect = null;
+ mPlatform.mCurrentX = r.centerX();
+ b.mCurrentY = r.centerY();
+ b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
+ r.height() / (float) b.mImageH);
+ startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_OPENING);
+ }
+
+ public void setFilmMode(boolean enabled) {
+ if (enabled == mFilmMode) return;
+ mFilmMode = enabled;
+
+ updateScaleAndGapLimit();
+ stopAnimation();
+ snapAndRedraw();
+ }
+
+ public void setExtraScalingRange(boolean enabled) {
+ if (mExtraScalingRange == enabled) return;
+ mExtraScalingRange = enabled;
+ if (!enabled) {
+ snapAndRedraw();
+ }
+ }
+
+ // This should be called whenever the scale range of boxes or the default
+ // gap size may change. Currently this can happen due to change of view
+ // size, image size, and mode.
+ private void updateScaleAndGapLimit() {
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ Box b = mBoxes.get(i);
+ b.mScaleMin = getMinimalScale(b.mImageW, b.mImageH);
+ b.mScaleMax = getMaximalScale(b.mImageW, b.mImageH);
+ }
+
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ Gap g = mGaps.get(i);
+ g.mDefaultSize = getDefaultGapSize(i);
+ }
+ }
+
+ // Returns the default gap size according the the size of the boxes around
+ // the gap and the current mode.
+ private int getDefaultGapSize(int i) {
+ if (mFilmMode) return IMAGE_GAP;
+ Box a = mBoxes.get(i);
+ Box b = mBoxes.get(i + 1);
+ return IMAGE_GAP + Math.max(gapToSide(a), gapToSide(b));
+ }
+
+ // Here is how we layout the boxes in the page mode.
+ //
+ // previous current next
+ // ___________ ________________ __________
+ // | _______ | | __________ | | ______ |
+ // | | | | | | right->| | | | | |
+ // | | |<-------->|<--left | | | | | |
+ // | |_______| | | | |__________| | | |______| |
+ // |___________| | |________________| |__________|
+ // | <--> gapToSide()
+ // |
+ // IMAGE_GAP + MAX(gapToSide(previous), gapToSide(current))
+ private int gapToSide(Box b) {
+ return (int) ((mViewW - getMinimalScale(b) * b.mImageW) / 2 + 0.5f);
+ }
+
+ // Stop all animations at where they are now.
+ public void stopAnimation() {
+ mPlatform.mAnimationStartTime = NO_ANIMATION;
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ mBoxes.get(i).mAnimationStartTime = NO_ANIMATION;
+ }
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ mGaps.get(i).mAnimationStartTime = NO_ANIMATION;
+ }
+ }
+
+ public void skipAnimation() {
+ if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
+ mPlatform.mCurrentX = mPlatform.mToX;
+ mPlatform.mAnimationStartTime = NO_ANIMATION;
+ }
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ Box b = mBoxes.get(i);
+ if (b.mAnimationStartTime == NO_ANIMATION) continue;
+ b.mCurrentY = b.mToY;
+ b.mCurrentScale = b.mToScale;
+ b.mAnimationStartTime = NO_ANIMATION;
+ }
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ Gap g = mGaps.get(i);
+ if (g.mAnimationStartTime == NO_ANIMATION) continue;
+ g.mCurrentGap = g.mToGap;
+ g.mAnimationStartTime = NO_ANIMATION;
+ }
+ redraw();
+ }
+
+ public void snapback() {
+ snapAndRedraw();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Start an animations for the focused box
+ ////////////////////////////////////////////////////////////////////////////
+
+ public void zoomIn(float tapX, float tapY, float targetScale) {
+ Box b = mBoxes.get(0);
+
+ // Convert the tap position to distance to center in bitmap coordinates
+ float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
+ float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
+
+ int x = (int) (mViewW / 2 - tempX * targetScale + 0.5f);
+ int y = (int) (mViewH / 2 - tempY * targetScale + 0.5f);
calculateStableBound(targetScale);
- int targetX = Utils.clamp(tempX, mBoundLeft, mBoundRight);
- int targetY = Utils.clamp(tempY, mBoundTop, mBoundBottom);
+ int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
+ int targetY = Utils.clamp(y, mBoundTop, mBoundBottom);
+ targetScale = Utils.clamp(targetScale, b.mScaleMin, b.mScaleMax);
startAnimation(targetX, targetY, targetScale, ANIM_KIND_ZOOM);
}
public void resetToFullView() {
- startAnimation(mImageW / 2, mImageH / 2, mScaleMin, ANIM_KIND_ZOOM);
- }
-
- public float getMinimalScale(int w, int h) {
- return Math.min(SCALE_LIMIT,
- Math.min((float) mViewW / w, (float) mViewH / h));
- }
-
- // Translate a coordinate on bitmap if the bitmap size changes.
- // If the aspect ratio doesn't change, it's easy:
- //
- // r = w / w' (= h / h')
- // x' = x / r
- // y' = y / r
- //
- // However the aspect ratio may change. That happens when the user slides
- // a image before it's loaded, we don't know the actual aspect ratio, so
- // we will assume one. When we receive the actual bitmap size, we need to
- // translate the coordinate from the old bitmap into the new bitmap.
- //
- // What we want to do is center the bitmap at the original position.
- //
- // ...+--+...
- // . | | .
- // . | | .
- // ...+--+...
- //
- // First we scale down the new bitmap by a factor r = min(w/w', h/h').
- // Overlay it onto the original bitmap. Now (0, 0) of the old bitmap maps
- // to (-(w-w'*r)/2 / r, -(h-h'*r)/2 / r) in the new bitmap. So (x, y) of
- // the old bitmap maps to (x', y') in the new bitmap, where
- // x' = (x-(w-w'*r)/2) / r = w'/2 + (x-w/2)/r
- // y' = (y-(h-h'*r)/2) / r = h'/2 + (y-h/2)/r
- private static int translate(int value, int size, int newSize, float ratio) {
- return Math.round(newSize / 2f + (value - size / 2f) / ratio);
- }
-
- public void setViewSize(int viewW, int viewH) {
- boolean needLayout = mViewW == 0 || mViewH == 0;
-
- mViewW = viewW;
- mViewH = viewH;
-
- if (mUseViewSize) {
- mImageW = viewW;
- mImageH = viewH;
- mCurrentX = mImageW / 2;
- mCurrentY = mImageH / 2;
- mCurrentScale = 1;
- mScaleMin = 1;
- mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
- return;
- }
-
- // In most cases we want to keep the scaling factor intact when the
- // view size changes. The cases we want to reset the scaling factor
- // (to fit the view if possible) are (1) the scaling factor is too
- // small for the new view size (2) the scaling factor has not been
- // changed by the user.
- boolean wasMinScale = (mCurrentScale == mScaleMin);
- mScaleMin = getMinimalScale(mImageW, mImageH);
-
- if (needLayout || mCurrentScale < mScaleMin || wasMinScale) {
- mCurrentX = mImageW / 2;
- mCurrentY = mImageH / 2;
- mCurrentScale = mScaleMin;
- mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
- }
- }
-
- public void stopAnimation() {
- mAnimationStartTime = NO_ANIMATION;
- }
-
- public void skipAnimation() {
- if (mAnimationStartTime == NO_ANIMATION) return;
- mAnimationStartTime = NO_ANIMATION;
- mCurrentX = mToX;
- mCurrentY = mToY;
- mCurrentScale = mToScale;
+ Box b = mBoxes.get(0);
+ startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_ZOOM);
}
public void beginScale(float focusX, float focusY) {
+ Box b = mBoxes.get(0);
+ Platform p = mPlatform;
mInScale = true;
- mFocusBitmapX = Math.round(mCurrentX +
- (focusX - mViewW / 2f) / mCurrentScale);
- mFocusBitmapY = Math.round(mCurrentY +
- (focusY - mViewH / 2f) / mCurrentScale);
+ mFocusX = (int) ((focusX - p.mCurrentX) / b.mCurrentScale + 0.5f);
+ mFocusY = (int) ((focusY - b.mCurrentY) / b.mCurrentScale + 0.5f);
}
- // Returns true if the result scale is outside the stable range.
- public boolean scaleBy(float s, float focusX, float focusY) {
+ // Scales the image by the given factor.
+ // Returns an out-of-range indicator:
+ // 1 if the intended scale is too large for the stable range.
+ // 0 if the intended scale is in the stable range.
+ // -1 if the intended scale is too small for the stable range.
+ public int scaleBy(float s, float focusX, float focusY) {
+ Box b = mBoxes.get(0);
+ Platform p = mPlatform;
- // We want to keep the focus point (on the bitmap) the same as when
- // we begin the scale guesture, that is,
+ // We want to keep the focus point (on the bitmap) the same as when we
+ // begin the scale guesture, that is,
//
- // mCurrentX' + (focusX - mViewW / 2f) / scale = mFocusBitmapX
+ // (focusX' - currentX') / scale' = (focusX - currentX) / scale
//
- s *= getTargetScale();
- int x = Math.round(mFocusBitmapX - (focusX - mViewW / 2f) / s);
- int y = Math.round(mFocusBitmapY - (focusY - mViewH / 2f) / s);
-
+ s *= getTargetScale(b);
+ int x = mFilmMode ? p.mCurrentX : (int) (focusX - s * mFocusX + 0.5f);
+ int y = mFilmMode ? b.mCurrentY : (int) (focusY - s * mFocusY + 0.5f);
startAnimation(x, y, s, ANIM_KIND_SCALE);
- return (s < mScaleMin || s > mScaleMax);
+ if (s < b.mScaleMin) return -1;
+ if (s > b.mScaleMax) return 1;
+ return 0;
}
public void endScale() {
mInScale = false;
- startSnapbackIfNeeded();
+ snapAndRedraw();
}
- public void setExtraScalingRange(boolean enabled) {
- mExtraScalingRange = enabled;
- if (!enabled) {
- startSnapbackIfNeeded();
+ // Slide the focused box to the center of the view.
+ public void startHorizontalSlide() {
+ Box b = mBoxes.get(0);
+ startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_SLIDE);
+ }
+
+ // Slide the focused box to the center of the view with the capture
+ // animation. In addition to the sliding, the animation will also scale the
+ // the focused box, the specified neighbor box, and the gap between the
+ // two. The specified offset should be 1 or -1.
+ public void startCaptureAnimationSlide(int offset) {
+ Box b = mBoxes.get(0);
+ Box n = mBoxes.get(offset); // the neighbor box
+ Gap g = mGaps.get(offset); // the gap between the two boxes
+
+ mPlatform.doAnimation(mViewW / 2, ANIM_KIND_CAPTURE);
+ b.doAnimation(mViewH / 2, b.mScaleMin, ANIM_KIND_CAPTURE);
+ n.doAnimation(mViewH / 2, n.mScaleMin, ANIM_KIND_CAPTURE);
+ g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
+ redraw();
+ }
+
+ public void startScroll(float dx, float dy) {
+ Box b = mBoxes.get(0);
+ Platform p = mPlatform;
+
+ int x = getTargetX(p) + (int) (dx + 0.5f);
+ int y = getTargetY(b) + (int) (dy + 0.5f);
+
+ if (mFilmMode) {
+ scrollToFilm(x, y);
+ } else {
+ scrollToPage(x, y);
}
}
- public float getCurrentScale() {
- return mCurrentScale;
- }
+ private void scrollToPage(int x, int y) {
+ Box b = mBoxes.get(0);
- public boolean isAtMinimalScale() {
- return isAlmostEquals(mCurrentScale, mScaleMin);
- }
-
- private static boolean isAlmostEquals(float a, float b) {
- float diff = a - b;
- return (diff < 0 ? -diff : diff) < 0.02f;
- }
-
- public void up() {
- startSnapback();
- }
-
- // |<--| (1/2) * mImageW
- // +-------+-------+-------+
- // | | | |
- // | | o | |
- // | | | |
- // +-------+-------+-------+
- // |<----------| (3/2) * mImageW
- // Slide in the image from left or right.
- // Precondition: mCurrentScale = 1 (mView{W|H} == mImage{W|H}).
- // Sliding from left: mCurrentX = (1/2) * mImageW
- // right: mCurrentX = (3/2) * mImageW
- public void startSlideInAnimation(int direction) {
- int fromX = (direction == PhotoView.TRANS_SLIDE_IN_LEFT) ?
- mImageW / 2 : 3 * mImageW / 2;
- mFromX = Math.round(fromX);
- mFromY = Math.round(mImageH / 2f);
- mCurrentX = mFromX;
- mCurrentY = mFromY;
- startAnimation(
- mImageW / 2, mImageH / 2, mCurrentScale, ANIM_KIND_SLIDE);
- }
-
- public void startHorizontalSlide(int distance) {
- scrollBy(distance, 0, ANIM_KIND_SLIDE);
- }
-
- private void scrollBy(float dx, float dy, int type) {
- startAnimation(getTargetX() + Math.round(dx / mCurrentScale),
- getTargetY() + Math.round(dy / mCurrentScale),
- mCurrentScale, type);
- }
-
- public void startScroll(float dx, float dy, boolean hasNext,
- boolean hasPrev) {
- int x = getTargetX() + Math.round(dx / mCurrentScale);
- int y = getTargetY() + Math.round(dy / mCurrentScale);
-
- calculateStableBound(mCurrentScale);
+ calculateStableBound(b.mCurrentScale);
// Vertical direction: If we have space to move in the vertical
// direction, we show the edge effect when scrolling reaches the edge.
if (mBoundTop != mBoundBottom) {
if (y < mBoundTop) {
- mEdgeView.onPull(mBoundTop - y, EdgeView.TOP);
+ mListener.onPull(mBoundTop - y, EdgeView.BOTTOM);
} else if (y > mBoundBottom) {
- mEdgeView.onPull(y - mBoundBottom, EdgeView.BOTTOM);
+ mListener.onPull(y - mBoundBottom, EdgeView.TOP);
}
}
@@ -370,213 +466,517 @@
// Horizontal direction: we show the edge effect when the scrolling
// tries to go left of the first image or go right of the last image.
- if (!hasPrev && x < mBoundLeft) {
- int pixels = Math.round((mBoundLeft - x) * mCurrentScale);
- mEdgeView.onPull(pixels, EdgeView.LEFT);
- x = mBoundLeft;
- } else if (!hasNext && x > mBoundRight) {
- int pixels = Math.round((x - mBoundRight) * mCurrentScale);
- mEdgeView.onPull(pixels, EdgeView.RIGHT);
+ if (!mHasPrev && x > mBoundRight) {
+ int pixels = x - mBoundRight;
+ mListener.onPull(pixels, EdgeView.LEFT);
x = mBoundRight;
+ } else if (!mHasNext && x < mBoundLeft) {
+ int pixels = mBoundLeft - x;
+ mListener.onPull(pixels, EdgeView.RIGHT);
+ x = mBoundLeft;
}
- startAnimation(x, y, mCurrentScale, ANIM_KIND_SCROLL);
+ startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
+ }
+
+ private void scrollToFilm(int x, int y) {
+ Box b = mBoxes.get(0);
+
+ // Horizontal direction: we show the edge effect when the scrolling
+ // tries to go left of the first image or go right of the last image.
+ int cx = mViewW / 2;
+ if (!mHasPrev && x > cx) {
+ int pixels = x - cx;
+ mListener.onPull(pixels, EdgeView.LEFT);
+ x = cx;
+ } else if (!mHasNext && x < cx) {
+ int pixels = cx - x;
+ mListener.onPull(pixels, EdgeView.RIGHT);
+ x = cx;
+ }
+
+ startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
}
public boolean fling(float velocityX, float velocityY) {
+ int vx = (int) (velocityX + 0.5f);
+ int vy = (int) (velocityY + 0.5f);
+ return mFilmMode ? flingFilm(vx, vy) : flingPage(vx, vy);
+ }
+
+ private boolean flingPage(int velocityX, int velocityY) {
+ Box b = mBoxes.get(0);
+ Platform p = mPlatform;
+
// We only want to do fling when the picture is zoomed-in.
- if (mImageW * mCurrentScale <= mViewW &&
- mImageH * mCurrentScale <= mViewH) {
+ if (viewWiderThanScaledImage(b.mCurrentScale) &&
+ viewTallerThanScaledImage(b.mCurrentScale)) {
return false;
}
- calculateStableBound(mCurrentScale);
- mScroller.fling(mCurrentX, mCurrentY,
- Math.round(-velocityX / mCurrentScale),
- Math.round(-velocityY / mCurrentScale),
+ // We only allow flinging in the directions where it won't go over the
+ // picture.
+ int edges = getImageAtEdges();
+ if ((velocityX > 0 && (edges & IMAGE_AT_LEFT_EDGE) != 0) ||
+ (velocityX < 0 && (edges & IMAGE_AT_RIGHT_EDGE) != 0)) {
+ velocityX = 0;
+ }
+ if ((velocityY > 0 && (edges & IMAGE_AT_TOP_EDGE) != 0) ||
+ (velocityY < 0 && (edges & IMAGE_AT_BOTTOM_EDGE) != 0)) {
+ velocityY = 0;
+ }
+
+ if (velocityX == 0 && velocityY == 0) return false;
+
+ mPageScroller.fling(p.mCurrentX, b.mCurrentY, velocityX, velocityY,
mBoundLeft, mBoundRight, mBoundTop, mBoundBottom);
- int targetX = mScroller.getFinalX();
- int targetY = mScroller.getFinalY();
- mAnimationDuration = mScroller.getDuration();
- startAnimation(targetX, targetY, mCurrentScale, ANIM_KIND_FLING);
+ int targetX = mPageScroller.getFinalX();
+ int targetY = mPageScroller.getFinalY();
+ ANIM_TIME[ANIM_KIND_FLING] = mPageScroller.getDuration();
+ startAnimation(targetX, targetY, b.mCurrentScale, ANIM_KIND_FLING);
return true;
}
- private void startAnimation(
- int targetX, int targetY, float scale, int kind) {
- if (targetX == mCurrentX && targetY == mCurrentY
- && scale == mCurrentScale) return;
+ private boolean flingFilm(int velocityX, int velocityY) {
+ Box b = mBoxes.get(0);
+ Platform p = mPlatform;
- mFromX = mCurrentX;
- mFromY = mCurrentY;
- mFromScale = mCurrentScale;
-
- mToX = targetX;
- mToY = targetY;
- mToScale = Utils.clamp(scale, SCALE_MIN_EXTRA * mScaleMin,
- SCALE_MAX_EXTRA * mScaleMax);
-
- // If the scaled height is smaller than the view height,
- // force it to be in the center.
- // (We do for height only, not width, because the user may
- // want to scroll to the previous/next image.)
- if (Math.floor(mImageH * mToScale) <= mViewH) {
- mToY = mImageH / 2;
- }
-
- mAnimationStartTime = SystemClock.uptimeMillis();
- mAnimationKind = kind;
- if (mAnimationKind != ANIM_KIND_FLING) {
- mAnimationDuration = ANIM_TIME[mAnimationKind];
- }
- if (advanceAnimation()) mViewer.invalidate();
- }
-
- // Returns true if redraw is needed.
- public boolean advanceAnimation() {
- if (mAnimationStartTime == NO_ANIMATION) {
+ // If we are already at the edge, don't start the fling.
+ int cx = mViewW / 2;
+ if ((!mHasPrev && p.mCurrentX >= cx)
+ || (!mHasNext && p.mCurrentX <= cx)) {
return false;
- } else if (mAnimationStartTime == LAST_ANIMATION) {
- mAnimationStartTime = NO_ANIMATION;
- if (mViewer.isInTransition()) {
- mViewer.notifyTransitionComplete();
- return false;
- } else {
- return startSnapbackIfNeeded();
- }
}
- long now = SystemClock.uptimeMillis();
- float progress;
- if (mAnimationDuration == 0) {
- progress = 1;
- } else {
- progress = (now - mAnimationStartTime) / mAnimationDuration;
- }
+ if (velocityX == 0) return false;
- if (progress >= 1) {
- progress = 1;
- mCurrentX = mToX;
- mCurrentY = mToY;
- mCurrentScale = mToScale;
- mAnimationStartTime = LAST_ANIMATION;
- } else {
- float f = 1 - progress;
- switch (mAnimationKind) {
- case ANIM_KIND_SCROLL:
- case ANIM_KIND_FLING:
- progress = 1 - f; // linear
- break;
- case ANIM_KIND_SCALE:
- progress = 1 - f * f; // quadratic
- break;
- case ANIM_KIND_SNAPBACK:
- case ANIM_KIND_ZOOM:
- case ANIM_KIND_SLIDE:
- progress = 1 - f * f * f * f * f; // x^5
- break;
- }
- if (mAnimationKind == ANIM_KIND_FLING) {
- flingInterpolate(progress);
- } else {
- linearInterpolate(progress);
- }
- }
- mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
+ mFilmScroller.fling(p.mCurrentX, 0, velocityX, 0,
+ Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
+ int targetX = mFilmScroller.getFinalX();
+ // This value doesn't matter because we use mFilmScroller.isFinished()
+ // to decide when to stop. We set this to 0 so it's faster for
+ // Animatable.advanceAnimation() to calculate the progress (always 1).
+ ANIM_TIME[ANIM_KIND_FLING] = 0;
+ startAnimation(targetX, b.mCurrentY, b.mCurrentScale, ANIM_KIND_FLING);
return true;
}
- private void flingInterpolate(float progress) {
- mScroller.computeScrollOffset(progress);
- int oldX = mCurrentX;
- int oldY = mCurrentY;
- mCurrentX = mScroller.getCurrX();
- mCurrentY = mScroller.getCurrY();
+ ////////////////////////////////////////////////////////////////////////////
+ // Redraw
+ //
+ // If a method changes box positions directly, redraw()
+ // should be called.
+ //
+ // If a method may also cause a snapback to happen, snapAndRedraw() should
+ // be called.
+ //
+ // If a method starts an animation to change the position of focused box,
+ // startAnimation() should be called.
+ //
+ // If time advances to change the box position, advanceAnimation() should
+ // be called.
+ ////////////////////////////////////////////////////////////////////////////
+ private void redraw() {
+ layoutAndSetPosition();
+ mListener.invalidate();
+ }
- // Check if we hit the edges; show edge effects if we do.
- if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
- int v = Math.round(-mScroller.getCurrVelocityX() * mCurrentScale);
- mEdgeView.onAbsorb(v, EdgeView.LEFT);
- } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
- int v = Math.round(mScroller.getCurrVelocityX() * mCurrentScale);
- mEdgeView.onAbsorb(v, EdgeView.RIGHT);
+ private void snapAndRedraw() {
+ mPlatform.startSnapback();
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ mBoxes.get(i).startSnapback();
+ }
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ mGaps.get(i).startSnapback();
+ }
+ redraw();
+ }
+
+ private void startAnimation(int targetX, int targetY, float targetScale,
+ int kind) {
+ boolean changed = false;
+ changed |= mPlatform.doAnimation(targetX, kind);
+ changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
+ if (changed) redraw();
+ }
+
+ public void advanceAnimation() {
+ boolean changed = false;
+ changed |= mPlatform.advanceAnimation();
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ changed |= mBoxes.get(i).advanceAnimation();
+ }
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ changed |= mGaps.get(i).advanceAnimation();
+ }
+ if (changed) redraw();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Layout
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Returns the display width of this box.
+ private int widthOf(Box b) {
+ return (int) (b.mImageW * b.mCurrentScale + 0.5f);
+ }
+
+ // Returns the display height of this box.
+ private int heightOf(Box b) {
+ return (int) (b.mImageH * b.mCurrentScale + 0.5f);
+ }
+
+ // Returns the display width of this box, using the given scale.
+ private int widthOf(Box b, float scale) {
+ return (int) (b.mImageW * scale + 0.5f);
+ }
+
+ // Returns the display height of this box, using the given scale.
+ private int heightOf(Box b, float scale) {
+ return (int) (b.mImageH * scale + 0.5f);
+ }
+
+ // Convert the information in mPlatform and mBoxes to mRects, so the user
+ // can get the position of each box by getPosition().
+ //
+ // Note the loop index goes from inside-out because each box's X coordinate
+ // is relative to its anchor box (except the focused box).
+ private void layoutAndSetPosition() {
+ // layout box 0 (focused box)
+ convertBoxToRect(0);
+ for (int i = 1; i <= BOX_MAX; i++) {
+ // layout box i and -i
+ convertBoxToRect(i);
+ convertBoxToRect(-i);
+ }
+ //dumpState();
+ }
+
+ private void dumpState() {
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
}
- if (oldY > mBoundTop && mCurrentY == mBoundTop) {
- int v = Math.round(-mScroller.getCurrVelocityY() * mCurrentScale);
- mEdgeView.onAbsorb(v, EdgeView.TOP);
- } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
- int v = Math.round(mScroller.getCurrVelocityY() * mCurrentScale);
- mEdgeView.onAbsorb(v, EdgeView.BOTTOM);
+ dumpRect(0);
+ for (int i = 1; i <= BOX_MAX; i++) {
+ dumpRect(i);
+ dumpRect(-i);
+ }
+
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ for (int j = i + 1; j <= BOX_MAX; j++) {
+ if (Rect.intersects(mRects.get(i), mRects.get(j))) {
+ Log.d(TAG, "rect " + i + " and rect " + j + "intersects!");
+ }
+ }
}
}
- // Interpolates mCurrent{X,Y,Scale} given the progress in [0, 1].
- private void linearInterpolate(float progress) {
- // To linearly interpolate the position on view coordinates, we do the
- // following steps:
- // (1) convert a bitmap position (x, y) to view coordinates:
- // from: (x - mFromX) * mFromScale + mViewW / 2
- // to: (x - mToX) * mToScale + mViewW / 2
- // (2) interpolate between the "from" and "to" coordinates:
- // (x - mFromX) * mFromScale * (1 - p) + (x - mToX) * mToScale * p
- // + mViewW / 2
- // should be equal to
- // (x - mCurrentX) * mCurrentScale + mViewW / 2
- // (3) The x-related terms in the above equation can be removed because
- // mFromScale * (1 - p) + ToScale * p = mCurrentScale
- // (4) Solve for mCurrentX, we have mCurrentX =
- // (mFromX * mFromScale * (1 - p) + mToX * mToScale * p) / mCurrentScale
- float fromX = mFromX * mFromScale;
- float toX = mToX * mToScale;
- float currentX = fromX + progress * (toX - fromX);
-
- float fromY = mFromY * mFromScale;
- float toY = mToY * mToScale;
- float currentY = fromY + progress * (toY - fromY);
-
- mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
- mCurrentX = Math.round(currentX / mCurrentScale);
- mCurrentY = Math.round(currentY / mCurrentScale);
+ private void dumpRect(int i) {
+ StringBuilder sb = new StringBuilder();
+ Rect r = mRects.get(i);
+ sb.append("Rect " + i + ":");
+ sb.append("(");
+ sb.append(r.centerX());
+ sb.append(",");
+ sb.append(r.centerY());
+ sb.append(") [");
+ sb.append(r.width());
+ sb.append("x");
+ sb.append(r.height());
+ sb.append("]");
+ Log.d(TAG, sb.toString());
}
- // Returns true if redraw is needed.
- private boolean startSnapbackIfNeeded() {
- if (mAnimationStartTime != NO_ANIMATION) return false;
- if (mInScale) return false;
- if (mAnimationKind == ANIM_KIND_SCROLL && mViewer.isDown()) {
- return false;
+ private void convertBoxToRect(int i) {
+ Box b = mBoxes.get(i);
+ Rect r = mRects.get(i);
+ int y = b.mCurrentY;
+ int w = widthOf(b);
+ int h = heightOf(b);
+ if (i == 0) {
+ int x = mPlatform.mCurrentX;
+ r.left = x - w / 2;
+ r.right = r.left + w;
+ } else if (i > 0) {
+ Rect a = mRects.get(i - 1);
+ Gap g = mGaps.get(i - 1);
+ r.left = a.right + g.mCurrentGap;
+ r.right = r.left + w;
+ } else { // i < 0
+ Rect a = mRects.get(i + 1);
+ Gap g = mGaps.get(i);
+ r.right = a.left - g.mCurrentGap;
+ r.left = r.right - w;
}
- return startSnapback();
+ r.top = y - h / 2;
+ r.bottom = r.top + h;
}
- public boolean startSnapback() {
- boolean needAnimation = false;
- float scale = mCurrentScale;
+ // Returns the position of a box.
+ public Rect getPosition(int index) {
+ return mRects.get(index);
+ }
- float scaleMin = mExtraScalingRange ?
- mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
- float scaleMax = mExtraScalingRange ?
- mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
+ ////////////////////////////////////////////////////////////////////////////
+ // Box management
+ ////////////////////////////////////////////////////////////////////////////
- if (mCurrentScale < scaleMin || mCurrentScale > scaleMax) {
- needAnimation = true;
- scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
+ // Initialize the platform to be at the view center.
+ private void initPlatform() {
+ mPlatform.mCurrentX = mViewW / 2;
+ mPlatform.mAnimationStartTime = NO_ANIMATION;
+ }
+
+ // Initialize a box to have the size of the view.
+ private void initBox(int index) {
+ Box b = mBoxes.get(index);
+ b.mImageW = mViewW;
+ b.mImageH = mViewH;
+ b.mUseViewSize = true;
+ b.mScaleMin = getMinimalScale(b.mImageW, b.mImageH);
+ b.mScaleMax = getMaximalScale(b.mImageW, b.mImageH);
+ b.mCurrentY = mViewH / 2;
+ b.mCurrentScale = b.mScaleMin;
+ b.mAnimationStartTime = NO_ANIMATION;
+ }
+
+ // Initialize a gap. This can only be called after the boxes around the gap
+ // has been initialized.
+ private void initGap(int index) {
+ Gap g = mGaps.get(index);
+ g.mDefaultSize = getDefaultGapSize(index);
+ g.mCurrentGap = g.mDefaultSize;
+ g.mAnimationStartTime = NO_ANIMATION;
+ }
+
+ private void initGap(int index, int size) {
+ Gap g = mGaps.get(index);
+ g.mDefaultSize = getDefaultGapSize(index);
+ g.mCurrentGap = size;
+ g.mAnimationStartTime = NO_ANIMATION;
+ }
+
+ private void debugMoveBox(int fromIndex[]) {
+ StringBuilder s = new StringBuilder("moveBox:");
+ for (int i = 0; i < fromIndex.length; i++) {
+ int j = fromIndex[i];
+ if (j == Integer.MAX_VALUE) {
+ s.append(" N");
+ } else {
+ s.append(" ");
+ s.append(fromIndex[i]);
+ }
+ }
+ Log.d(TAG, s.toString());
+ }
+
+ // Move the boxes: it may indicate focus change, box deleted, box appearing,
+ // box reordered, etc.
+ //
+ // Each element in the fromIndex array indicates where each box was in the
+ // old array. If the value is Integer.MAX_VALUE (pictured as N below), it
+ // means the box is new.
+ //
+ // For example:
+ // N N N N N N N -- all new boxes
+ // -3 -2 -1 0 1 2 3 -- nothing changed
+ // -2 -1 0 1 2 3 N -- focus goes to the next box
+ // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
+ // -3 -2 -1 1 2 3 N -- the focused box was deleted.
+ public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext) {
+ //debugMoveBox(fromIndex);
+ mHasPrev = hasPrev;
+ mHasNext = hasNext;
+ RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
+
+ // 1. Get the absolute X coordiates for the boxes.
+ layoutAndSetPosition();
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ Box b = mBoxes.get(i);
+ Rect r = mRects.get(i);
+ b.mAbsoluteX = r.centerX();
}
- calculateStableBound(scale, sHorizontalSlack);
- int x = Utils.clamp(mCurrentX, mBoundLeft, mBoundRight);
- int y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
-
- if (mCurrentX != x || mCurrentY != y || mCurrentScale != scale) {
- needAnimation = true;
+ // 2. copy boxes and gaps to temporary storage.
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ mTempBoxes.put(i, mBoxes.get(i));
+ mBoxes.put(i, null);
+ }
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ mTempGaps.put(i, mGaps.get(i));
+ mGaps.put(i, null);
}
- if (needAnimation) {
- startAnimation(x, y, scale, ANIM_KIND_SNAPBACK);
+ // 3. move back boxes that are used in the new array.
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ int j = from.get(i);
+ if (j == Integer.MAX_VALUE) continue;
+ mBoxes.put(i, mTempBoxes.get(j));
+ mTempBoxes.put(j, null);
}
- return needAnimation;
+ // 4. move back gaps if both boxes around it are kept together.
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ int j = from.get(i);
+ if (j == Integer.MAX_VALUE) continue;
+ int k = from.get(i + 1);
+ if (k == Integer.MAX_VALUE) continue;
+ if (j + 1 == k) {
+ mGaps.put(i, mTempGaps.get(j));
+ mTempGaps.put(j, null);
+ }
+ }
+
+ // 5. recycle the boxes that are not used in the new array.
+ int k = -BOX_MAX;
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ if (mBoxes.get(i) != null) continue;
+ while (mTempBoxes.get(k) == null) {
+ k++;
+ }
+ mBoxes.put(i, mTempBoxes.get(k++));
+ initBox(i);
+ }
+
+ // 6. Now give the recycled box a reasonable absolute X position.
+ //
+ // First try to find the first and the last box which the absolute X
+ // position is known.
+ int first, last;
+ for (first = -BOX_MAX; first <= BOX_MAX; first++) {
+ if (from.get(first) != Integer.MAX_VALUE) break;
+ }
+ for (last = BOX_MAX; last >= -BOX_MAX; last--) {
+ if (from.get(last) != Integer.MAX_VALUE) break;
+ }
+ // If there is no box has known X position at all, make the focused one
+ // as known.
+ if (first > BOX_MAX) {
+ mBoxes.get(0).mAbsoluteX = mPlatform.mCurrentX;
+ first = last = 0;
+ }
+ // Now for those boxes between first and last, just assign the same
+ // position as the next box. (We can do better, but this should be
+ // rare). For the boxes before first or after last, we will use a new
+ // default gap size below.
+ for (int i = last - 1; i > first; i--) {
+ if (from.get(i) != Integer.MAX_VALUE) continue;
+ mBoxes.get(i).mAbsoluteX = mBoxes.get(i + 1).mAbsoluteX;
+ }
+
+ // 7. recycle the gaps that are not used in the new array.
+ k = -BOX_MAX;
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ if (mGaps.get(i) != null) continue;
+ while (mTempGaps.get(k) == null) {
+ k++;
+ }
+ mGaps.put(i, mTempGaps.get(k++));
+ Box a = mBoxes.get(i);
+ Box b = mBoxes.get(i + 1);
+ int wa = widthOf(a);
+ int wb = widthOf(b);
+ if (i >= first && i < last) {
+ int g = b.mAbsoluteX - a.mAbsoluteX - wb / 2 - (wa - wa / 2);
+ initGap(i, g);
+ } else {
+ initGap(i);
+ }
+ }
+
+ // 8. offset the Platform position
+ int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
+ mPlatform.mCurrentX += dx;
+ mPlatform.mFromX += dx;
+ mPlatform.mToX += dx;
+ mPlatform.mFlingOffset += dx;
+
+ snapAndRedraw();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Public utilities
+ ////////////////////////////////////////////////////////////////////////////
+
+ public float getMinimalScale(int imageW, int imageH) {
+ float wFactor = 1.0f;
+ float hFactor = 1.0f;
+
+ if (mFilmMode) {
+ if (mViewH > mViewW) { // portrait
+ wFactor = FILM_MODE_PORTRAIT_WIDTH;
+ hFactor = FILM_MODE_PORTRAIT_HEIGHT;
+ } else { // landscape
+ wFactor = FILM_MODE_LANDSCAPE_WIDTH;
+ hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
+ }
+ }
+
+ float s = Math.min(wFactor * mViewW / imageW,
+ hFactor * mViewH / imageH);
+ return Math.min(SCALE_LIMIT, s);
+ }
+
+ public float getMaximalScale(int imageW, int imageH) {
+ return mFilmMode ? getMinimalScale(imageW, imageH) : SCALE_LIMIT;
+ }
+
+ public boolean isAtMinimalScale() {
+ Box b = mBoxes.get(0);
+ return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
+ }
+
+ public int getImageWidth() {
+ Box b = mBoxes.get(0);
+ return b.mImageW;
+ }
+
+ public int getImageHeight() {
+ Box b = mBoxes.get(0);
+ return b.mImageH;
+ }
+
+ public float getImageScale() {
+ Box b = mBoxes.get(0);
+ return b.mCurrentScale;
+ }
+
+ public int getImageAtEdges() {
+ Box b = mBoxes.get(0);
+ Platform p = mPlatform;
+ calculateStableBound(b.mCurrentScale);
+ int edges = 0;
+ if (p.mCurrentX <= mBoundLeft) {
+ edges |= IMAGE_AT_RIGHT_EDGE;
+ }
+ if (p.mCurrentX >= mBoundRight) {
+ edges |= IMAGE_AT_LEFT_EDGE;
+ }
+ if (b.mCurrentY <= mBoundTop) {
+ edges |= IMAGE_AT_BOTTOM_EDGE;
+ }
+ if (b.mCurrentY >= mBoundBottom) {
+ edges |= IMAGE_AT_TOP_EDGE;
+ }
+ return edges;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Private utilities
+ ////////////////////////////////////////////////////////////////////////////
+
+ private float getMinimalScale(Box b) {
+ return getMinimalScale(b.mImageW, b.mImageH);
+ }
+
+ private float getMaxmimalScale(Box b) {
+ return getMaximalScale(b.mImageW, b.mImageH);
+ }
+
+ private static boolean isAlmostEqual(float a, float b) {
+ float diff = a - b;
+ return (diff < 0 ? -diff : diff) < 0.02f;
}
// Calculates the stable region of mCurrent{X/Y}, where "stable" means
@@ -594,95 +994,439 @@
// An extra parameter "horizontalSlack" (which has the value of 0 usually)
// is used to extend the stable region by some pixels on each side
// horizontally.
- private void calculateStableBound(float scale) {
- calculateStableBound(scale, 0f);
- }
+ private void calculateStableBound(float scale, int horizontalSlack) {
+ Box b = mBoxes.get(0);
- private void calculateStableBound(float scale, float horizontalSlack) {
- // The number of pixels between the center of the view
- // and the edge when the edge is aligned.
- mBoundLeft = (int) Math.ceil((mViewW - horizontalSlack) / (2 * scale));
- mBoundRight = mImageW - mBoundLeft;
- mBoundTop = (int) Math.ceil(mViewH / (2 * scale));
- mBoundBottom = mImageH - mBoundTop;
+ // The width and height of the box in number of view pixels
+ int w = widthOf(b, scale);
+ int h = heightOf(b, scale);
+
+ // When the edge of the view is aligned with the edge of the box
+ mBoundLeft = (mViewW - horizontalSlack) - w / 2;
+ mBoundRight = mViewW - mBoundLeft;
+ mBoundTop = mViewH - h / 2;
+ mBoundBottom = mViewH - mBoundTop;
// If the scaled height is smaller than the view height,
// force it to be in the center.
- if (Math.floor(mImageH * scale) <= mViewH) {
- mBoundTop = mBoundBottom = mImageH / 2;
+ if (viewTallerThanScaledImage(scale)) {
+ mBoundTop = mBoundBottom = mViewH / 2;
}
// Same for width
- if (Math.floor(mImageW * scale) <= mViewW) {
- mBoundLeft = mBoundRight = mImageW / 2;
+ if (viewWiderThanScaledImage(scale)) {
+ mBoundLeft = mBoundRight = mViewW / 2;
}
}
- private boolean useCurrentValueAsTarget() {
- return mAnimationStartTime == NO_ANIMATION ||
- mAnimationKind == ANIM_KIND_SNAPBACK ||
- mAnimationKind == ANIM_KIND_FLING;
+ private void calculateStableBound(float scale) {
+ calculateStableBound(scale, 0);
}
- private float getTargetScale() {
- return useCurrentValueAsTarget() ? mCurrentScale : mToScale;
+ private boolean viewTallerThanScaledImage(float scale) {
+ return mViewH >= heightOf(mBoxes.get(0), scale);
}
- private int getTargetX() {
- return useCurrentValueAsTarget() ? mCurrentX : mToX;
+ private boolean viewWiderThanScaledImage(float scale) {
+ return mViewW >= widthOf(mBoxes.get(0), scale);
}
- private int getTargetY() {
- return useCurrentValueAsTarget() ? mCurrentY : mToY;
+ private float getTargetScale(Box b) {
+ return useCurrentValueAsTarget(b) ? b.mCurrentScale : b.mToScale;
}
- public RectF getImageBounds() {
- float points[] = mTempPoints;
+ private int getTargetX(Platform p) {
+ return useCurrentValueAsTarget(p) ? p.mCurrentX : p.mToX;
+ }
- /*
- * (p0,p1)----------(p2,p3)
- * | |
- * | |
- * (p4,p5)----------(p6,p7)
- */
- points[0] = points[4] = -mCurrentX;
- points[1] = points[3] = -mCurrentY;
- points[2] = points[6] = mImageW - mCurrentX;
- points[5] = points[7] = mImageH - mCurrentY;
+ private int getTargetY(Box b) {
+ return useCurrentValueAsTarget(b) ? b.mCurrentY : b.mToY;
+ }
- RectF rect = mTempRect;
- rect.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
- Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
+ private boolean useCurrentValueAsTarget(Animatable a) {
+ return a.mAnimationStartTime == NO_ANIMATION ||
+ a.mAnimationKind == ANIM_KIND_SNAPBACK ||
+ a.mAnimationKind == ANIM_KIND_FLING;
+ }
- float scale = mCurrentScale;
- float offsetX = mViewW / 2;
- float offsetY = mViewH / 2;
- for (int i = 0; i < 4; ++i) {
- float x = points[i + i] * scale + offsetX;
- float y = points[i + i + 1] * scale + offsetY;
- if (x < rect.left) rect.left = x;
- if (x > rect.right) rect.right = x;
- if (y < rect.top) rect.top = y;
- if (y > rect.bottom) rect.bottom = y;
+ // Returns the index of the anchor box.
+ private int anchorIndex(int i) {
+ if (i > 0) return i - 1;
+ if (i < 0) return i + 1;
+ throw new IllegalArgumentException();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Animatable: an thing which can do animation.
+ ////////////////////////////////////////////////////////////////////////////
+ private abstract static class Animatable {
+ public long mAnimationStartTime;
+ public int mAnimationKind;
+ public int mAnimationDuration;
+
+ // This should be overidden in subclass to change the animation values
+ // give the progress value in [0, 1].
+ protected abstract boolean interpolate(float progress);
+ public abstract boolean startSnapback();
+
+ // Returns true if the animation values changes, so things need to be
+ // redrawn.
+ public boolean advanceAnimation() {
+ if (mAnimationStartTime == NO_ANIMATION) {
+ return false;
+ }
+ if (mAnimationStartTime == LAST_ANIMATION) {
+ mAnimationStartTime = NO_ANIMATION;
+ return startSnapback();
+ }
+
+ float progress;
+ if (mAnimationDuration == 0) {
+ progress = 1;
+ } else {
+ long now = AnimationTime.get();
+ progress =
+ (float) (now - mAnimationStartTime) / mAnimationDuration;
+ }
+
+ if (progress >= 1) {
+ progress = 1;
+ } else {
+ progress = applyInterpolationCurve(mAnimationKind, progress);
+ }
+
+ boolean done = interpolate(progress);
+
+ if (done) {
+ mAnimationStartTime = LAST_ANIMATION;
+ }
+
+ return true;
}
- return rect;
+
+ private static float applyInterpolationCurve(int kind, float progress) {
+ float f = 1 - progress;
+ switch (kind) {
+ case ANIM_KIND_SCROLL:
+ case ANIM_KIND_FLING:
+ case ANIM_KIND_CAPTURE:
+ progress = 1 - f; // linear
+ break;
+ case ANIM_KIND_SCALE:
+ progress = 1 - f * f; // quadratic
+ break;
+ case ANIM_KIND_SNAPBACK:
+ case ANIM_KIND_ZOOM:
+ case ANIM_KIND_SLIDE:
+ case ANIM_KIND_OPENING:
+ progress = 1 - f * f * f * f * f; // x^5
+ break;
+ }
+ return progress;
+ }
}
- public int getImageWidth() {
- return mImageW;
+ ////////////////////////////////////////////////////////////////////////////
+ // Platform: captures the global X movement.
+ ////////////////////////////////////////////////////////////////////////////
+ private class Platform extends Animatable {
+ public int mCurrentX, mFromX, mToX;
+ public int mFlingOffset;
+
+ @Override
+ public boolean startSnapback() {
+ if (mAnimationStartTime != NO_ANIMATION) return false;
+ if (mAnimationKind == ANIM_KIND_SCROLL
+ && mListener.isHolding()) return false;
+
+ Box b = mBoxes.get(0);
+ float scaleMin = mExtraScalingRange ?
+ b.mScaleMin * SCALE_MIN_EXTRA : b.mScaleMin;
+ float scaleMax = mExtraScalingRange ?
+ b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
+ float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
+ int x = mCurrentX;
+ if (mFilmMode) {
+ if (!mHasNext) x = Math.max(x, mViewW / 2);
+ if (!mHasPrev) x = Math.min(x, mViewW / 2);
+ } else {
+ calculateStableBound(scale, HORIZONTAL_SLACK);
+ x = Utils.clamp(x, mBoundLeft, mBoundRight);
+ }
+ if (mCurrentX != x) {
+ return doAnimation(x, ANIM_KIND_SNAPBACK);
+ }
+ return false;
+ }
+
+ // Starts an animation for the platform.
+ public boolean doAnimation(int targetX, int kind) {
+ if (mCurrentX == targetX) return false;
+ mAnimationKind = kind;
+ mFromX = mCurrentX;
+ mToX = targetX;
+ mAnimationStartTime = AnimationTime.startTime();
+ mAnimationDuration = ANIM_TIME[kind];
+ mFlingOffset = 0;
+ advanceAnimation();
+ return true;
+ }
+
+ @Override
+ protected boolean interpolate(float progress) {
+ if (mAnimationKind == ANIM_KIND_FLING) {
+ return mFilmMode
+ ? interpolateFlingFilm(progress)
+ : interpolateFlingPage(progress);
+ } else {
+ return interpolateLinear(progress);
+ }
+ }
+
+ private boolean interpolateFlingFilm(float progress) {
+ mFilmScroller.computeScrollOffset();
+ mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
+
+ int dir = EdgeView.INVALID_DIRECTION;
+ if (mCurrentX < mViewW / 2) {
+ if (!mHasNext) {
+ dir = EdgeView.RIGHT;
+ }
+ } else if (mCurrentX > mViewW / 2) {
+ if (!mHasPrev) {
+ dir = EdgeView.LEFT;
+ }
+ }
+ if (dir != EdgeView.INVALID_DIRECTION) {
+ int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
+ mListener.onAbsorb(v, dir);
+ mFilmScroller.forceFinished(true);
+ mCurrentX = mViewW / 2;
+ }
+ return mFilmScroller.isFinished();
+ }
+
+ private boolean interpolateFlingPage(float progress) {
+ mPageScroller.computeScrollOffset(progress);
+ Box b = mBoxes.get(0);
+ calculateStableBound(b.mCurrentScale);
+
+ int oldX = mCurrentX;
+ mCurrentX = mPageScroller.getCurrX();
+
+ // Check if we hit the edges; show edge effects if we do.
+ if (oldX > mBoundLeft && mCurrentX == mBoundLeft) {
+ int v = (int) (-mPageScroller.getCurrVelocityX() + 0.5f);
+ mListener.onAbsorb(v, EdgeView.RIGHT);
+ } else if (oldX < mBoundRight && mCurrentX == mBoundRight) {
+ int v = (int) (mPageScroller.getCurrVelocityX() + 0.5f);
+ mListener.onAbsorb(v, EdgeView.LEFT);
+ }
+
+ return progress >= 1;
+ }
+
+ private boolean interpolateLinear(float progress) {
+ // Other animations
+ if (progress >= 1) {
+ mCurrentX = mToX;
+ return true;
+ } else {
+ if (mAnimationKind == ANIM_KIND_CAPTURE) {
+ progress = CaptureAnimation.calculateSlide(progress);
+ mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
+ return false;
+ } else {
+ mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
+ return (mCurrentX == mToX);
+ }
+ }
+ }
}
- public int getImageHeight() {
- return mImageH;
+ ////////////////////////////////////////////////////////////////////////////
+ // Box: represents a rectangular area which shows a picture.
+ ////////////////////////////////////////////////////////////////////////////
+ private class Box extends Animatable {
+ // Size of the bitmap
+ public int mImageW, mImageH;
+
+ // This is true if we assume the image size is the same as view size
+ // until we know the actual size of image. This is also used to
+ // determine if there is an image ready to show.
+ public boolean mUseViewSize;
+
+ // The minimum and maximum scale we allow for this box.
+ public float mScaleMin, mScaleMax;
+
+ // The X/Y value indicates where the center of the box is on the view
+ // coordinate. We always keep the mCurrent{X,Y,Scale} sync with the
+ // actual values used currently. Note that the X values are implicitly
+ // defined by Platform and Gaps.
+ public int mCurrentY, mFromY, mToY;
+ public float mCurrentScale, mFromScale, mToScale;
+
+ // The absolute X coordinate of the center of the box. This is only used
+ // during moveBox().
+ public int mAbsoluteX;
+
+ @Override
+ public boolean startSnapback() {
+ if (mAnimationStartTime != NO_ANIMATION) return false;
+ if (mAnimationKind == ANIM_KIND_SCROLL
+ && mListener.isHolding()) return false;
+ if (mInScale && this == mBoxes.get(0)) return false;
+
+ int y;
+ float scale;
+
+ if (this == mBoxes.get(0)) {
+ float scaleMin = mExtraScalingRange ?
+ mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
+ float scaleMax = mExtraScalingRange ?
+ mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
+ scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
+ if (mFilmMode) {
+ y = mViewH / 2;
+ } else {
+ calculateStableBound(scale, HORIZONTAL_SLACK);
+ y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
+ }
+ } else {
+ y = mViewH / 2;
+ scale = mScaleMin;
+ }
+
+ if (mCurrentY != y || mCurrentScale != scale) {
+ return doAnimation(y, scale, ANIM_KIND_SNAPBACK);
+ }
+ return false;
+ }
+
+ private boolean doAnimation(int targetY, float targetScale, int kind) {
+ targetScale = Utils.clamp(targetScale,
+ SCALE_MIN_EXTRA * mScaleMin,
+ SCALE_MAX_EXTRA * mScaleMax);
+
+ // If the scaled height is smaller than the view height, force it to be
+ // in the center. (We do this for height only, not width, because the
+ // user may want to scroll to the previous/next image.)
+ if (!mInScale && viewTallerThanScaledImage(targetScale)) {
+ targetY = mViewH / 2;
+ }
+
+ if (mCurrentY == targetY && mCurrentScale == targetScale
+ && kind != ANIM_KIND_CAPTURE) {
+ return false;
+ }
+
+ // Now starts an animation for the box.
+ mAnimationKind = kind;
+ mFromY = mCurrentY;
+ mFromScale = mCurrentScale;
+ mToY = targetY;
+ mToScale = targetScale;
+ mAnimationStartTime = AnimationTime.startTime();
+ mAnimationDuration = ANIM_TIME[kind];
+ advanceAnimation();
+ return true;
+ }
+
+ @Override
+ protected boolean interpolate(float progress) {
+ if (mAnimationKind == ANIM_KIND_FLING) {
+ // Currently a Box can only be flung in page mode.
+ return interpolateFlingPage(progress);
+ } else {
+ return interpolateLinear(progress);
+ }
+ }
+
+ private boolean interpolateFlingPage(float progress) {
+ mPageScroller.computeScrollOffset(progress);
+ calculateStableBound(mCurrentScale);
+
+ int oldY = mCurrentY;
+ mCurrentY = mPageScroller.getCurrY();
+
+ // Check if we hit the edges; show edge effects if we do.
+ if (oldY > mBoundTop && mCurrentY == mBoundTop) {
+ int v = (int) (-mPageScroller.getCurrVelocityY() + 0.5f);
+ mListener.onAbsorb(v, EdgeView.BOTTOM);
+ } else if (oldY < mBoundBottom && mCurrentY == mBoundBottom) {
+ int v = (int) (mPageScroller.getCurrVelocityY() + 0.5f);
+ mListener.onAbsorb(v, EdgeView.TOP);
+ }
+
+ return progress >= 1;
+ }
+
+ private boolean interpolateLinear(float progress) {
+ if (progress >= 1) {
+ mCurrentY = mToY;
+ mCurrentScale = mToScale;
+ return true;
+ } else {
+ mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
+ mCurrentScale = mFromScale + progress * (mToScale - mFromScale);
+ if (mAnimationKind == ANIM_KIND_CAPTURE) {
+ float f = CaptureAnimation.calculateScale(progress);
+ mCurrentScale *= f;
+ return false;
+ } else {
+ return (mCurrentY == mToY && mCurrentScale == mToScale);
+ }
+ }
+ }
}
- public boolean isAtLeftEdge() {
- calculateStableBound(mCurrentScale);
- return mCurrentX <= mBoundLeft;
- }
+ ////////////////////////////////////////////////////////////////////////////
+ // Gap: represents a rectangular area which is between two boxes.
+ ////////////////////////////////////////////////////////////////////////////
+ private class Gap extends Animatable {
+ // The default gap size between two boxes. The value may vary for
+ // different image size of the boxes and for different modes (page or
+ // film).
+ public int mDefaultSize;
- public boolean isAtRightEdge() {
- calculateStableBound(mCurrentScale);
- return mCurrentX >= mBoundRight;
+ // The gap size between the two boxes.
+ public int mCurrentGap, mFromGap, mToGap;
+
+ @Override
+ public boolean startSnapback() {
+ if (mAnimationStartTime != NO_ANIMATION) return false;
+ return doAnimation(mDefaultSize, ANIM_KIND_SNAPBACK);
+ }
+
+ // Starts an animation for a gap.
+ public boolean doAnimation(int targetSize, int kind) {
+ if (mCurrentGap == targetSize && kind != ANIM_KIND_CAPTURE) {
+ return false;
+ }
+ mAnimationKind = kind;
+ mFromGap = mCurrentGap;
+ mToGap = targetSize;
+ mAnimationStartTime = AnimationTime.startTime();
+ mAnimationDuration = ANIM_TIME[mAnimationKind];
+ advanceAnimation();
+ return true;
+ }
+
+ @Override
+ protected boolean interpolate(float progress) {
+ if (progress >= 1) {
+ mCurrentGap = mToGap;
+ return true;
+ } else {
+ mCurrentGap = (int) (mFromGap + progress * (mToGap - mFromGap));
+ if (mAnimationKind == ANIM_KIND_CAPTURE) {
+ float f = CaptureAnimation.calculateScale(progress);
+ mCurrentGap = (int) (mCurrentGap * f);
+ return false;
+ } else {
+ return (mCurrentGap == mToGap);
+ }
+ }
+ }
}
}
diff --git a/src/com/android/gallery3d/ui/PositionRepository.java b/src/com/android/gallery3d/ui/PositionRepository.java
deleted file mode 100644
index 0b829fa..0000000
--- a/src/com/android/gallery3d/ui/PositionRepository.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import com.android.gallery3d.app.GalleryActivity;
-import com.android.gallery3d.common.Utils;
-
-import java.util.HashMap;
-import java.util.WeakHashMap;
-
-public class PositionRepository {
- private static final WeakHashMap<GalleryActivity, PositionRepository>
- sMap = new WeakHashMap<GalleryActivity, PositionRepository>();
-
- public static class Position implements Cloneable {
- public float x;
- public float y;
- public float z;
- public float theta;
- public float alpha;
-
- public Position() {
- }
-
- public Position(float x, float y, float z) {
- this(x, y, z, 0f, 1f);
- }
-
- public Position(float x, float y, float z, float ftheta, float alpha) {
- this.x = x;
- this.y = y;
- this.z = z;
- this.theta = ftheta;
- this.alpha = alpha;
- }
-
- @Override
- public Position clone() {
- try {
- return (Position) super.clone();
- } catch (CloneNotSupportedException e) {
- throw new AssertionError(); // we do support clone.
- }
- }
-
- public void set(Position another) {
- x = another.x;
- y = another.y;
- z = another.z;
- theta = another.theta;
- alpha = another.alpha;
- }
-
- public void set(float x, float y, float z, float ftheta, float alpha) {
- this.x = x;
- this.y = y;
- this.z = z;
- this.theta = ftheta;
- this.alpha = alpha;
- }
-
- @Override
- public boolean equals(Object object) {
- if (!(object instanceof Position)) return false;
- Position position = (Position) object;
- return x == position.x && y == position.y && z == position.z
- && theta == position.theta
- && alpha == position.alpha;
- }
-
- public static void interpolate(
- Position source, Position target, Position output, float progress) {
- if (progress < 1f) {
- output.set(
- Utils.interpolateScale(source.x, target.x, progress),
- Utils.interpolateScale(source.y, target.y, progress),
- Utils.interpolateScale(source.z, target.z, progress),
- Utils.interpolateAngle(source.theta, target.theta, progress),
- Utils.interpolateScale(source.alpha, target.alpha, progress));
- } else {
- output.set(target);
- }
- }
- }
-
- public static PositionRepository getInstance(GalleryActivity activity) {
- PositionRepository repository = sMap.get(activity);
- if (repository == null) {
- repository = new PositionRepository();
- sMap.put(activity, repository);
- }
- return repository;
- }
-
- private HashMap<Long, Position> mData = new HashMap<Long, Position>();
- private int mOffsetX;
- private int mOffsetY;
- private Position mTempPosition = new Position();
-
- public Position get(Long identity) {
- Position position = mData.get(identity);
- if (position == null) return null;
- mTempPosition.set(position);
- position = mTempPosition;
- position.x -= mOffsetX;
- position.y -= mOffsetY;
- return position;
- }
-
- public void setOffset(int offsetX, int offsetY) {
- mOffsetX = offsetX;
- mOffsetY = offsetY;
- }
-
- public void putPosition(Long identity, Position position) {
- Position clone = position.clone();
- clone.x += mOffsetX;
- clone.y += mOffsetY;
- mData.put(identity, clone);
- }
-
- public void clear() {
- mData.clear();
- }
-}
diff --git a/src/com/android/gallery3d/ui/ProgressSpinner.java b/src/com/android/gallery3d/ui/ProgressSpinner.java
index e4d6024..4f9381c 100644
--- a/src/com/android/gallery3d/ui/ProgressSpinner.java
+++ b/src/com/android/gallery3d/ui/ProgressSpinner.java
@@ -16,10 +16,10 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.R;
-
import android.content.Context;
+import com.android.gallery3d.R;
+
public class ProgressSpinner {
private static float ROTATE_SPEED_OUTER = 1080f / 3500f;
private static float ROTATE_SPEED_INNER = -720f / 3500f;
@@ -55,7 +55,7 @@
}
public void draw(GLCanvas canvas, int x, int y) {
- long now = canvas.currentAnimationTimeMillis();
+ long now = AnimationTime.get();
if (mAnimationTimestamp == -1) mAnimationTimestamp = now;
mOuterDegree += (now - mAnimationTimestamp) * ROTATE_SPEED_OUTER;
mInnerDegree += (now - mAnimationTimestamp) * ROTATE_SPEED_INNER;
@@ -68,7 +68,7 @@
canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
- canvas.translate(x + mWidth / 2, y + mHeight / 2, 0);
+ canvas.translate(x + mWidth / 2, y + mHeight / 2);
canvas.rotate(mInnerDegree, 0, 0, 1);
mOuter.draw(canvas, -mOuter.getWidth() / 2, -mOuter.getHeight() / 2);
canvas.rotate(mOuterDegree - mInnerDegree, 0, 0, 1);
diff --git a/src/com/android/gallery3d/ui/Raw2DTexture.java b/src/com/android/gallery3d/ui/Raw2DTexture.java
new file mode 100644
index 0000000..c23bf94
--- /dev/null
+++ b/src/com/android/gallery3d/ui/Raw2DTexture.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.opengl.GLU;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+/**
+ * A wrapper class of GL 2D texture.
+ */
+public class Raw2DTexture extends BasicTexture {
+ private static final int[] sTextureId = new int[1];
+ // OpenGL related fields used when copy.
+ private static final int[] sBufferName = new int[1];
+ private int mFBO; // Frame buffer object.
+
+ // We need copy from another texture when in camera capture animation.
+ // The copy is done through a framebuffer object with an attached
+ // destination texture. The source texture is rendered to the framebuffer
+ // and the data will be stored in the destination texture.
+ public static void copy(GLCanvas canvas, BasicTexture src, Raw2DTexture dst) {
+ int[] viewPort = new int[4];
+ GL11 gl11 = canvas.getGLInstance();
+ GL11ExtensionPack gl11ep = (GL11ExtensionPack) gl11;
+
+ if (!dst.isLoaded(canvas)) {
+ dst.prepare(canvas.getGLInstance());
+ }
+
+ gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, dst.mFBO);
+ gl11ep.glFramebufferTexture2DOES(
+ GL11ExtensionPack.GL_FRAMEBUFFER_OES,
+ GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES,
+ GL11.GL_TEXTURE_2D,
+ dst.getId(), 0);
+ checkFramebufferStatus(gl11ep);
+
+ // Draw the source onto our destination.
+ // The texture coords and vertex pointer are already set properly. We don't
+ // need to set again.
+ gl11.glBindTexture(src.getTarget(), src.getId());
+ boolean targetEnabled = gl11.glIsEnabled(src.getTarget());
+ gl11.glEnable(src.getTarget());
+ boolean scissorEnabled = gl11.glIsEnabled(GL11.GL_SCISSOR_TEST);
+ gl11.glDisable(GL11.GL_SCISSOR_TEST);
+
+ // Set the texture matrix.
+ gl11.glMatrixMode(GL11.GL_TEXTURE);
+ gl11.glPushMatrix();
+ gl11.glLoadIdentity();
+
+ // Set the view port.
+ gl11.glGetIntegerv(GL11.GL_VIEWPORT, viewPort, 0);
+ gl11.glViewport(0, 0, dst.getTextureWidth(), dst.getTextureHeight());
+
+ // Set the projection matrix.
+ gl11.glMatrixMode(GL11.GL_PROJECTION);
+ gl11.glPushMatrix();
+ gl11.glLoadIdentity();
+ GLU.gluOrtho2D(gl11, 0, 1, 0, 1);
+
+ // Set the modelview matrix.
+ gl11.glMatrixMode(GL11.GL_MODELVIEW);
+ gl11.glPushMatrix();
+ gl11.glLoadIdentity();
+
+ // Draw the texture.
+ gl11.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 4);
+
+ // Clear.
+ if (!targetEnabled) gl11.glDisable(src.getTarget());
+ if (scissorEnabled) gl11.glEnable(GL11.GL_SCISSOR_TEST);
+ gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0);
+ gl11.glBindTexture(src.getTarget(), 0);
+ gl11.glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3]);
+ gl11.glMatrixMode(GL11.GL_TEXTURE);
+ gl11.glPopMatrix();
+ gl11.glMatrixMode(GL11.GL_PROJECTION);
+ gl11.glPopMatrix();
+ gl11.glMatrixMode(GL11.GL_MODELVIEW);
+ gl11.glPopMatrix();
+ GLId.glDeleteFramebuffers(gl11ep, 1, dst.sBufferName, 0);
+ }
+
+ public Raw2DTexture(int w, int h) {
+ setSize(w, h);
+ GLId.glGenTextures(1, sTextureId, 0);
+ mId = sTextureId[0];
+ GLId.glGenBuffers(1, sBufferName, 0);
+ mFBO = sBufferName[0];
+ }
+
+ private void prepare(GL11 gl11) {
+ gl11.glBindTexture(GL11.GL_TEXTURE_2D, mId);
+ gl11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
+ gl11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
+ gl11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
+ gl11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
+ gl11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA,
+ getTextureWidth(), getTextureHeight(),
+ 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, null);
+ gl11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
+ mState = UploadedTexture.STATE_LOADED;
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ if (!isLoaded(canvas)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+
+ public boolean isOpaque() {
+ return true;
+ }
+
+ @Override
+ public void yield() {
+ // we cannot free the texture because we have no backup.
+ }
+
+ private static void checkFramebufferStatus(GL11ExtensionPack gl11ep) {
+ int status = gl11ep.glCheckFramebufferStatusOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES);
+ if (status != GL11ExtensionPack.GL_FRAMEBUFFER_COMPLETE_OES) {
+ String msg = "";
+ switch (status) {
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
+ msg = "FRAMEBUFFER_FORMATS"; break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
+ msg = "FRAMEBUFFER_ATTACHMENT"; break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
+ msg = "FRAMEBUFFER_MISSING_ATTACHMENT"; break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_OES:
+ msg = "FRAMEBUFFER_DRAW_BUFFER"; break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_OES:
+ msg = "FRAMEBUFFER_READ_BUFFER"; break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_UNSUPPORTED_OES:
+ msg = "FRAMEBUFFER_UNSUPPORTED"; break;
+ case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
+ msg = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; break;
+ }
+ throw new RuntimeException(msg + ":" + Integer.toHexString(status));
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/ui/RawTexture.java b/src/com/android/gallery3d/ui/RawTexture.java
deleted file mode 100644
index c1be435..0000000
--- a/src/com/android/gallery3d/ui/RawTexture.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import javax.microedition.khronos.opengles.GL11;
-
-// RawTexture is used for texture created by glCopyTexImage2D.
-//
-// It will throw RuntimeException in onBind() if used with a different GL
-// context. It is only used internally by copyTexture() in GLCanvas.
-class RawTexture extends BasicTexture {
-
- private RawTexture(GLCanvas canvas, int id) {
- super(canvas, id, STATE_LOADED);
- }
-
- public static RawTexture newInstance(GLCanvas canvas) {
- int[] textureId = new int[1];
- GL11 gl = canvas.getGLInstance();
- gl.glGenTextures(1, textureId, 0);
- return new RawTexture(canvas, textureId[0]);
- }
-
- @Override
- protected boolean onBind(GLCanvas canvas) {
- if (mCanvasRef.get() != canvas) {
- throw new RuntimeException("cannot bind to different canvas");
- }
- return true;
- }
-
- public boolean isOpaque() {
- return true;
- }
-
- @Override
- public void yield() {
- // we cannot free the texture because we have no backup.
- }
-}
diff --git a/src/com/android/gallery3d/ui/RelativePosition.java b/src/com/android/gallery3d/ui/RelativePosition.java
new file mode 100644
index 0000000..0f2bfd8
--- /dev/null
+++ b/src/com/android/gallery3d/ui/RelativePosition.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+public class RelativePosition {
+ private float mAbsoluteX;
+ private float mAbsoluteY;
+ private float mReferenceX;
+ private float mReferenceY;
+
+ public void setAbsolutePosition(int absoluteX, int absoluteY) {
+ mAbsoluteX = absoluteX;
+ mAbsoluteY = absoluteY;
+ }
+
+ public void setReferencePosition(int x, int y) {
+ mReferenceX = x;
+ mReferenceY = y;
+ }
+
+ public float getX() {
+ return mAbsoluteX - mReferenceX;
+ }
+
+ public float getY() {
+ return mAbsoluteY - mReferenceY;
+ }
+}
diff --git a/src/com/android/gallery3d/ui/ResourceTexture.java b/src/com/android/gallery3d/ui/ResourceTexture.java
index 08fb891..1fa9d70 100644
--- a/src/com/android/gallery3d/ui/ResourceTexture.java
+++ b/src/com/android/gallery3d/ui/ResourceTexture.java
@@ -16,12 +16,12 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
-
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import com.android.gallery3d.common.Utils;
+
// ResourceTexture is a texture whose Bitmap is decoded from a resource.
// By default ResourceTexture is not opaque.
public class ResourceTexture extends UploadedTexture {
diff --git a/src/com/android/gallery3d/ui/ScreenNail.java b/src/com/android/gallery3d/ui/ScreenNail.java
new file mode 100644
index 0000000..0a16ab8
--- /dev/null
+++ b/src/com/android/gallery3d/ui/ScreenNail.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.gallery3d.ui;
+
+import android.graphics.RectF;
+
+public interface ScreenNail {
+ public int getWidth();
+ public int getHeight();
+ public void draw(GLCanvas canvas, int x, int y, int width, int height);
+
+ // We do not need to draw this ScreenNail in this frame.
+ public void noDraw();
+
+ // This ScreenNail will not be used anymore. Release related resources.
+ public void recycle();
+
+ // This is only used by TileImageView to back up the tiles not yet loaded.
+ public void draw(GLCanvas canvas, RectF source, RectF dest);
+}
diff --git a/src/com/android/gallery3d/ui/PositionProvider.java b/src/com/android/gallery3d/ui/ScreenNailHolder.java
similarity index 62%
rename from src/com/android/gallery3d/ui/PositionProvider.java
rename to src/com/android/gallery3d/ui/ScreenNailHolder.java
index 930c61e..a7d5417 100644
--- a/src/com/android/gallery3d/ui/PositionProvider.java
+++ b/src/com/android/gallery3d/ui/ScreenNailHolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 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,11 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.gallery3d.ui;
-import com.android.gallery3d.ui.PositionRepository.Position;
+import android.os.Parcel;
+import android.os.Parcelable;
-public interface PositionProvider {
- public Position getPosition(long identity, Position target);
+public abstract class ScreenNailHolder implements Parcelable {
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ public abstract ScreenNail attach();
+ public abstract void detach();
}
diff --git a/src/com/android/gallery3d/ui/ScrollBarView.java b/src/com/android/gallery3d/ui/ScrollBarView.java
index b33f03b..82d4800 100644
--- a/src/com/android/gallery3d/ui/ScrollBarView.java
+++ b/src/com/android/gallery3d/ui/ScrollBarView.java
@@ -16,8 +16,6 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.R;
-
import android.content.Context;
import android.graphics.Rect;
import android.util.TypedValue;
@@ -26,10 +24,6 @@
@SuppressWarnings("unused")
private static final String TAG = "ScrollBarView";
- public interface Listener {
- void onScrollBarPositionChanged(int position);
- }
-
private int mBarHeight;
private int mGripHeight;
@@ -40,7 +34,6 @@
private int mContentPosition;
private int mContentTotal;
- private Listener mListener;
private NinePatchTexture mScrollBarTexture;
public ScrollBarView(Context context, int gripHeight, int gripWidth) {
@@ -55,10 +48,6 @@
mGripHeight = gripHeight;
}
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
@Override
protected void onLayout(
boolean changed, int left, int top, int right, int bottom) {
@@ -94,13 +83,6 @@
mGripPosition = Math.round(r * mContentPosition);
}
- private void notifyContentPositionFromGrip() {
- if (mContentTotal <= 0) return;
- float r = (getWidth() - mGripWidth) / (float) mContentTotal;
- int newContentPosition = Math.round(mGripPosition / r);
- mListener.onScrollBarPositionChanged(newContentPosition);
- }
-
@Override
protected void render(GLCanvas canvas) {
super.render(canvas);
@@ -109,31 +91,4 @@
int y = (mBarHeight - mGripHeight) / 2;
mScrollBarTexture.draw(canvas, mGripPosition, y, mGripWidth, mGripHeight);
}
-
- // The onTouch() handler is disabled because now we don't want the user
- // to drag the bar (it's an indicator only).
- /*
- @Override
- protected boolean onTouch(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN: {
- int x = (int) event.getX();
- return (x >= mGripPosition && x < mGripPosition + mGripWidth);
- }
- case MotionEvent.ACTION_MOVE: {
- // Adjust x by mGripWidth / 2 so the center of the grip
- // matches the touch position.
- int x = (int) event.getX() - mGripWidth / 2;
- x = Utils.clamp(x, 0, getWidth() - mGripWidth);
- if (mGripPosition != x) {
- mGripPosition = x;
- notifyContentPositionFromGrip();
- invalidate();
- }
- break;
- }
- }
- return true;
- }
- */
}
diff --git a/src/com/android/gallery3d/ui/ScrollerHelper.java b/src/com/android/gallery3d/ui/ScrollerHelper.java
index 8423518..1ff633a 100644
--- a/src/com/android/gallery3d/ui/ScrollerHelper.java
+++ b/src/com/android/gallery3d/ui/ScrollerHelper.java
@@ -16,12 +16,12 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
-
import android.content.Context;
import android.view.ViewConfiguration;
import android.widget.OverScroller;
+import com.android.gallery3d.common.Utils;
+
public class ScrollerHelper {
private OverScroller mScroller;
private int mOverflingDistance;
@@ -84,7 +84,8 @@
// Returns the distance that over the scroll limit.
public int startScroll(int distance, int min, int max) {
int currPosition = mScroller.getCurrX();
- int finalPosition = mScroller.getFinalX();
+ int finalPosition = mScroller.isFinished() ? currPosition :
+ mScroller.getFinalX();
int newPosition = Utils.clamp(finalPosition + distance, min, max);
if (newPosition != currPosition) {
mScroller.startScroll(
diff --git a/src/com/android/gallery3d/ui/SelectionDrawer.java b/src/com/android/gallery3d/ui/SelectionDrawer.java
deleted file mode 100644
index 43b368f..0000000
--- a/src/com/android/gallery3d/ui/SelectionDrawer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.Path;
-
-import android.graphics.Rect;
-
-/**
- * Drawer class responsible for drawing selectable frame.
- */
-public abstract class SelectionDrawer {
- public static final int DATASOURCE_TYPE_NOT_CATEGORIZED = 0;
- public static final int DATASOURCE_TYPE_LOCAL = 1;
- public static final int DATASOURCE_TYPE_PICASA = 2;
- public static final int DATASOURCE_TYPE_MTP = 3;
- public static final int DATASOURCE_TYPE_CAMERA = 4;
-
- public abstract void prepareDrawing();
- public abstract void draw(GLCanvas canvas, Texture content,
- int width, int height, int rotation, Path path,
- int dataSourceType, int mediaType, boolean isPanorama,
- int labelBackgroundHeight, boolean wantCache, boolean isCaching);
- public abstract void drawFocus(GLCanvas canvas, int width, int height);
-
- public void draw(GLCanvas canvas, Texture content, int width, int height,
- int rotation, Path path, int mediaType, boolean isPanorama) {
- draw(canvas, content, width, height, rotation, path,
- DATASOURCE_TYPE_NOT_CATEGORIZED, mediaType, isPanorama,
- 0, false, false);
- }
-
- public static void drawWithRotation(GLCanvas canvas, Texture content,
- int x, int y, int width, int height, int rotation) {
- if (rotation != 0) {
- canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
- canvas.rotate(rotation, 0, 0, 1);
- }
-
- content.draw(canvas, x, y, width, height);
-
- if (rotation != 0) {
- canvas.restore();
- }
- }
-
- public static void drawFrame(GLCanvas canvas, NinePatchTexture frame,
- int x, int y, int width, int height) {
- Rect p = frame.getPaddings();
- frame.draw(canvas, x - p.left, y - p.top, width + p.left + p.right,
- height + p.top + p.bottom);
- }
-}
diff --git a/src/com/android/gallery3d/ui/SelectionManager.java b/src/com/android/gallery3d/ui/SelectionManager.java
index 2726e8a..1783b11 100644
--- a/src/com/android/gallery3d/ui/SelectionManager.java
+++ b/src/com/android/gallery3d/ui/SelectionManager.java
@@ -43,7 +43,6 @@
private boolean mInSelectionMode;
private boolean mAutoLeave = true;
private int mTotal;
- private Path mPressedPath;
public interface SelectionListener {
public void onSelectionModeChange(int mode);
@@ -135,14 +134,6 @@
}
}
- public void setPressedPath(Path path) {
- mPressedPath = path;
- }
-
- public boolean isPressedPath(Path path) {
- return path != null && path == mPressedPath;
- }
-
private static void expandMediaSet(ArrayList<Path> items, MediaSet set) {
int subCount = set.getSubMediaSetCount();
for (int i = 0; i < subCount; i++) {
@@ -216,8 +207,4 @@
mSourceMediaSet = set;
mTotal = -1;
}
-
- public MediaSet getSourceMediaSet() {
- return mSourceMediaSet;
- }
}
diff --git a/src/com/android/gallery3d/ui/SlideshowView.java b/src/com/android/gallery3d/ui/SlideshowView.java
index 79a6bf0..a057bb7 100644
--- a/src/com/android/gallery3d/ui/SlideshowView.java
+++ b/src/com/android/gallery3d/ui/SlideshowView.java
@@ -16,13 +16,14 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.anim.CanvasAnimation;
-import com.android.gallery3d.anim.FloatAnimation;
-
import android.graphics.Bitmap;
import android.graphics.PointF;
+import com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.anim.FloatAnimation;
+
import java.util.Random;
+
import javax.microedition.khronos.opengles.GL11;
public class SlideshowView extends GLView {
@@ -90,14 +91,14 @@
@Override
protected void render(GLCanvas canvas) {
- long currentTimeMillis = canvas.currentAnimationTimeMillis();
- boolean requestRender = mTransitionAnimation.calculate(currentTimeMillis);
+ long animTime = AnimationTime.get();
+ boolean requestRender = mTransitionAnimation.calculate(animTime);
GL11 gl = canvas.getGLInstance();
gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
float alpha = mPrevTexture == null ? 1f : mTransitionAnimation.get();
if (mPrevTexture != null && alpha != 1f) {
- requestRender |= mPrevAnimation.calculate(currentTimeMillis);
+ requestRender |= mPrevAnimation.calculate(animTime);
canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
canvas.setAlpha(1f - alpha);
mPrevAnimation.apply(canvas);
@@ -107,7 +108,7 @@
canvas.restore();
}
if (mCurrentTexture != null) {
- requestRender |= mCurrentAnimation.calculate(currentTimeMillis);
+ requestRender |= mCurrentAnimation.calculate(animTime);
canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
canvas.setAlpha(alpha);
mCurrentAnimation.apply(canvas);
@@ -148,7 +149,7 @@
float centerX = viewWidth / 2 + mMovingVector.x * mProgress;
float centerY = viewHeight / 2 + mMovingVector.y * mProgress;
- canvas.translate(centerX, centerY, 0);
+ canvas.translate(centerX, centerY);
canvas.scale(scale, scale, 0);
}
diff --git a/src/com/android/gallery3d/ui/SlotView.java b/src/com/android/gallery3d/ui/SlotView.java
index 3e0e2f2..bf3a55d 100644
--- a/src/com/android/gallery3d/ui/SlotView.java
+++ b/src/com/android/gallery3d/ui/SlotView.java
@@ -24,35 +24,40 @@
import android.view.animation.DecelerateInterpolator;
import com.android.gallery3d.anim.Animation;
+import com.android.gallery3d.app.GalleryActivity;
import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.ui.PositionRepository.Position;
-import com.android.gallery3d.util.LinkedNode;
-
-import java.util.ArrayList;
-import java.util.HashMap;
public class SlotView extends GLView {
@SuppressWarnings("unused")
private static final String TAG = "SlotView";
private static final boolean WIDE = true;
-
private static final int INDEX_NONE = -1;
+ public static final int RENDER_MORE_PASS = 1;
+ public static final int RENDER_MORE_FRAME = 2;
+
public interface Listener {
public void onDown(int index);
- public void onUp();
+ public void onUp(boolean followedByLongPress);
public void onSingleTapUp(int index);
public void onLongTap(int index);
public void onScrollPositionChanged(int position, int total);
}
public static class SimpleListener implements Listener {
- public void onDown(int index) {}
- public void onUp() {}
- public void onSingleTapUp(int index) {}
- public void onLongTap(int index) {}
- public void onScrollPositionChanged(int position, int total) {}
+ @Override public void onDown(int index) {}
+ @Override public void onUp(boolean followedByLongPress) {}
+ @Override public void onSingleTapUp(int index) {}
+ @Override public void onLongTap(int index) {}
+ @Override public void onScrollPositionChanged(int position, int total) {}
+ }
+
+ public static interface SlotRenderer {
+ public void prepareDrawing();
+ public void onVisibleRangeChanged(int visibleStart, int visibleEnd);
+ public void onSlotSizeChanged(int width, int height);
+ public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height);
}
private final GestureDetector mGestureDetector;
@@ -62,21 +67,9 @@
private Listener mListener;
private UserInteractionListener mUIListener;
- // Use linked hash map to keep the rendering order
- private final HashMap<DisplayItem, ItemEntry> mItems =
- new HashMap<DisplayItem, ItemEntry>();
-
- public LinkedNode.List<ItemEntry> mItemList = LinkedNode.newList();
-
- // This is used for multipass rendering
- private ArrayList<ItemEntry> mCurrentItems = new ArrayList<ItemEntry>();
- private ArrayList<ItemEntry> mNextItems = new ArrayList<ItemEntry>();
-
private boolean mMoreAnimation = false;
- private MyAnimation mAnimation = null;
- private final Position mTempPosition = new Position();
+ private SlotAnimation mAnimation = null;
private final Layout mLayout = new Layout();
- private PositionProvider mPositions;
private int mStartIndex = INDEX_NONE;
// whether the down action happened while the view is scrolling.
@@ -84,15 +77,31 @@
private int mOverscrollEffect = OVERSCROLL_3D;
private final Handler mHandler;
+ private SlotRenderer mRenderer;
+
+ private int[] mRequestRenderSlots = new int[16];
+
public static final int OVERSCROLL_3D = 0;
public static final int OVERSCROLL_SYSTEM = 1;
public static final int OVERSCROLL_NONE = 2;
- public SlotView(Context context) {
- mGestureDetector =
- new GestureDetector(context, new MyGestureListener());
- mScroller = new ScrollerHelper(context);
- mHandler = new Handler(context.getMainLooper());
+ // to prevent allocating memory
+ private final Rect mTempRect = new Rect();
+
+ public SlotView(GalleryActivity activity, Spec spec) {
+ mGestureDetector = new GestureDetector(
+ (Context) activity, new MyGestureListener());
+ mScroller = new ScrollerHelper((Context) activity);
+ mHandler = new SynchronizedHandler(activity.getGLRoot());
+ setSlotSpec(spec);
+ }
+
+ public void setSlotRenderer(SlotRenderer slotDrawer) {
+ mRenderer = slotDrawer;
+ if (mRenderer != null) {
+ mRenderer.onSlotSizeChanged(mLayout.mSlotWidth, mLayout.mSlotHeight);
+ mRenderer.onVisibleRangeChanged(getVisibleStart(), getVisibleEnd());
+ }
}
public void setCenterIndex(int index) {
@@ -100,7 +109,7 @@
if (index < 0 || index >= slotCount) {
return;
}
- Rect rect = mLayout.getSlotRect(index);
+ Rect rect = mLayout.getSlotRect(index, mTempRect);
int position = WIDE
? (rect.left + rect.right - getWidth()) / 2
: (rect.top + rect.bottom - getHeight()) / 2;
@@ -108,7 +117,7 @@
}
public void makeSlotVisible(int index) {
- Rect rect = mLayout.getSlotRect(index);
+ Rect rect = mLayout.getSlotRect(index, mTempRect);
int visibleBegin = WIDE ? mScrollX : mScrollY;
int visibleLength = WIDE ? getWidth() : getHeight();
int visibleEnd = visibleBegin + visibleLength;
@@ -143,11 +152,6 @@
}
@Override
- public boolean removeComponent(GLView view) {
- throw new UnsupportedOperationException();
- }
-
- @Override
protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
if (!changeSize) return;
@@ -158,35 +162,27 @@
(mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2;
mLayout.setSize(r - l, b - t);
makeSlotVisible(visibleIndex);
-
- onLayoutChanged(r - l, b - t);
if (mOverscrollEffect == OVERSCROLL_3D) {
mPaper.setSize(r - l, b - t);
}
}
- protected void onLayoutChanged(int width, int height) {
- }
-
- public void startTransition(PositionProvider position) {
- mPositions = position;
- mAnimation = new MyAnimation();
+ public void startScatteringAnimation(RelativePosition position) {
+ mAnimation = new ScatteringAnimation(position);
mAnimation.start();
- if (mItems.size() != 0) invalidate();
+ if (mLayout.mSlotCount != 0) invalidate();
}
- public void savePositions(PositionRepository repository) {
- repository.clear();
- LinkedNode.List<ItemEntry> list = mItemList;
- ItemEntry entry = list.getFirst();
- Position position = new Position();
- while (entry != null) {
- position.set(entry.target);
- position.x -= mScrollX;
- position.y -= mScrollY;
- repository.putPosition(entry.item.getIdentity(), position);
- entry = list.nextOf(entry);
- }
+ public void startRisingAnimation() {
+ mAnimation = new RisingAnimation();
+ mAnimation.start();
+ if (mLayout.mSlotCount != 0) invalidate();
+ }
+
+ public void startRestoringAnimation(int targetIndex) {
+ mAnimation = new RestoringAnimation(targetIndex);
+ mAnimation.start();
+ if (mLayout.mSlotCount != 0) invalidate();
}
private void updateScrollPosition(int position, boolean force) {
@@ -205,20 +201,8 @@
mListener.onScrollPositionChanged(newPosition, limit);
}
- public void putDisplayItem(Position target, Position base, DisplayItem item) {
- item.setBox(mLayout.getSlotWidth(), mLayout.getSlotHeight());
- ItemEntry entry = new ItemEntry(item, target, base);
- mItemList.insertLast(entry);
- mItems.put(item, entry);
- }
-
- public void removeDisplayItem(DisplayItem item) {
- ItemEntry entry = mItems.remove(item);
- if (entry != null) entry.remove();
- }
-
public Rect getSlotRect(int slotIndex) {
- return mLayout.getSlotRect(slotIndex);
+ return mLayout.getSlotRect(slotIndex, new Rect());
}
@Override
@@ -251,12 +235,22 @@
mScroller.setOverfling(kind == OVERSCROLL_SYSTEM);
}
+ private static int[] expandIntArray(int array[], int capacity) {
+ while (array.length < capacity) {
+ array = new int[array.length * 2];
+ }
+ return array;
+ }
+
@Override
protected void render(GLCanvas canvas) {
super.render(canvas);
- long currentTimeMillis = canvas.currentAnimationTimeMillis();
- boolean more = mScroller.advanceAnimation(currentTimeMillis);
+ if (mRenderer == null) return;
+ mRenderer.prepareDrawing();
+
+ long animTime = AnimationTime.get();
+ boolean more = mScroller.advanceAnimation(animTime);
int oldX = mScrollX;
updateScrollPosition(mScroller.getPosition(), false);
@@ -279,51 +273,34 @@
more |= paperActive;
- float interpolate = 1f;
if (mAnimation != null) {
- more |= mAnimation.calculate(currentTimeMillis);
- interpolate = mAnimation.value;
+ more |= mAnimation.calculate(animTime);
}
- if (WIDE) {
- canvas.translate(-mScrollX, 0, 0);
- } else {
- canvas.translate(0, -mScrollY, 0);
+ canvas.translate(-mScrollX, -mScrollY);
+
+ int requestCount = 0;
+ int requestedSlot[] = expandIntArray(mRequestRenderSlots,
+ mLayout.mVisibleEnd - mLayout.mVisibleStart);
+
+ for (int i = mLayout.mVisibleEnd - 1; i >= mLayout.mVisibleStart; --i) {
+ int r = renderItem(canvas, i, 0, paperActive);
+ if ((r & RENDER_MORE_FRAME) != 0) more = true;
+ if ((r & RENDER_MORE_PASS) != 0) requestedSlot[requestCount++] = i;
}
- LinkedNode.List<ItemEntry> list = mItemList;
- for (ItemEntry entry = list.getLast(); entry != null;) {
- int r = renderItem(canvas, entry, interpolate, 0, paperActive);
- if ((r & DisplayItem.RENDER_MORE_PASS) != 0) {
- mCurrentItems.add(entry);
+ for (int pass = 1; requestCount != 0; ++pass) {
+ int newCount = 0;
+ for (int i = 0; i < requestCount; ++i) {
+ int r = renderItem(canvas,
+ requestedSlot[i], pass, paperActive);
+ if ((r & RENDER_MORE_FRAME) != 0) more = true;
+ if ((r & RENDER_MORE_PASS) != 0) requestedSlot[newCount++] = i;
}
- more |= ((r & DisplayItem.RENDER_MORE_FRAME) != 0);
- entry = list.previousOf(entry);
+ requestCount = newCount;
}
- int pass = 1;
- while (!mCurrentItems.isEmpty()) {
- for (int i = 0, n = mCurrentItems.size(); i < n; i++) {
- ItemEntry entry = mCurrentItems.get(i);
- int r = renderItem(canvas, entry, interpolate, pass, paperActive);
- if ((r & DisplayItem.RENDER_MORE_PASS) != 0) {
- mNextItems.add(entry);
- }
- more |= ((r & DisplayItem.RENDER_MORE_FRAME) != 0);
- }
- mCurrentItems.clear();
- // swap mNextItems with mCurrentItems
- ArrayList<ItemEntry> tmp = mNextItems;
- mNextItems = mCurrentItems;
- mCurrentItems = tmp;
- pass += 1;
- }
-
- if (WIDE) {
- canvas.translate(mScrollX, 0, 0);
- } else {
- canvas.translate(0, mScrollY, 0);
- }
+ canvas.translate(mScrollX, mScrollY);
if (more) invalidate();
@@ -339,59 +316,80 @@
mMoreAnimation = more;
}
- private int renderItem(GLCanvas canvas, ItemEntry entry,
- float interpolate, int pass, boolean paperActive) {
+ private int renderItem(
+ GLCanvas canvas, int index, int pass, boolean paperActive) {
canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
- Position position = entry.target;
- if (mPositions != null) {
- position = mTempPosition;
- position.set(entry.target);
- position.x -= mScrollX;
- position.y -= mScrollY;
- Position source = mPositions
- .getPosition(entry.item.getIdentity(), position);
- source.x += mScrollX;
- source.y += mScrollY;
- position = mTempPosition;
- Position.interpolate(
- source, entry.target, position, interpolate);
- }
- canvas.multiplyAlpha(position.alpha);
+ Rect rect = mLayout.getSlotRect(index, mTempRect);
if (paperActive) {
- canvas.multiplyMatrix(mPaper.getTransform(
- position, entry.base, mScrollX, mScrollY), 0);
+ canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0);
} else {
- canvas.translate(position.x, position.y, position.z);
+ canvas.translate(rect.left, rect.top, 0);
}
- canvas.rotate(position.theta, 0, 0, 1);
- int more = entry.item.render(canvas, pass);
+ if (mAnimation != null && mAnimation.isActive()) {
+ mAnimation.apply(canvas, index, rect);
+ }
+ int result = mRenderer.renderSlot(
+ canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top);
canvas.restore();
- return more;
+ return result;
}
- public static class MyAnimation extends Animation {
- public float value;
+ public static abstract class SlotAnimation extends Animation {
+ protected float mProgress = 0;
- public MyAnimation() {
+ public SlotAnimation() {
setInterpolator(new DecelerateInterpolator(4));
setDuration(1500);
}
@Override
protected void onCalculate(float progress) {
- value = progress;
+ mProgress = progress;
+ }
+
+ abstract public void apply(GLCanvas canvas, int slotIndex, Rect target);
+ }
+
+ public static class RisingAnimation extends SlotAnimation {
+ private static final int RISING_DISTANCE = 128;
+
+ @Override
+ public void apply(GLCanvas canvas, int slotIndex, Rect target) {
+ canvas.translate(0, 0, RISING_DISTANCE * (1 - mProgress));
}
}
- private static class ItemEntry extends LinkedNode {
- public DisplayItem item;
- public Position target;
- public Position base;
+ public static class ScatteringAnimation extends SlotAnimation {
+ private int PHOTO_DISTANCE = 1000;
+ private RelativePosition mCenter;
- public ItemEntry(DisplayItem item, Position target, Position base) {
- this.item = item;
- this.target = target;
- this.base = base;
+ public ScatteringAnimation(RelativePosition center) {
+ mCenter = center;
+ }
+
+ @Override
+ public void apply(GLCanvas canvas, int slotIndex, Rect target) {
+ canvas.translate(
+ (mCenter.getX() - target.centerX()) * (1 - mProgress),
+ (mCenter.getY() - target.centerY()) * (1 - mProgress),
+ slotIndex * PHOTO_DISTANCE * (1 - mProgress));
+ canvas.setAlpha(mProgress);
+ }
+ }
+
+ public static class RestoringAnimation extends SlotAnimation {
+ private static final int DISTANCE = 1000;
+ private int mTargetIndex;
+
+ public RestoringAnimation(int targetIndex) {
+ mTargetIndex = targetIndex;
+ }
+
+ @Override
+ public void apply(GLCanvas canvas, int slotIndex, Rect target) {
+ if (slotIndex == mTargetIndex) {
+ canvas.translate(0, 0, -DISTANCE * (1 - mProgress));
+ }
}
}
@@ -413,24 +411,9 @@
public int rowsLand = -1;
public int rowsPort = -1;
public int slotGap = -1;
-
- static Spec newWithSize(int width, int height) {
- Spec s = new Spec();
- s.slotWidth = width;
- s.slotHeight = height;
- return s;
- }
-
- static Spec newWithRows(int rowsLand, int rowsPort, int slotGap) {
- Spec s = new Spec();
- s.rowsLand = rowsLand;
- s.rowsPort = rowsPort;
- s.slotGap = slotGap;
- return s;
- }
}
- public static class Layout {
+ public class Layout {
private int mVisibleStart;
private int mVisibleEnd;
@@ -464,7 +447,7 @@
return vPadding != mVerticalPadding || hPadding != mHorizontalPadding;
}
- public Rect getSlotRect(int index) {
+ public Rect getSlotRect(int index, Rect rect) {
int col, row;
if (WIDE) {
col = index / mUnitCount;
@@ -476,7 +459,8 @@
int x = mHorizontalPadding + col * (mSlotWidth + mSlotGap);
int y = mVerticalPadding + row * (mSlotHeight + mSlotGap);
- return new Rect(x, y, x + mSlotWidth, y + mSlotHeight);
+ rect.set(x, y, x + mSlotWidth, y + mSlotHeight);
+ return rect;
}
public int getSlotWidth() {
@@ -487,10 +471,6 @@
return mSlotHeight;
}
- public int getContentLength() {
- return mContentLength;
- }
-
// Calculate
// (1) mUnitCount: the number of slots we can fit into one column (or row).
// (2) mContentLength: the width (or height) we need to display all the
@@ -539,6 +519,10 @@
mSlotWidth = mSlotHeight;
}
+ if (mRenderer != null) {
+ mRenderer.onSlotSizeChanged(mSlotWidth, mSlotHeight);
+ }
+
int[] padding = new int[2];
if (WIDE) {
initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
@@ -592,6 +576,9 @@
} else {
mVisibleStart = mVisibleEnd = 0;
}
+ if (mRenderer != null) {
+ mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd);
+ }
}
public int getVisibleStart() {
@@ -645,26 +632,31 @@
}
}
- private class MyGestureListener implements
- GestureDetector.OnGestureListener {
+ private class MyGestureListener implements GestureDetector.OnGestureListener {
private boolean isDown;
// We call the listener's onDown() when our onShowPress() is called and
// call the listener's onUp() when we receive any further event.
@Override
public void onShowPress(MotionEvent e) {
- if (isDown) return;
- int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
- if (index != INDEX_NONE) {
- isDown = true;
- mListener.onDown(index);
+ GLRoot root = getGLRoot();
+ root.lockRenderThread();
+ try {
+ if (isDown) return;
+ int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
+ if (index != INDEX_NONE) {
+ isDown = true;
+ mListener.onDown(index);
+ }
+ } finally {
+ root.unlockRenderThread();
}
}
- private void cancelDown() {
+ private void cancelDown(boolean byLongPress) {
if (!isDown) return;
isDown = false;
- mListener.onUp();
+ mListener.onUp(byLongPress);
}
@Override
@@ -675,7 +667,7 @@
@Override
public boolean onFling(MotionEvent e1,
MotionEvent e2, float velocityX, float velocityY) {
- cancelDown();
+ cancelDown(false);
int scrollLimit = mLayout.getScrollLimit();
if (scrollLimit == 0) return false;
float velocity = WIDE ? velocityX : velocityY;
@@ -688,7 +680,7 @@
@Override
public boolean onScroll(MotionEvent e1,
MotionEvent e2, float distanceX, float distanceY) {
- cancelDown();
+ cancelDown(false);
float distance = WIDE ? distanceX : distanceY;
int overDistance = mScroller.startScroll(
Math.round(distance), 0, mLayout.getScrollLimit());
@@ -701,7 +693,7 @@
@Override
public boolean onSingleTapUp(MotionEvent e) {
- cancelDown();
+ cancelDown(false);
if (mDownInScrolling) return true;
int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
if (index != INDEX_NONE) mListener.onSingleTapUp(index);
@@ -710,7 +702,7 @@
@Override
public void onLongPress(MotionEvent e) {
- cancelDown();
+ cancelDown(true);
if (mDownInScrolling) return;
lockRendering();
try {
@@ -735,7 +727,8 @@
setCenterIndex(mStartIndex);
mStartIndex = INDEX_NONE;
}
- updateScrollPosition(WIDE ? mScrollX : mScrollY, true);
+ // Reset the scroll position to avoid scrolling over the updated limit.
+ setScrollPosition(WIDE ? mScrollX : mScrollY);
return changed;
}
diff --git a/src/com/android/gallery3d/ui/StaticBackground.java b/src/com/android/gallery3d/ui/StaticBackground.java
deleted file mode 100644
index 08c55c3..0000000
--- a/src/com/android/gallery3d/ui/StaticBackground.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import android.content.Context;
-
-public class StaticBackground extends GLView {
-
- private Context mContext;
- private int mLandscapeResource;
- private int mPortraitResource;
-
- private BasicTexture mBackground;
- private boolean mIsLandscape = false;
-
- public StaticBackground(Context context) {
- mContext = context;
- }
-
- @Override
- protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
- setOrientation(getWidth() >= getHeight());
- }
-
- private void setOrientation(boolean isLandscape) {
- if (mIsLandscape == isLandscape) return;
- mIsLandscape = isLandscape;
- if (mBackground != null) mBackground.recycle();
- mBackground = new ResourceTexture(
- mContext, mIsLandscape ? mLandscapeResource : mPortraitResource);
- invalidate();
- }
-
- public void setImage(int landscapeId, int portraitId) {
- mLandscapeResource = landscapeId;
- mPortraitResource = portraitId;
- if (mBackground != null) mBackground.recycle();
- mBackground = new ResourceTexture(
- mContext, mIsLandscape ? landscapeId : portraitId);
- invalidate();
- }
-
- @Override
- protected void render(GLCanvas canvas) {
- //mBackground.draw(canvas, 0, 0, getWidth(), getHeight());
- canvas.fillRect(0, 0, getWidth(), getHeight(), 0xFF000000);
- }
-}
diff --git a/src/com/android/gallery3d/ui/StringTexture.java b/src/com/android/gallery3d/ui/StringTexture.java
index f576c01..2db2de4 100644
--- a/src/com/android/gallery3d/ui/StringTexture.java
+++ b/src/com/android/gallery3d/ui/StringTexture.java
@@ -23,6 +23,7 @@
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.TextUtils;
+import android.util.FloatMath;
// StringTexture is a texture shows the content of a specified String.
//
@@ -69,7 +70,7 @@
private static StringTexture newInstance(String text, TextPaint paint) {
FontMetricsInt metrics = paint.getFontMetricsInt();
- int width = (int) Math.ceil(paint.measureText(text));
+ int width = (int) FloatMath.ceil(paint.measureText(text));
int height = metrics.bottom - metrics.top;
// The texture size needs to be at least 1x1.
if (width <= 0) width = 1;
diff --git a/src/com/android/gallery3d/ui/StripDrawer.java b/src/com/android/gallery3d/ui/StripDrawer.java
deleted file mode 100644
index 5941392..0000000
--- a/src/com/android/gallery3d/ui/StripDrawer.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.ui;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.data.Path;
-
-import android.content.Context;
-import android.graphics.Rect;
-
-public class StripDrawer extends SelectionDrawer {
- private NinePatchTexture mFramePressed;
- private NinePatchTexture mFocusBox;
- private Rect mFocusBoxPadding;
- private Path mPressedPath;
-
- public StripDrawer(Context context) {
- mFramePressed = new NinePatchTexture(context, R.drawable.grid_pressed);
- mFocusBox = new NinePatchTexture(context, R.drawable.thumb_selected);
- mFocusBoxPadding = mFocusBox.getPaddings();
- }
-
- public void setPressedPath(Path path) {
- mPressedPath = path;
- }
-
- private boolean isPressedPath(Path path) {
- return path != null && path == mPressedPath;
- }
-
- @Override
- public void prepareDrawing() {
- }
-
- @Override
- public void draw(GLCanvas canvas, Texture content,
- int width, int height, int rotation, Path path,
- int dataSourceType, int mediaType, boolean isPanorama,
- int labelBackgroundHeight, boolean wantCache, boolean isCaching) {
-
- int x = -width / 2;
- int y = -height / 2;
-
- drawWithRotation(canvas, content, x, y, width, height, rotation);
-
- if (isPressedPath(path)) {
- drawFrame(canvas, mFramePressed, x, y, width, height);
- }
- }
-
- @Override
- public void drawFocus(GLCanvas canvas, int width, int height) {
- int x = -width / 2;
- int y = -height / 2;
- Rect p = mFocusBoxPadding;
- mFocusBox.draw(canvas, x - p.left, y - p.top,
- width + p.left + p.right, height + p.top + p.bottom);
- }
-}
diff --git a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
new file mode 100644
index 0000000..f7a3099
--- /dev/null
+++ b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLES11Ext;
+
+public abstract class SurfaceTextureScreenNail implements ScreenNail,
+ SurfaceTexture.OnFrameAvailableListener {
+ private static final String TAG = "SurfaceTextureScreenNail";
+ protected ExtTexture mExtTexture;
+ private SurfaceTexture mSurfaceTexture;
+ private int mWidth, mHeight;
+ private float[] mTransform = new float[16];
+ private boolean mHasTexture = false;
+
+ public SurfaceTextureScreenNail() {
+ }
+
+ public void acquireSurfaceTexture() {
+ mExtTexture = new ExtTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+ mExtTexture.setSize(mWidth, mHeight);
+ mSurfaceTexture = new SurfaceTexture(mExtTexture.getId());
+ mSurfaceTexture.setOnFrameAvailableListener(this);
+ synchronized (this) {
+ mHasTexture = true;
+ }
+ }
+
+ public SurfaceTexture getSurfaceTexture() {
+ return mSurfaceTexture;
+ }
+
+ public void releaseSurfaceTexture() {
+ synchronized (this) {
+ mHasTexture = false;
+ }
+ mExtTexture.recycle();
+ mExtTexture = null;
+ mSurfaceTexture.release();
+ mSurfaceTexture = null;
+ }
+
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y, int width, int height) {
+ synchronized (this) {
+ if (!mHasTexture) return;
+ mSurfaceTexture.updateTexImage();
+ mSurfaceTexture.getTransformMatrix(mTransform);
+
+ // Flip vertically.
+ canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
+ int cx = x + width / 2;
+ int cy = y + height / 2;
+ canvas.translate(cx, cy);
+ canvas.scale(1, -1, 1);
+ canvas.translate(-cx, -cy);
+ canvas.drawTexture(mExtTexture, mTransform, x, y, width, height);
+ canvas.restore();
+ }
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, RectF source, RectF dest) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ abstract public void noDraw();
+
+ @Override
+ abstract public void recycle();
+
+ @Override
+ abstract public void onFrameAvailable(SurfaceTexture surfaceTexture);
+}
diff --git a/src/com/android/gallery3d/ui/SynchronizedHandler.java b/src/com/android/gallery3d/ui/SynchronizedHandler.java
index bd494a3..ba10357 100644
--- a/src/com/android/gallery3d/ui/SynchronizedHandler.java
+++ b/src/com/android/gallery3d/ui/SynchronizedHandler.java
@@ -16,11 +16,11 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
-
import android.os.Handler;
import android.os.Message;
+import com.android.gallery3d.common.Utils;
+
public class SynchronizedHandler extends Handler {
private final GLRoot mRoot;
diff --git a/src/com/android/gallery3d/ui/Texture.java b/src/com/android/gallery3d/ui/Texture.java
index 4d1749b..2c426f9 100644
--- a/src/com/android/gallery3d/ui/Texture.java
+++ b/src/com/android/gallery3d/ui/Texture.java
@@ -26,14 +26,12 @@
// -- ColorTexture
// -- FadeInTexture
// -- BasicTexture
-// -- RawTexture
// -- UploadedTexture
// -- BitmapTexture
// -- Tile
// -- ResourceTexture
// -- NinePatchTexture
// -- CanvasTexture
-// -- DrawableTexture
// -- StringTexture
//
public interface Texture {
diff --git a/src/com/android/gallery3d/ui/TextureUploader.java b/src/com/android/gallery3d/ui/TextureUploader.java
new file mode 100644
index 0000000..b651c69
--- /dev/null
+++ b/src/com/android/gallery3d/ui/TextureUploader.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
+
+import java.util.ArrayDeque;
+
+public class TextureUploader implements OnGLIdleListener {
+ private static final int INIT_CAPACITY = 64;
+ private static final int QUOTA_PER_FRAME = 1;
+
+ private final ArrayDeque<UploadedTexture> mFgTextures =
+ new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+ private final ArrayDeque<UploadedTexture> mBgTextures =
+ new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+ private final GLRoot mGLRoot;
+ private transient boolean mIsQueued = false;
+
+ public TextureUploader(GLRoot root) {
+ mGLRoot = root;
+ }
+
+ public synchronized void clear() {
+ mFgTextures.clear();
+ mBgTextures.clear();
+ }
+
+ // caller should hold synchronized on "this"
+ private void queueSelfIfNeed() {
+ if (mIsQueued) return;
+ mIsQueued = true;
+ mGLRoot.addOnGLIdleListener(this);
+ }
+
+ public synchronized void addBgTexture(UploadedTexture t) {
+ mBgTextures.addLast(t);
+ queueSelfIfNeed();
+ }
+
+ public synchronized void addFgTexture(UploadedTexture t) {
+ mFgTextures.addLast(t);
+ queueSelfIfNeed();
+ }
+
+ private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
+ int uploadQuota, boolean isBackground) {
+ while (uploadQuota > 0) {
+ UploadedTexture t;
+ synchronized (this) {
+ if (deque.isEmpty()) break;
+ t = deque.removeFirst();
+ }
+ if (!t.isContentValid(canvas)) {
+ t.updateContent(canvas);
+
+ // It will took some more time for a texture to be drawn for
+ // the first time.
+ // Thus, when scrolling, if a new column appears on screen,
+ // it may cause a UI jank even these textures are uploaded.
+ if (isBackground) t.draw(canvas, 0, 0);
+ --uploadQuota;
+ }
+ }
+ return uploadQuota;
+ }
+
+ @Override
+ public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+ int uploadQuota = QUOTA_PER_FRAME;
+ uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
+ if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
+ upload(canvas, mBgTextures, uploadQuota, true);
+ synchronized (this) {
+ mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
+ return mIsQueued;
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index 5c9f3f4..b37cf9c 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -20,6 +20,8 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.FloatMath;
+import android.util.LongSparseArray;
import com.android.gallery3d.app.GalleryContext;
import com.android.gallery3d.common.Utils;
@@ -29,9 +31,6 @@
import com.android.gallery3d.util.ThreadPool.CancelListener;
import com.android.gallery3d.util.ThreadPool.JobContext;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
public class TileImageView extends GLView {
@@ -72,7 +71,7 @@
private static final int STATE_RECYCLED = 0x40;
private Model mModel;
- protected BitmapTexture mBackupImage;
+ private ScreenNail mScreenNail;
protected int mLevelCount; // cache the value of mScaledBitmaps.length
// The mLevel variable indicates which level of bitmap we should use.
@@ -80,7 +79,7 @@
// 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, and that means we use mBackupTexture for display.
+ // is mLevelCount, and that means we use mScreenNail for display.
private int mLevel = 0;
// The offsets of the (left, top) of the upper-left tile to the (left, top)
@@ -94,7 +93,7 @@
private final RectF mSourceRect = new RectF();
private final RectF mTargetRect = new RectF();
- private final HashMap<Long, Tile> mActiveTiles = new HashMap<Long, Tile>();
+ private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>();
// The following three queue is guarded by TileImageView.this
private TileQueue mRecycledQueue = new TileQueue();
@@ -123,7 +122,7 @@
public static interface Model {
public int getLevelCount();
- public Bitmap getBackupImage();
+ public ScreenNail getScreenNail();
public int getImageWidth();
public int getImageHeight();
@@ -154,31 +153,19 @@
if (model != null) notifyModelInvalidated();
}
- private void updateBackupTexture(Bitmap backup) {
- if (backup == null) {
- if (mBackupImage != null) mBackupImage.recycle();
- mBackupImage = null;
- } else {
- if (mBackupImage != null) {
- if (mBackupImage.getBitmap() != backup) {
- mBackupImage.recycle();
- mBackupImage = new BitmapTexture(backup);
- }
- } else {
- mBackupImage = new BitmapTexture(backup);
- }
- }
+ public void setScreenNail(ScreenNail s) {
+ mScreenNail = s;
}
public void notifyModelInvalidated() {
invalidateTiles();
if (mModel == null) {
- mBackupImage = null;
+ mScreenNail = null;
mImageWidth = 0;
mImageHeight = 0;
mLevelCount = 0;
} else {
- updateBackupTexture(mModel.getBackupImage());
+ setScreenNail(mModel.getScreenNail());
mImageWidth = mModel.getImageWidth();
mImageHeight = mModel.getImageHeight();
mLevelCount = mModel.getLevelCount();
@@ -249,14 +236,15 @@
// Recycle unused tiles: if the level of the active tile is outside the
// range [fromLevel, endLevel) or not in the visible range.
- Iterator<Map.Entry<Long, Tile>>
- iter = mActiveTiles.entrySet().iterator();
- while (iter.hasNext()) {
- Tile tile = iter.next().getValue();
+ 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)) {
- iter.remove();
+ mActiveTiles.removeAt(i);
+ i--;
+ n--;
recycleTile(tile);
}
}
@@ -277,7 +265,9 @@
mDecodeQueue.clean();
mUploadQueue.clean();
// TODO disable decoder
- for (Tile tile : mActiveTiles.values()) {
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
recycleTile(tile);
}
mActiveTiles.clear();
@@ -307,10 +297,10 @@
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);
+ int left = (int) FloatMath.floor(cX - width / (2f * scale));
+ int top = (int) FloatMath.floor(cY - height / (2f * scale));
+ int right = (int) FloatMath.ceil(left + width / scale);
+ int bottom = (int) FloatMath.ceil(top + height / scale);
// align the rectangle to tile boundary
int size = TILE_SIZE << level;
@@ -374,11 +364,13 @@
mTileDecoder = null;
}
- for (Tile texture : mActiveTiles.values()) {
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile texture = mActiveTiles.valueAt(i);
texture.recycle();
}
- mTileRange.set(0, 0, 0, 0);
mActiveTiles.clear();
+ mTileRange.set(0, 0, 0, 0);
synchronized (this) {
mUploadQueue.clean();
@@ -389,7 +381,7 @@
tile = mRecycledQueue.pop();
}
}
- updateBackupTexture(null);
+ setScreenNail(null);
}
public void prepareTextures() {
@@ -399,7 +391,7 @@
if (mIsTextureFreed) {
layoutTiles(mCenterX, mCenterY, mScale, mRotation);
mIsTextureFreed = false;
- updateBackupTexture(mModel != null ? mModel.getBackupImage() : null);
+ setScreenNail(mModel == null ? null : mModel.getScreenNail());
}
}
@@ -418,14 +410,18 @@
canvas.save(flags);
if (rotation != 0) {
int centerX = getWidth() / 2, centerY = getHeight() / 2;
- canvas.translate(centerX, centerY, 0);
+ canvas.translate(centerX, centerY);
canvas.rotate(rotation, 0, 0, 1);
- canvas.translate(-centerX, -centerY, 0);
+ canvas.translate(-centerX, -centerY);
}
if (mAlpha != 1.0f) canvas.multiplyAlpha(mAlpha);
}
try {
if (level != mLevelCount) {
+ if (mScreenNail != null) {
+ mScreenNail.noDraw();
+ }
+
int size = (TILE_SIZE << level);
float length = size * mScale;
Rect r = mTileRange;
@@ -437,8 +433,8 @@
drawTile(canvas, tx, ty, level, x, y, length);
}
}
- } else if (mBackupImage != null) {
- mBackupImage.draw(canvas, mOffsetX, mOffsetY,
+ } else if (mScreenNail != null) {
+ mScreenNail.draw(canvas, mOffsetX, mOffsetY,
Math.round(mImageWidth * mScale),
Math.round(mImageHeight * mScale));
}
@@ -455,7 +451,9 @@
private void uploadBackgroundTiles(GLCanvas canvas) {
mBackgroundTileUploaded = true;
- for (Tile tile : mActiveTiles.values()) {
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
if (!tile.isContentValid(canvas)) queueForDecode(tile);
}
}
@@ -515,7 +513,7 @@
}
private void activateTile(int x, int y, int level) {
- Long key = makeTileKey(x, y, level);
+ long key = makeTileKey(x, y, level);
Tile tile = mActiveTiles.get(key);
if (tile != null) {
if (tile.mTileState == STATE_IN_QUEUE) {
@@ -531,18 +529,19 @@
return mActiveTiles.get(makeTileKey(x, y, level));
}
- private static Long makeTileKey(int x, int y, int level) {
+ private static long makeTileKey(int x, int y, int level) {
long result = x;
result = (result << 16) | y;
result = (result << 16) | level;
- return Long.valueOf(result);
+ return result;
}
private class TileUploader implements GLRoot.OnGLIdleListener {
AtomicBoolean mActive = new AtomicBoolean(false);
@Override
- public boolean onGLIdle(GLRoot root, GLCanvas canvas) {
+ public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+ if (renderRequested) return false;
int quota = UPLOAD_LIMIT;
Tile tile;
while (true) {
@@ -587,14 +586,13 @@
}
if (drawTile(tile, canvas, source, target)) return;
}
- if (mBackupImage != null) {
- BasicTexture backup = mBackupImage;
+ if (mScreenNail != null) {
int size = TILE_SIZE << level;
- float scaleX = (float) backup.getWidth() / mImageWidth;
- float scaleY = (float) backup.getHeight() / mImageHeight;
+ float scaleX = (float) mScreenNail.getWidth() / mImageWidth;
+ float scaleY = (float) mScreenNail.getHeight() / mImageHeight;
source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX,
(ty + size) * scaleY);
- canvas.drawTexture(backup, source, target);
+ mScreenNail.draw(canvas, source, target);
}
}
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index be255d2..f4c6e65 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -27,18 +27,21 @@
public class TileImageViewAdapter implements TileImageView.Model {
private static final String TAG = "TileImageViewAdapter";
+ protected ScreenNail mScreenNail;
+ protected boolean mOwnScreenNail;
protected BitmapRegionDecoder mRegionDecoder;
protected int mImageWidth;
protected int mImageHeight;
- protected Bitmap mBackupImage;
protected int mLevelCount;
protected boolean mFailedToLoad;
public TileImageViewAdapter() {
}
- public TileImageViewAdapter(Bitmap backup, BitmapRegionDecoder regionDecoder) {
- mBackupImage = Utils.checkNotNull(backup);
+ public TileImageViewAdapter(
+ Bitmap bitmap, BitmapRegionDecoder regionDecoder) {
+ Utils.checkNotNull(bitmap);
+ updateScreenNail(new BitmapScreenNail(bitmap), true);
mRegionDecoder = regionDecoder;
mImageWidth = regionDecoder.getWidth();
mImageHeight = regionDecoder.getHeight();
@@ -46,7 +49,7 @@
}
public synchronized void clear() {
- mBackupImage = null;
+ updateScreenNail(null, false);
mImageWidth = 0;
mImageHeight = 0;
mLevelCount = 0;
@@ -54,8 +57,9 @@
mFailedToLoad = false;
}
- public synchronized void setBackupImage(Bitmap backup, int width, int height) {
- mBackupImage = Utils.checkNotNull(backup);
+ public synchronized void setScreenNail(Bitmap bitmap, int width, int height) {
+ Utils.checkNotNull(bitmap);
+ updateScreenNail(new BitmapScreenNail(bitmap), true);
mImageWidth = width;
mImageHeight = height;
mRegionDecoder = null;
@@ -63,6 +67,25 @@
mFailedToLoad = false;
}
+ public synchronized void setScreenNail(
+ ScreenNail screenNail, int width, int height) {
+ Utils.checkNotNull(screenNail);
+ updateScreenNail(screenNail, false);
+ mImageWidth = width;
+ mImageHeight = height;
+ mRegionDecoder = null;
+ mLevelCount = 0;
+ mFailedToLoad = false;
+ }
+
+ private void updateScreenNail(ScreenNail screenNail, boolean own) {
+ if (mScreenNail != null && mOwnScreenNail) {
+ mScreenNail.recycle();
+ }
+ mScreenNail = screenNail;
+ mOwnScreenNail = own;
+ }
+
public synchronized void setRegionDecoder(BitmapRegionDecoder decoder) {
mRegionDecoder = Utils.checkNotNull(decoder);
mImageWidth = decoder.getWidth();
@@ -73,7 +96,7 @@
private int calculateLevelCount() {
return Math.max(0, Utils.ceilLog2(
- (float) mImageWidth / mBackupImage.getWidth()));
+ (float) mImageWidth / mScreenNail.getWidth()));
}
@Override
@@ -139,8 +162,8 @@
}
@Override
- public Bitmap getBackupImage() {
- return mBackupImage;
+ public ScreenNail getScreenNail() {
+ return mScreenNail;
}
@Override
diff --git a/src/com/android/gallery3d/ui/UploadedTexture.java b/src/com/android/gallery3d/ui/UploadedTexture.java
index 1777048..85aa1c4 100644
--- a/src/com/android/gallery3d/ui/UploadedTexture.java
+++ b/src/com/android/gallery3d/ui/UploadedTexture.java
@@ -16,13 +16,14 @@
package com.android.gallery3d.ui;
-import com.android.gallery3d.common.Utils;
-
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.opengl.GLUtils;
+import com.android.gallery3d.common.Utils;
+
import java.util.HashMap;
+
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;
@@ -227,7 +228,7 @@
sCropRect[3] = -bHeight;
// Upload the bitmap to a new texture.
- gl.glGenTextures(1, sTextureId, 0);
+ GLId.glGenTextures(1, sTextureId, 0);
gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]);
gl.glTexParameterfv(GL11.GL_TEXTURE_2D,
GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
@@ -298,6 +299,11 @@
return isContentValid(canvas);
}
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+
public void setOpaque(boolean isOpaque) {
mOpaque = isOpaque;
}
diff --git a/src/com/android/gallery3d/util/CacheManager.java b/src/com/android/gallery3d/util/CacheManager.java
index fcc444e..ba466f7 100644
--- a/src/com/android/gallery3d/util/CacheManager.java
+++ b/src/com/android/gallery3d/util/CacheManager.java
@@ -16,12 +16,12 @@
package com.android.gallery3d.util;
-import com.android.gallery3d.common.BlobCache;
-
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
+import com.android.gallery3d.common.BlobCache;
+
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
diff --git a/src/com/android/gallery3d/util/FutureTask.java b/src/com/android/gallery3d/util/FutureTask.java
deleted file mode 100644
index 9cfab27..0000000
--- a/src/com/android/gallery3d/util/FutureTask.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.util;
-
-import java.util.concurrent.Callable;
-
-// NOTE: If the Callable throws any Throwable, the result value will be null.
-public class FutureTask<T> implements Runnable, Future<T> {
- private static final String TAG = "FutureTask";
- private Callable<T> mCallable;
- private FutureListener<T> mListener;
- private volatile boolean mIsCancelled;
- private boolean mIsDone;
- private T mResult;
-
- public FutureTask(Callable<T> callable, FutureListener<T> listener) {
- mCallable = callable;
- mListener = listener;
- }
-
- public FutureTask(Callable<T> callable) {
- this(callable, null);
- }
-
- public void cancel() {
- mIsCancelled = true;
- }
-
- public synchronized T get() {
- while (!mIsDone) {
- try {
- wait();
- } catch (InterruptedException t) {
- // ignore.
- }
- }
- return mResult;
- }
-
- public void waitDone() {
- get();
- }
-
- public synchronized boolean isDone() {
- return mIsDone;
- }
-
- public boolean isCancelled() {
- return mIsCancelled;
- }
-
- public void run() {
- T result = null;
-
- if (!mIsCancelled) {
- try {
- result = mCallable.call();
- } catch (Throwable ex) {
- Log.w(TAG, "Exception in running a task", ex);
- }
- }
-
- synchronized(this) {
- mResult = result;
- mIsDone = true;
- if (mListener != null) {
- mListener.onFutureDone(this);
- }
- notifyAll();
- }
- }
-}
diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java
index fcb27ba..13e08f9 100644
--- a/src/com/android/gallery3d/util/GalleryUtils.java
+++ b/src/com/android/gallery3d/util/GalleryUtils.java
@@ -16,13 +16,6 @@
package com.android.gallery3d.util;
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.PackagesMonitor;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.util.ThreadPool.CancelListener;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -31,6 +24,7 @@
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.ConditionVariable;
import android.os.Environment;
@@ -41,6 +35,13 @@
import android.util.Log;
import android.view.WindowManager;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.PackagesMonitor;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.util.ThreadPool.CancelListener;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@@ -324,35 +325,23 @@
return false;
}
- public static void assertInMainThread() {
- if (Thread.currentThread() == sContext.getMainLooper().getThread()) {
- throw new AssertionError();
- }
- }
-
- public static void doubleToRational(double value, long[] output) {
- // error is a magic number to control the tollerance of error
- doubleToRational(value, output, 0.00001);
- }
-
- private static void doubleToRational(double value, long[] output, double error) {
- long number = (long) value;
- value -= number;
- if (value < 0.000001 || error > 1) {
- output[0] = (int) (number + value + 0.5);
- output[1] = 1;
- } else {
- doubleToRational(1.0 / value, output, error / value);
- number = number * output[0] + output[1];
- output[1] = output[0];
- output[0] = number;
- }
- }
-
public static boolean isPanorama(MediaItem item) {
if (item == null) return false;
int w = item.getWidth();
int h = item.getHeight();
return (h > 0 && w / h >= 2);
}
+
+ public static Intent getHelpIntent(int helpUrlResId, Context context) {
+ Resources res = context.getResources();
+ String url = res.getString(helpUrlResId)
+ + "&hl=" + res.getConfiguration().locale.getLanguage();
+
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ return i;
+ }
}
diff --git a/src/com/android/gallery3d/util/IdentityCache.java b/src/com/android/gallery3d/util/IdentityCache.java
index 02a46ae..3edc424 100644
--- a/src/com/android/gallery3d/util/IdentityCache.java
+++ b/src/com/android/gallery3d/util/IdentityCache.java
@@ -18,8 +18,8 @@
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
-import java.util.HashMap;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Set;
public class IdentityCache<K, V> {
@@ -61,11 +61,15 @@
return entry == null ? null : entry.get();
}
+ // This is currently unused.
+ /*
public synchronized void clear() {
mWeakMap.clear();
mQueue = new ReferenceQueue<V>();
}
+ */
+ // This is for debugging only
public synchronized ArrayList<K> keys() {
Set<K> set = mWeakMap.keySet();
ArrayList<K> result = new ArrayList<K>(set);
diff --git a/src/com/android/gallery3d/util/IntArray.java b/src/com/android/gallery3d/util/IntArray.java
index 88657bb..082089a 100644
--- a/src/com/android/gallery3d/util/IntArray.java
+++ b/src/com/android/gallery3d/util/IntArray.java
@@ -35,6 +35,7 @@
return mSize;
}
+ // For testing only
public int[] toArray(int[] result) {
if (result == null || result.length < mSize) {
result = new int[mSize];
diff --git a/src/com/android/gallery3d/util/JobLimiter.java b/src/com/android/gallery3d/util/JobLimiter.java
index 5abdfd8..42b7541 100644
--- a/src/com/android/gallery3d/util/JobLimiter.java
+++ b/src/com/android/gallery3d/util/JobLimiter.java
@@ -140,10 +140,6 @@
return future;
}
- public <T> Future<T> submit(Job<T> job) {
- return submit(job, null);
- }
-
@SuppressWarnings({"rawtypes", "unchecked"})
private void submitTasksIfAllowed() {
while (mLimit > 0 && !mJobs.isEmpty()) {
diff --git a/src/com/android/gallery3d/util/LinkedNode.java b/src/com/android/gallery3d/util/LinkedNode.java
index 8554acd..4cfc3cd 100644
--- a/src/com/android/gallery3d/util/LinkedNode.java
+++ b/src/com/android/gallery3d/util/LinkedNode.java
@@ -43,10 +43,6 @@
public static class List<T extends LinkedNode> {
private LinkedNode mHead = new LinkedNode();
- public void insertFirst(T node) {
- mHead.insert(node);
- }
-
public void insertLast(T node) {
mHead.mPrev.insert(node);
}
diff --git a/src/com/android/gallery3d/util/MediaSetUtils.java b/src/com/android/gallery3d/util/MediaSetUtils.java
index 817ffed..8e60f59 100644
--- a/src/com/android/gallery3d/util/MediaSetUtils.java
+++ b/src/com/android/gallery3d/util/MediaSetUtils.java
@@ -16,12 +16,12 @@
package com.android.gallery3d.util;
+import android.os.Environment;
+
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.MtpContext;
import com.android.gallery3d.data.Path;
-import android.os.Environment;
-
import java.util.Comparator;
public class MediaSetUtils {
@@ -34,6 +34,9 @@
public static final int IMPORTED_BUCKET_ID = GalleryUtils.getBucketId(
Environment.getExternalStorageDirectory().toString() + "/"
+ MtpContext.NAME_IMPORTED_FOLDER);
+ public static final int SNAPSHOT_BUCKET_ID = GalleryUtils.getBucketId(
+ Environment.getExternalStorageDirectory().toString() +
+ "/Pictures/Screenshots");
private static final Path[] CAMERA_PATHS = {
Path.fromString("/local/all/" + CAMERA_BUCKET_ID),
diff --git a/src/com/android/gallery3d/util/Profile.java b/src/com/android/gallery3d/util/Profile.java
new file mode 100644
index 0000000..6b6e5c3
--- /dev/null
+++ b/src/com/android/gallery3d/util/Profile.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.util;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+// The Profile class is used to collect profiling information for a thread. It
+// samples stack traces for a thread periodically. enable() and disable() is
+// used to enable and disable profiling for the calling thread. The profiling
+// information can then be dumped to a file using the dumpToFile() method.
+//
+// The disableAll() method can be used to disable profiling for all threads and
+// can be called in onPause() to ensure all profiling is disabled when an
+// activity is paused.
+public class Profile {
+ private static final String TAG = "Profile";
+ private static final int NS_PER_MS = 1000000;
+
+ // This is a watchdog entry for one thread.
+ // For every cycleTime period, we dump the stack of the thread.
+ private static class WatchEntry {
+ Thread thread;
+
+ // Both are in milliseconds
+ int cycleTime;
+ int wakeTime;
+
+ boolean isHolding;
+ ArrayList<String[]> holdingStacks = new ArrayList<String[]>();
+ }
+
+ // This is a watchdog thread which dumps stacks of other threads periodically.
+ private static Watchdog sWatchdog = new Watchdog();
+
+ private static class Watchdog {
+ private ArrayList<WatchEntry> mList = new ArrayList<WatchEntry>();
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private Runnable mProcessRunnable = new Runnable() {
+ public void run() {
+ synchronized (Watchdog.this) {
+ processList();
+ }
+ }
+ };
+ private Random mRandom = new Random();
+ private ProfileData mProfileData = new ProfileData();
+
+ public Watchdog() {
+ mHandlerThread = new HandlerThread("Watchdog Handler",
+ Process.THREAD_PRIORITY_FOREGROUND);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+
+ public synchronized void addWatchEntry(Thread thread, int cycleTime) {
+ WatchEntry e = new WatchEntry();
+ e.thread = thread;
+ e.cycleTime = cycleTime;
+ int firstDelay = 1 + mRandom.nextInt(cycleTime);
+ e.wakeTime = (int) (System.nanoTime() / NS_PER_MS) + firstDelay;
+ mList.add(e);
+ processList();
+ }
+
+ public synchronized void removeWatchEntry(Thread thread) {
+ for (int i = 0; i < mList.size(); i++) {
+ if (mList.get(i).thread == thread) {
+ mList.remove(i);
+ break;
+ }
+ }
+ processList();
+ }
+
+ public synchronized void removeAllWatchEntries() {
+ mList.clear();
+ processList();
+ }
+
+ private void processList() {
+ mHandler.removeCallbacks(mProcessRunnable);
+ if (mList.size() == 0) return;
+
+ int currentTime = (int) (System.nanoTime() / NS_PER_MS);
+ int nextWakeTime = 0;
+
+ for (WatchEntry entry : mList) {
+ if (currentTime > entry.wakeTime) {
+ entry.wakeTime += entry.cycleTime;
+ Thread thread = entry.thread;
+ sampleStack(entry);
+ }
+
+ if (entry.wakeTime > nextWakeTime) {
+ nextWakeTime = entry.wakeTime;
+ }
+ }
+
+ long delay = nextWakeTime - currentTime;
+ mHandler.postDelayed(mProcessRunnable, delay);
+ }
+
+ private void sampleStack(WatchEntry entry) {
+ Thread thread = entry.thread;
+ StackTraceElement[] stack = thread.getStackTrace();
+ String[] lines = new String[stack.length];
+ for (int i = 0; i < stack.length; i++) {
+ lines[i] = stack[i].toString();
+ }
+ if (entry.isHolding) {
+ entry.holdingStacks.add(lines);
+ } else {
+ mProfileData.addSample(lines);
+ }
+ }
+
+ private WatchEntry findEntry(Thread thread) {
+ for (int i = 0; i < mList.size(); i++) {
+ WatchEntry entry = mList.get(i);
+ if (entry.thread == thread) return entry;
+ }
+ return null;
+ }
+
+ public synchronized void dumpToFile(String filename) {
+ mProfileData.dumpToFile(filename);
+ }
+
+ public synchronized void reset() {
+ mProfileData.reset();
+ }
+
+ public synchronized void hold(Thread t) {
+ WatchEntry entry = findEntry(t);
+
+ // This can happen if the profiling is disabled (probably from
+ // another thread). Same check is applied in commit() and drop()
+ // below.
+ if (entry == null) return;
+
+ entry.isHolding = true;
+ }
+
+ public synchronized void commit(Thread t) {
+ WatchEntry entry = findEntry(t);
+ if (entry == null) return;
+ ArrayList<String[]> stacks = entry.holdingStacks;
+ for (int i = 0; i < stacks.size(); i++) {
+ mProfileData.addSample(stacks.get(i));
+ }
+ entry.isHolding = false;
+ entry.holdingStacks.clear();
+ }
+
+ public synchronized void drop(Thread t) {
+ WatchEntry entry = findEntry(t);
+ if (entry == null) return;
+ entry.isHolding = false;
+ entry.holdingStacks.clear();
+ }
+ }
+
+ // Enable profiling for the calling thread. Periodically (every cycleTimeInMs
+ // milliseconds) sample the stack trace of the calling thread.
+ public static void enable(int cycleTimeInMs) {
+ Thread t = Thread.currentThread();
+ sWatchdog.addWatchEntry(t, cycleTimeInMs);
+ }
+
+ // Disable profiling for the calling thread.
+ public static void disable() {
+ sWatchdog.removeWatchEntry(Thread.currentThread());
+ }
+
+ // Disable profiling for all threads.
+ public static void disableAll() {
+ sWatchdog.removeAllWatchEntries();
+ }
+
+ // Dump the profiling data to a file.
+ public static void dumpToFile(String filename) {
+ sWatchdog.dumpToFile(filename);
+ }
+
+ // Reset the collected profiling data.
+ public static void reset() {
+ sWatchdog.reset();
+ }
+
+ // Hold the future samples coming from current thread until commit() or
+ // drop() is called, and those samples are recorded or ignored as a result.
+ // This must called after enable() to be effective.
+ public static void hold() {
+ sWatchdog.hold(Thread.currentThread());
+ }
+
+ public static void commit() {
+ sWatchdog.commit(Thread.currentThread());
+ }
+
+ public static void drop() {
+ sWatchdog.drop(Thread.currentThread());
+ }
+}
diff --git a/src/com/android/gallery3d/util/ProfileData.java b/src/com/android/gallery3d/util/ProfileData.java
new file mode 100644
index 0000000..02eb37a
--- /dev/null
+++ b/src/com/android/gallery3d/util/ProfileData.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.util;
+
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+
+import java.io.DataOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+// ProfileData keeps profiling samples in a tree structure.
+// The addSample() method adds a sample. The dumpToFile() method saves the data
+// to a file. The reset() method clears all samples.
+public class ProfileData {
+ private static final String TAG = "ProfileData";
+
+ private static class Node {
+ public int id; // this is the name of this node, mapped from mNameToId
+ public Node parent;
+ public int sampleCount;
+ public ArrayList<Node> children;
+ public Node(Node parent, int id) {
+ this.parent = parent;
+ this.id = id;
+ }
+ }
+
+ private Node mRoot;
+ private int mNextId;
+ private HashMap<String, Integer> mNameToId;
+ private DataOutputStream mOut;
+ private byte mScratch[] = new byte[4]; // scratch space for writeInt()
+
+ public ProfileData() {
+ mRoot = new Node(null, -1); // The id of the root node is unused.
+ mNameToId = new HashMap<String, Integer>();
+ }
+
+ public void reset() {
+ mRoot = new Node(null, -1);
+ mNameToId.clear();
+ mNextId = 0;
+ }
+
+ private int nameToId(String name) {
+ Integer id = mNameToId.get(name);
+ if (id == null) {
+ id = ++mNextId; // The tool doesn't want id=0, so we start from 1.
+ mNameToId.put(name, id);
+ }
+ return id;
+ }
+
+ public void addSample(String[] stack) {
+ int[] ids = new int[stack.length];
+ for (int i = 0; i < stack.length; i++) {
+ ids[i] = nameToId(stack[i]);
+ }
+
+ Node node = mRoot;
+ for (int i = stack.length - 1; i >= 0; i--) {
+ if (node.children == null) {
+ node.children = new ArrayList<Node>();
+ }
+
+ int id = ids[i];
+ ArrayList<Node> children = node.children;
+ int j;
+ for (j = 0; j < children.size(); j++) {
+ if (children.get(j).id == id) break;
+ }
+ if (j == children.size()) {
+ children.add(new Node(node, id));
+ }
+
+ node = children.get(j);
+ }
+
+ node.sampleCount++;
+ }
+
+ public void dumpToFile(String filename) {
+ try {
+ mOut = new DataOutputStream(new FileOutputStream(filename));
+ // Start record
+ writeInt(0);
+ writeInt(3);
+ writeInt(1);
+ writeInt(20000); // Sampling period: 20ms
+ writeInt(0);
+
+ // Samples
+ writeAllStacks(mRoot, 0);
+
+ // End record
+ writeInt(0);
+ writeInt(1);
+ writeInt(0);
+ writeAllSymbols();
+ } catch (IOException ex) {
+ Log.w("Failed to dump to file", ex);
+ } finally {
+ Utils.closeSilently(mOut);
+ }
+ }
+
+ // Writes out one stack, consisting of N+2 words:
+ // first word: sample count
+ // second word: depth of the stack (N)
+ // N words: each word is the id of one address in the stack
+ private void writeOneStack(Node node, int depth) throws IOException {
+ writeInt(node.sampleCount);
+ writeInt(depth);
+ while (depth-- > 0) {
+ writeInt(node.id);
+ node = node.parent;
+ }
+ }
+
+ private void writeAllStacks(Node node, int depth) throws IOException {
+ if (node.sampleCount > 0) {
+ writeOneStack(node, depth);
+ }
+
+ ArrayList<Node> children = node.children;
+ if (children != null) {
+ for (int i = 0; i < children.size(); i++) {
+ writeAllStacks(children.get(i), depth + 1);
+ }
+ }
+ }
+
+ // Writes out the symbol table. Each line is like:
+ // 0x17e java.util.ArrayList.isEmpty(ArrayList.java:319)
+ private void writeAllSymbols() throws IOException {
+ for (Entry<String, Integer> entry : mNameToId.entrySet()) {
+ mOut.writeBytes(String.format("0x%x %s\n", entry.getValue(), entry.getKey()));
+ }
+ }
+
+ private void writeInt(int v) throws IOException {
+ mScratch[0] = (byte) v;
+ mScratch[1] = (byte) (v >> 8);
+ mScratch[2] = (byte) (v >> 16);
+ mScratch[3] = (byte) (v >> 24);
+ mOut.write(mScratch);
+ }
+}
diff --git a/src/com/android/gallery3d/util/RangeArray.java b/src/com/android/gallery3d/util/RangeArray.java
new file mode 100644
index 0000000..8e61348
--- /dev/null
+++ b/src/com/android/gallery3d/util/RangeArray.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.util;
+
+// This is an array whose index ranges from min to max (inclusive).
+public class RangeArray<T> {
+ private T[] mData;
+ private int mOffset;
+
+ public RangeArray(int min, int max) {
+ mData = (T[]) new Object[max - min + 1];
+ mOffset = min;
+ }
+
+ // Wraps around an existing array
+ public RangeArray(T[] src, int min, int max) {
+ if (max - min + 1 != src.length) {
+ throw new AssertionError();
+ }
+ mData = src;
+ mOffset = min;
+ }
+
+ public void put(int i, T object) {
+ mData[i - mOffset] = object;
+ }
+
+ public T get(int i) {
+ return mData[i - mOffset];
+ }
+
+ public int indexOf(T object) {
+ for (int i = 0; i < mData.length; i++) {
+ if (mData[i] == object) return i + mOffset;
+ }
+ return Integer.MAX_VALUE;
+ }
+}
diff --git a/src/com/android/gallery3d/util/RangeBoolArray.java b/src/com/android/gallery3d/util/RangeBoolArray.java
new file mode 100644
index 0000000..035fc40
--- /dev/null
+++ b/src/com/android/gallery3d/util/RangeBoolArray.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.util;
+
+// This is an array whose index ranges from min to max (inclusive).
+public class RangeBoolArray {
+ private boolean[] mData;
+ private int mOffset;
+
+ public RangeBoolArray(int min, int max) {
+ mData = new boolean[max - min + 1];
+ mOffset = min;
+ }
+
+ // Wraps around an existing array
+ public RangeBoolArray(boolean[] src, int min, int max) {
+ mData = src;
+ mOffset = min;
+ }
+
+ public void put(int i, boolean object) {
+ mData[i - mOffset] = object;
+ }
+
+ public boolean get(int i) {
+ return mData[i - mOffset];
+ }
+
+ public int indexOf(boolean object) {
+ for (int i = 0; i < mData.length; i++) {
+ if (mData[i] == object) return i + mOffset;
+ }
+ return Integer.MAX_VALUE;
+ }
+}
diff --git a/src/com/android/gallery3d/util/RangeIntArray.java b/src/com/android/gallery3d/util/RangeIntArray.java
new file mode 100644
index 0000000..9dbb99f
--- /dev/null
+++ b/src/com/android/gallery3d/util/RangeIntArray.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.util;
+
+// This is an array whose index ranges from min to max (inclusive).
+public class RangeIntArray {
+ private int[] mData;
+ private int mOffset;
+
+ public RangeIntArray(int min, int max) {
+ mData = new int[max - min + 1];
+ mOffset = min;
+ }
+
+ // Wraps around an existing array
+ public RangeIntArray(int[] src, int min, int max) {
+ mData = src;
+ mOffset = min;
+ }
+
+ public void put(int i, int object) {
+ mData[i - mOffset] = object;
+ }
+
+ public int get(int i) {
+ return mData[i - mOffset];
+ }
+
+ public int indexOf(int object) {
+ for (int i = 0; i < mData.length; i++) {
+ if (mData[i] == object) return i + mOffset;
+ }
+ return Integer.MAX_VALUE;
+ }
+}
diff --git a/src/com/android/gallery3d/util/ReverseGeocoder.java b/src/com/android/gallery3d/util/ReverseGeocoder.java
index d253b4b..3df4c49 100644
--- a/src/com/android/gallery3d/util/ReverseGeocoder.java
+++ b/src/com/android/gallery3d/util/ReverseGeocoder.java
@@ -16,8 +16,6 @@
package com.android.gallery3d.util;
-import com.android.gallery3d.common.BlobCache;
-
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
@@ -26,6 +24,8 @@
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import com.android.gallery3d.common.BlobCache;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
diff --git a/src/com/android/gallery3d/util/SpinnerVisibilitySetter.java b/src/com/android/gallery3d/util/SpinnerVisibilitySetter.java
index 6ccc264..1e0534e 100644
--- a/src/com/android/gallery3d/util/SpinnerVisibilitySetter.java
+++ b/src/com/android/gallery3d/util/SpinnerVisibilitySetter.java
@@ -21,6 +21,7 @@
import android.os.Message;
import android.os.SystemClock;
+import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
/**
@@ -47,25 +48,29 @@
new WeakHashMap<Activity, SpinnerVisibilitySetter>();
private long mSpinnerVisibilityStartTime = -1;
- private Activity mActivity;
+ private WeakReference<Activity> mActivityRef;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
+ Activity activity = mActivityRef.get();
+
switch(msg.what) {
case MSG_SHOW_SPINNER:
removeMessages(MSG_SHOW_SPINNER);
+ if (activity == null) break;
if (mSpinnerVisibilityStartTime >= 0) break;
mSpinnerVisibilityStartTime = SystemClock.elapsedRealtime();
- mActivity.setProgressBarIndeterminateVisibility(true);
+ activity.setProgressBarIndeterminateVisibility(true);
break;
case MSG_HIDE_SPINNER:
removeMessages(MSG_HIDE_SPINNER);
+ if (activity == null) break;
if (mSpinnerVisibilityStartTime < 0) break;
long t = SystemClock.elapsedRealtime() - mSpinnerVisibilityStartTime;
if (t >= MIN_SPINNER_DISPLAY_TIME) {
mSpinnerVisibilityStartTime = -1;
- mActivity.setProgressBarIndeterminateVisibility(false);
+ activity.setProgressBarIndeterminateVisibility(false);
} else {
sendEmptyMessageDelayed(MSG_HIDE_SPINNER, MIN_SPINNER_DISPLAY_TIME - t);
}
@@ -91,7 +96,8 @@
}
private SpinnerVisibilitySetter(Activity activity) {
- mActivity = activity;
+ // Activity are keys. Value objects should not strongly refer to keys.
+ mActivityRef = new WeakReference<Activity>(activity);
}
public void setSpinnerVisibility(boolean visible) {
diff --git a/src/com/android/gallery3d/util/UpdateHelper.java b/src/com/android/gallery3d/util/UpdateHelper.java
index 9fdade6..f76705d 100644
--- a/src/com/android/gallery3d/util/UpdateHelper.java
+++ b/src/com/android/gallery3d/util/UpdateHelper.java
@@ -45,14 +45,6 @@
return original;
}
- public double update(float original, float update) {
- if (original != update) {
- mUpdated = true;
- original = update;
- }
- return original;
- }
-
public <T> T update(T original, T update) {
if (!Utils.equals(original, update)) {
mUpdated = true;
diff --git a/src_pd/com/android/gallery3d/picasasource/PicasaSource.java b/src_pd/com/android/gallery3d/picasasource/PicasaSource.java
index 8cfdac3..f849067 100644
--- a/src_pd/com/android/gallery3d/picasasource/PicasaSource.java
+++ b/src_pd/com/android/gallery3d/picasasource/PicasaSource.java
@@ -118,6 +118,14 @@
throw new UnsupportedOperationException();
}
+ public static long getPicasaId(MediaObject image) {
+ throw new UnsupportedOperationException();
+ }
+
+ public static String getUserAccount(Context context, MediaObject image) {
+ throw new UnsupportedOperationException();
+ }
+
public static ParcelFileDescriptor openFile(Context context, MediaObject image, String mode)
throws FileNotFoundException {
throw new UnsupportedOperationException();
diff --git a/tests/Android.mk b/tests/Android.mk
index 602f693..cfd0791 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -10,7 +10,6 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := Gallery2Tests
-LOCAL_CERTIFICATE := media
LOCAL_INSTRUMENTATION_FOR := Gallery2
diff --git a/tests/src/com/android/gallery3d/common/UtilsTest.java b/tests/src/com/android/gallery3d/common/UtilsTest.java
index b355244..a20ebeb 100644
--- a/tests/src/com/android/gallery3d/common/UtilsTest.java
+++ b/tests/src/com/android/gallery3d/common/UtilsTest.java
@@ -151,30 +151,6 @@
assertFalse(Utils.equals(a, b));
}
- public void testIsPowerOf2() {
- for (int i = 0; i < 31; i++) {
- int v = (1 << i);
- assertTrue(Utils.isPowerOf2(v));
- }
-
- int[] f = new int[] {3, 5, 6, 7, 9, 10, 65535, Integer.MAX_VALUE - 1,
- Integer.MAX_VALUE };
- for (int v : f) {
- assertFalse(Utils.isPowerOf2(v));
- }
-
- int[] e = new int[] {0, -1, -2, -4, -65536, Integer.MIN_VALUE + 1,
- Integer.MIN_VALUE };
- for (int v : e) {
- try {
- Utils.isPowerOf2(v);
- fail();
- } catch (IllegalArgumentException ex) {
- // expected.
- }
- }
- }
-
public void testNextPowerOf2() {
int[] q = new int[] {1, 2, 3, 4, 5, 6, 10, 65535, (1 << 30) - 1, (1 << 30)};
int[] a = new int[] {1, 2, 4, 4, 8, 8, 16, 65536, (1 << 30) , (1 << 30)};
@@ -195,16 +171,6 @@
}
}
- public void testDistance() {
- assertFloatEq(0f, Utils.distance(0, 0, 0, 0));
- assertFloatEq(1f, Utils.distance(0, 1, 0, 0));
- assertFloatEq(1f, Utils.distance(0, 0, 0, 1));
- assertFloatEq(2f, Utils.distance(1, 2, 3, 2));
- assertFloatEq(5f, Utils.distance(1, 2, 1 + 3, 2 + 4));
- assertFloatEq(5f, Utils.distance(1, 2, 1 + 3, 2 + 4));
- assertFloatEq(Float.MAX_VALUE, Utils.distance(Float.MAX_VALUE, 0, 0, 0));
- }
-
public void testClamp() {
assertEquals(1000, Utils.clamp(300, 1000, 2000));
assertEquals(1300, Utils.clamp(1300, 1000, 2000));
@@ -227,14 +193,6 @@
assertFalse(Utils.isOpaque(0xAA0000FF));
}
- public static void testSwap() {
- Integer[] a = {1, 2, 3};
- Utils.swap(a, 0, 2);
- assertEquals(a[0].intValue(), 3);
- assertEquals(a[1].intValue(), 2);
- assertEquals(a[2].intValue(), 1);
- }
-
public static void assertFloatEq(float expected, float actual) {
if (Math.abs(actual - expected) > 1e-6) {
Log.v(TAG, "expected: " + expected + ", actual: " + actual);
diff --git a/tests/src/com/android/gallery3d/data/GalleryAppStub.java b/tests/src/com/android/gallery3d/data/GalleryAppStub.java
index 36075f4..47693d2 100644
--- a/tests/src/com/android/gallery3d/data/GalleryAppStub.java
+++ b/tests/src/com/android/gallery3d/data/GalleryAppStub.java
@@ -19,7 +19,6 @@
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.app.StateManager;
import com.android.gallery3d.ui.GLRoot;
-import com.android.gallery3d.ui.PositionRepository;
import com.android.gallery3d.util.ThreadPool;
import android.content.ContentResolver;
@@ -35,7 +34,6 @@
public DecodeUtils getDecodeService() { return null; }
public GLRoot getGLRoot() { return null; }
- public PositionRepository getPositionRepository() { return null; }
public Context getAndroidContext() { return null; }
diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
index f1663f4..ee43cb9 100644
--- a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
+++ b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
@@ -33,15 +33,16 @@
}
public void multiplyAlpha(float alpha) {}
public void translate(float x, float y, float z) {}
+ public void translate(float x, float y) {}
public void scale(float sx, float sy, float sz) {}
public void rotate(float angle, float x, float y, float z) {}
public boolean clipRect(int left, int top, int right, int bottom) {
throw new UnsupportedOperationException();
}
- public int save() {
+ public void save() {
throw new UnsupportedOperationException();
}
- public int save(int saveFlags) {
+ public void save(int saveFlags) {
throw new UnsupportedOperationException();
}
public void setBlendEnabled(boolean enabled) {}
@@ -56,6 +57,8 @@
public void drawTexture(BasicTexture texture,
int x, int y, int width, int height, float alpha) {}
public void drawTexture(BasicTexture texture, RectF source, RectF target) {}
+ public void drawTexture(BasicTexture texture, float[] mTextureTransform,
+ int x, int y, int w, int h) {}
public void drawMixed(BasicTexture from, BasicTexture to,
float ratio, int x, int y, int w, int h) {}
public void drawMixed(BasicTexture from, int to,
@@ -76,4 +79,5 @@
}
public void deleteRecycledResources() {}
public void multiplyMatrix(float[] mMatrix, int offset) {}
+ public void dumpStatisticsAndClear() {}
}
diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasTest.java b/tests/src/com/android/gallery3d/ui/GLCanvasTest.java
index 528b04f..72ccbfb 100644
--- a/tests/src/com/android/gallery3d/ui/GLCanvasTest.java
+++ b/tests/src/com/android/gallery3d/ui/GLCanvasTest.java
@@ -66,26 +66,6 @@
}
@SmallTest
- public void testAnimationTime() {
- GL11 glStub = new GLStub();
- GLCanvas canvas = new GLCanvasImpl(glStub);
-
- long[] testData = {0, 1, 2, 1000, 10000, Long.MAX_VALUE};
-
- for (long v : testData) {
- canvas.setCurrentAnimationTimeMillis(v);
- assertEquals(v, canvas.currentAnimationTimeMillis());
- }
-
- try {
- canvas.setCurrentAnimationTimeMillis(-1);
- fail();
- } catch (Throwable ex) {
- // expected.
- }
- }
-
- @SmallTest
public void testSetColor() {
new SetColorTest().run();
}
@@ -380,370 +360,6 @@
}
@SmallTest
- public void testClipRect() {
- // The test is currently broken, waiting for the fix
- // new ClipRectTest().run();
- }
-
- private static class ClipRectTest extends GLStub {
- int mX, mY, mWidth, mHeight;
-
- @Override
- public void glScissor(int x, int y, int width, int height) {
- mX = x;
- mY = 100 - y - height; // flip in Y direction
- mWidth = width;
- mHeight = height;
- }
-
- private void assertClipRect(int x, int y, int width, int height) {
- assertEquals(x, mX);
- assertEquals(y, mY);
- assertEquals(width, mWidth);
- assertEquals(height, mHeight);
- }
-
- private void assertEmptyClipRect() {
- assertEquals(0, mWidth);
- assertEquals(0, mHeight);
- }
-
- void run() {
- GLCanvas canvas = new GLCanvasImpl(this);
- canvas.setSize(100, 100);
- canvas.save();
- assertClipRect(0, 0, 100, 100);
-
- assertTrue(canvas.clipRect(10, 10, 70, 70));
- canvas.save();
- assertClipRect(10, 10, 60, 60);
-
- assertTrue(canvas.clipRect(30, 30, 90, 90));
- canvas.save();
- assertClipRect(30, 30, 40, 40);
-
- assertTrue(canvas.clipRect(40, 40, 60, 90));
- assertClipRect(40, 40, 20, 30);
-
- assertFalse(canvas.clipRect(30, 30, 70, 40));
- assertEmptyClipRect();
- assertFalse(canvas.clipRect(0, 0, 100, 100));
- assertEmptyClipRect();
-
- canvas.restore();
- assertClipRect(30, 30, 40, 40);
-
- canvas.restore();
- assertClipRect(10, 10, 60, 60);
-
- canvas.restore();
- assertClipRect(0, 0, 100, 100);
-
- canvas.translate(10, 20, 30);
- assertTrue(canvas.clipRect(10, 10, 70, 70));
- canvas.save();
- assertClipRect(20, 30, 60, 60);
- }
- }
-
- @SmallTest
- public void testSaveRestore() {
- new SaveRestoreTest().run();
- }
-
- private static class SaveRestoreTest extends GLStub {
- int mX, mY, mWidth, mHeight;
-
- @Override
- public void glScissor(int x, int y, int width, int height) {
- mX = x;
- mY = 100 - y - height; // flip in Y direction
- mWidth = width;
- mHeight = height;
- }
-
- private void assertClipRect(int x, int y, int width, int height) {
- assertEquals(x, mX);
- assertEquals(y, mY);
- assertEquals(width, mWidth);
- assertEquals(height, mHeight);
- }
-
- void run() {
- GLCanvas canvas = new GLCanvasImpl(this);
- canvas.setSize(100, 100);
-
- canvas.setAlpha(0.7f);
- assertTrue(canvas.clipRect(10, 10, 70, 70));
-
- canvas.save(canvas.SAVE_FLAG_CLIP);
- canvas.setAlpha(0.6f);
- assertTrue(canvas.clipRect(30, 30, 90, 90));
-
- canvas.save(canvas.SAVE_FLAG_CLIP | canvas.SAVE_FLAG_ALPHA);
- canvas.setAlpha(0.5f);
- assertTrue(canvas.clipRect(40, 40, 60, 90));
-
- assertEquals(0.5f, canvas.getAlpha());
- assertClipRect(40, 40, 20, 30);
-
- canvas.restore(); // now both clipping rect and alpha are restored.
- assertEquals(0.6f, canvas.getAlpha());
- assertClipRect(30, 30, 40, 40);
-
- canvas.restore(); // now only clipping rect is restored.
-
- canvas.save(0);
- canvas.save(0);
- canvas.restore();
- canvas.restore();
-
- assertEquals(0.6f, canvas.getAlpha());
- assertTrue(canvas.clipRect(10, 10, 60, 60));
- }
- }
-
- @SmallTest
- public void testDrawTexture() {
- new DrawTextureTest().run();
- new DrawTextureMixedTest().run();
- }
-
- private static class MyTexture extends BasicTexture {
- boolean mIsOpaque;
- int mBindCalled;
-
- MyTexture(GLCanvas canvas, int id, boolean isOpaque) {
- super(canvas, id, STATE_LOADED);
- setSize(1, 1);
- mIsOpaque = isOpaque;
- }
-
- @Override
- protected boolean onBind(GLCanvas canvas) {
- mBindCalled++;
- return true;
- }
-
- public boolean isOpaque() {
- return mIsOpaque;
- }
- }
-
- private static class DrawTextureTest extends GLMock {
- int mDrawTexiOESCalled;
- int mDrawArrayCalled;
- int[] mResult = new int[4];
-
- @Override
- public void glDrawTexiOES(int x, int y, int z,
- int width, int height) {
- mDrawTexiOESCalled++;
- }
-
- @Override
- public void glDrawArrays(int mode, int first, int count) {
- assertNotNull(mGLVertexPointer);
- assertEquals(GL10.GL_TRIANGLE_STRIP, mode);
- assertEquals(4, count);
- mGLVertexPointer.bindByteBuffer();
-
- double[] coord = new double[4];
- mGLVertexPointer.getArrayElement(first, coord);
- mResult[0] = (int) coord[0];
- mResult[1] = (int) coord[1];
- mGLVertexPointer.getArrayElement(first + 1, coord);
- mResult[2] = (int) coord[0];
- mResult[3] = (int) coord[1];
- mDrawArrayCalled++;
- }
-
- void run() {
- GLCanvas canvas = new GLCanvasImpl(this);
- canvas.setSize(400, 300);
- MyTexture texture = new MyTexture(canvas, 42, false); // non-opaque
- MyTexture texture_o = new MyTexture(canvas, 47, true); // opaque
-
- // Draw a non-opaque texture
- canvas.drawTexture(texture, 100, 200, 300, 400);
- assertEquals(42, mGLBindTextureId);
- assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE_ENV_MODE));
- assertPremultipliedBlending(this);
- assertFalse(mGLStencilEnabled);
-
- // Draw an opaque texture
- canvas.drawTexture(texture_o, 100, 200, 300, 400);
- assertEquals(47, mGLBindTextureId);
- assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE_ENV_MODE));
- assertFalse(mGLBlendEnabled);
-
- // Draw a non-opaque texture with alpha = 0.5
- canvas.setAlpha(0.5f);
- canvas.drawTexture(texture, 100, 200, 300, 400);
- assertEquals(42, mGLBindTextureId);
- assertEquals(0x80808080, mGLColor);
- assertEquals(GL_MODULATE, getTexEnvi(GL_TEXTURE_ENV_MODE));
- assertPremultipliedBlending(this);
- assertFalse(mGLStencilEnabled);
-
- // Draw an non-opaque texture with overriden alpha = 1
- canvas.drawTexture(texture, 100, 200, 300, 400, 1f);
- assertEquals(42, mGLBindTextureId);
- assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE_ENV_MODE));
- assertPremultipliedBlending(this);
-
- // Draw an opaque texture with overriden alpha = 1
- canvas.drawTexture(texture_o, 100, 200, 300, 400, 1f);
- assertEquals(47, mGLBindTextureId);
- assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE_ENV_MODE));
- assertFalse(mGLBlendEnabled);
-
- // Draw an opaque texture with overridden alpha = 0.25
- canvas.drawTexture(texture_o, 100, 200, 300, 400, 0.25f);
- assertEquals(47, mGLBindTextureId);
- assertEquals(0x40404040, mGLColor);
- assertEquals(GL_MODULATE, getTexEnvi(GL_TEXTURE_ENV_MODE));
- assertPremultipliedBlending(this);
-
- // Draw an opaque texture with overridden alpha = 0.125
- // but with some rotation so it will use DrawArray.
- canvas.save();
- canvas.rotate(30, 0, 0, 1);
- canvas.drawTexture(texture_o, 100, 200, 300, 400, 0.125f);
- canvas.restore();
- assertEquals(47, mGLBindTextureId);
- assertEquals(0x20202020, mGLColor);
- assertEquals(GL_MODULATE, getTexEnvi(GL_TEXTURE_ENV_MODE));
- assertPremultipliedBlending(this);
-
- // We have drawn seven textures above.
- assertEquals(1, mDrawArrayCalled);
- assertEquals(6, mDrawTexiOESCalled);
-
- // translate and scale does not affect whether we
- // can use glDrawTexiOES, but rotate may.
- canvas.translate(10, 20, 30);
- canvas.drawTexture(texture, 100, 200, 300, 400);
- assertEquals(7, mDrawTexiOESCalled);
-
- canvas.scale(10, 20, 30);
- canvas.drawTexture(texture, 100, 200, 300, 400);
- assertEquals(8, mDrawTexiOESCalled);
-
- canvas.rotate(90, 1, 2, 3);
- canvas.drawTexture(texture, 100, 200, 300, 400);
- assertEquals(8, mDrawTexiOESCalled);
-
- canvas.rotate(-90, 1, 2, 3);
- canvas.drawTexture(texture, 100, 200, 300, 400);
- assertEquals(9, mDrawTexiOESCalled);
-
- canvas.rotate(180, 0, 0, 1);
- canvas.drawTexture(texture, 100, 200, 300, 400);
- assertEquals(9, mDrawTexiOESCalled);
-
- canvas.rotate(180, 0, 0, 1);
- canvas.drawTexture(texture, 100, 200, 300, 400);
- assertEquals(10, mDrawTexiOESCalled);
-
- assertEquals(3, mDrawArrayCalled);
-
- assertTrue(texture.isLoaded(canvas));
- texture.recycle();
- assertFalse(texture.isLoaded(canvas));
- canvas.deleteRecycledResources();
-
- assertTrue(texture_o.isLoaded(canvas));
- texture_o.recycle();
- assertFalse(texture_o.isLoaded(canvas));
- }
- }
-
- private static class DrawTextureMixedTest extends GLMock {
-
- boolean mTexture2DEnabled0, mTexture2DEnabled1;
- int mBindTexture0;
- int mBindTexture1;
-
- @Override
- public void glEnable(int cap) {
- if (cap == GL_TEXTURE_2D) {
- texture2DEnable(true);
- }
- }
-
- @Override
- public void glDisable(int cap) {
- if (cap == GL_TEXTURE_2D) {
- texture2DEnable(false);
- }
- }
-
- private void texture2DEnable(boolean enable) {
- if (mGLActiveTexture == GL_TEXTURE0) {
- mTexture2DEnabled0 = enable;
- } else if (mGLActiveTexture == GL_TEXTURE1) {
- mTexture2DEnabled1 = enable;
- } else {
- fail();
- }
- }
-
- @Override
- public void glTexEnvfv(int target, int pname, float[] params, int offset) {
- if (target == GL_TEXTURE_ENV && pname == GL_TEXTURE_ENV_COLOR) {
- assertEquals(0.5f, params[offset + 3]);
- }
- }
-
- @Override
- public void glBindTexture(int target, int texture) {
- if (target == GL_TEXTURE_2D) {
- if (mGLActiveTexture == GL_TEXTURE0) {
- mBindTexture0 = texture;
- } else if (mGLActiveTexture == GL_TEXTURE1) {
- mBindTexture1 = texture;
- } else {
- fail();
- }
- }
- }
-
- void run() {
- GLCanvas canvas = new GLCanvasImpl(this);
- canvas.setSize(400, 300);
- MyTexture from = new MyTexture(canvas, 42, false); // non-opaque
- MyTexture to = new MyTexture(canvas, 47, true); // opaque
-
- canvas.drawMixed(from, to, 0.5f, 100, 200, 300, 400);
- assertEquals(42, mBindTexture0);
- assertEquals(47, mBindTexture1);
- assertTrue(mTexture2DEnabled0);
- assertFalse(mTexture2DEnabled1);
-
- assertEquals(GL_COMBINE, getTexEnvi(GL_TEXTURE1, GL_TEXTURE_ENV_MODE));
- assertEquals(GL_INTERPOLATE, getTexEnvi(GL_TEXTURE1, GL_COMBINE_RGB));
- assertEquals(GL_INTERPOLATE, getTexEnvi(GL_TEXTURE1, GL_COMBINE_ALPHA));
- assertEquals(GL_CONSTANT, getTexEnvi(GL_TEXTURE1, GL_SRC2_RGB));
- assertEquals(GL_CONSTANT, getTexEnvi(GL_TEXTURE1, GL_SRC2_ALPHA));
- assertEquals(GL_SRC_ALPHA, getTexEnvi(GL_TEXTURE1, GL_OPERAND2_RGB));
- assertEquals(GL_SRC_ALPHA, getTexEnvi(GL_TEXTURE1, GL_OPERAND2_ALPHA));
-
- assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE0, GL_TEXTURE_ENV_MODE));
-
- assertFalse(mGLBlendEnabled);
-
- canvas.drawMixed(from, to, 0, 100, 200, 300, 400);
- assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE0, GL_TEXTURE_ENV_MODE));
- assertEquals(42, mBindTexture0);
-
- canvas.drawMixed(from, to, 1, 100, 200, 300, 400);
- assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE0, GL_TEXTURE_ENV_MODE));
- assertEquals(47, mBindTexture0);
- }
- }
-
- @SmallTest
public void testGetGLInstance() {
GL11 glStub = new GLStub();
GLCanvas canvas = new GLCanvasImpl(glStub);
diff --git a/tests/src/com/android/gallery3d/ui/GLViewTest.java b/tests/src/com/android/gallery3d/ui/GLViewTest.java
index a9377bf..b17b254 100644
--- a/tests/src/com/android/gallery3d/ui/GLViewTest.java
+++ b/tests/src/com/android/gallery3d/ui/GLViewTest.java
@@ -102,32 +102,6 @@
}
@SmallTest
- public void testPaddings() {
- GLView view = new GLView();
-
- Rect p = view.getPaddings();
- assertEquals(0, p.left);
- assertEquals(0, p.top);
- assertEquals(0, p.right);
- assertEquals(0, p.bottom);
-
- view.setPaddings(10, 20, 30, 100);
- p = view.getPaddings();
- assertEquals(10, p.left);
- assertEquals(20, p.top);
- assertEquals(30, p.right);
- assertEquals(100, p.bottom);
-
- p = new Rect(11, 22, 33, 104);
- view.setPaddings(p);
- p = view.getPaddings();
- assertEquals(11, p.left);
- assertEquals(22, p.top);
- assertEquals(33, p.right);
- assertEquals(104, p.bottom);
- }
-
- @SmallTest
public void testParent() {
GLView a = new GLView();
GLView b = new GLView();
diff --git a/tests/src/com/android/gallery3d/ui/TextureTest.java b/tests/src/com/android/gallery3d/ui/TextureTest.java
index fb26060..be2356c 100644
--- a/tests/src/com/android/gallery3d/ui/TextureTest.java
+++ b/tests/src/com/android/gallery3d/ui/TextureTest.java
@@ -43,6 +43,11 @@
return true;
}
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+
public boolean isOpaque() {
mOpaqueCalled++;
return true;
@@ -90,24 +95,6 @@
}
@SmallTest
- public void testRawTexture() {
- GL11 glStub = new GLStub();
- GLCanvas canvas = new GLCanvasImpl(glStub);
- RawTexture texture = RawTexture.newInstance(canvas);
- texture.onBind(canvas);
-
- GLCanvas canvas2 = new GLCanvasImpl(new GLStub());
- try {
- texture.onBind(canvas2);
- fail();
- } catch (RuntimeException ex) {
- // expected.
- }
-
- assertTrue(texture.isOpaque());
- }
-
- @SmallTest
public void testColorTexture() {
GLCanvasMock canvas = new GLCanvasMock();
ColorTexture texture = new ColorTexture(0x12345678);
@@ -189,6 +176,11 @@
return true;
}
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+
public boolean isOpaque() {
return true;
}
diff --git a/tests/src/com/android/gallery3d/util/ProfileTest.java b/tests/src/com/android/gallery3d/util/ProfileTest.java
new file mode 100644
index 0000000..798b905
--- /dev/null
+++ b/tests/src/com/android/gallery3d/util/ProfileTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.util;
+
+import com.android.gallery3d.util.Profile;
+
+import android.os.Environment;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+@SmallTest
+public class ProfileTest extends TestCase {
+ private static final String TAG = "ProfileTest";
+ private static final String TEST_FILE =
+ Environment.getExternalStorageDirectory().getPath() + "/test.dat";
+
+
+ public void testProfile() throws IOException {
+ ProfileData p = new ProfileData();
+ ParsedProfile q;
+ String[] A = {"A"};
+ String[] B = {"B"};
+ String[] AC = {"A", "C"};
+ String[] AD = {"A", "D"};
+
+ // Empty profile
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertTrue(q.mEntries.isEmpty());
+ assertTrue(q.mSymbols.isEmpty());
+
+ // Only one sample
+ p.addSample(A);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(1, q.mEntries.size());
+ assertEquals(1, q.mSymbols.size());
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+
+ // Two samples at the same place
+ p.addSample(A);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(1, q.mEntries.size());
+ assertEquals(1, q.mSymbols.size());
+ assertEquals(2, q.mEntries.get(0).sampleCount);
+
+ // Two samples at the different places
+ p.reset();
+ p.addSample(A);
+ p.addSample(B);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(2, q.mEntries.size());
+ assertEquals(2, q.mSymbols.size());
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+ assertEquals(1, q.mEntries.get(1).sampleCount);
+
+ // depth > 1
+ p.reset();
+ p.addSample(AC);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(1, q.mEntries.size());
+ assertEquals(2, q.mSymbols.size());
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+
+ // two samples (AC and AD)
+ p.addSample(AD);
+ p.dumpToFile(TEST_FILE);
+ q = new ParsedProfile(TEST_FILE);
+ assertEquals(2, q.mEntries.size());
+ assertEquals(3, q.mSymbols.size()); // three symbols: A, C, D
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+ assertEquals(1, q.mEntries.get(0).sampleCount);
+
+ // Remove the test file
+ new File(TEST_FILE).delete();
+ }
+}
+
+class ParsedProfile {
+ public class Entry {
+ int sampleCount;
+ int stackId[];
+ }
+
+ ArrayList<Entry> mEntries = new ArrayList<Entry>();
+ HashMap<Integer, String> mSymbols = new HashMap<Integer, String>();
+ private DataInputStream mIn;
+ private byte[] mScratch = new byte[4]; // scratch buffer for readInt
+
+ public ParsedProfile(String filename) throws IOException {
+ mIn = new DataInputStream(new FileInputStream(filename));
+
+ Entry entry = parseOneEntry();
+ checkIsFirstEntry(entry);
+
+ while (true) {
+ entry = parseOneEntry();
+ if (entry.sampleCount == 0) {
+ checkIsLastEntry(entry);
+ break;
+ }
+ mEntries.add(entry);
+ }
+
+ // Read symbol table
+ while (true) {
+ String line = mIn.readLine();
+ if (line == null) break;
+ String[] fields = line.split(" +");
+ checkIsValidSymbolLine(fields);
+ mSymbols.put(Integer.decode(fields[0]), fields[1]);
+ }
+ }
+
+ private void checkIsFirstEntry(Entry entry) {
+ Assert.assertEquals(0, entry.sampleCount);
+ Assert.assertEquals(3, entry.stackId.length);
+ Assert.assertEquals(1, entry.stackId[0]);
+ Assert.assertTrue(entry.stackId[1] > 0); // sampling period
+ Assert.assertEquals(0, entry.stackId[2]); // padding
+ }
+
+ private void checkIsLastEntry(Entry entry) {
+ Assert.assertEquals(0, entry.sampleCount);
+ Assert.assertEquals(1, entry.stackId.length);
+ Assert.assertEquals(0, entry.stackId[0]);
+ }
+
+ private void checkIsValidSymbolLine(String[] fields) {
+ Assert.assertEquals(2, fields.length);
+ Assert.assertTrue(fields[0].startsWith("0x"));
+ }
+
+ private Entry parseOneEntry() throws IOException {
+ int sampleCount = readInt();
+ int depth = readInt();
+ Entry e = new Entry();
+ e.sampleCount = sampleCount;
+ e.stackId = new int[depth];
+ for (int i = 0; i < depth; i++) {
+ e.stackId[i] = readInt();
+ }
+ return e;
+ }
+
+ private int readInt() throws IOException {
+ mIn.read(mScratch, 0, 4);
+ return (mScratch[0] & 0xff) |
+ ((mScratch[1] & 0xff) << 8) |
+ ((mScratch[2] & 0xff) << 16) |
+ ((mScratch[3] & 0xff) << 24);
+ }
+}