am b62f354d: am 582c3bb9: Merge "Gallery2: Use ICS alert drawable"
* commit 'b62f354d4f890a20f6bec37eac83226f75a4a475':
Gallery2: Use ICS alert drawable
diff --git a/Android.mk b/Android.mk
index 0f5170f..ea71b39 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1,4 +1,5 @@
LOCAL_PATH:= $(call my-dir)
+
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
@@ -8,17 +9,32 @@
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 libjni_eglfence
+
+LOCAL_REQUIRED_MODULES := libjni_mosaic libjni_eglfence
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
include $(BUILD_PACKAGE)
-# Use the following include to make our test apk.
-include $(call all-makefiles-under,$(LOCAL_PATH))
+include $(call all-makefiles-under, jni)
+
+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..2a69ce1 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,7 +35,10 @@
<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:logo="@mipmap/ic_launcher_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:configChanges="orientation|keyboardHidden|screenSize">
@@ -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" />
@@ -197,10 +208,11 @@
</intent-filter>
</activity>
<activity android:name="com.android.gallery3d.photoeditor.PhotoEditor"
- android:label="@string/photoeditor_name"
+ android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen"
android:configChanges="keyboardHidden|orientation|screenSize"
- android:hardwareAccelerated="true">
+ android:hardwareAccelerated="true"
+ android:process=":edit">
<intent-filter>
<action android:name="android.intent.action.EDIT" />
<data android:mimeType="image/*" />
@@ -238,8 +250,62 @@
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:windowSoftInputMode="stateAlwaysHidden|adjustPan">
+ <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-alias android:icon="@mipmap/ic_launcher_camera"
+ android:label="@string/camera_label"
+ android:name="com.android.camera.CameraLauncher"
+ android:targetActivity="com.android.camera.Camera" >
+ <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>
+ </activity-alias>
+
+ <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: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:windowSoftInputMode="stateAlwaysHidden|adjustPan">
+ </activity>
<receiver android:name="com.android.gallery3d.gadget.PhotoAppWidgetProvider"
android:label="@string/appwidget_title">
<intent-filter>
@@ -256,6 +322,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/Entry.java b/gallerycommon/src/com/android/gallery3d/common/Entry.java
index b8cc512..3f1644e 100644
--- a/gallerycommon/src/com/android/gallery3d/common/Entry.java
+++ b/gallerycommon/src/com/android/gallery3d/common/Entry.java
@@ -48,6 +48,8 @@
boolean fullText() default false;
String defaultValue() default "";
+
+ boolean unique() default false;
}
public void clear() {
diff --git a/gallerycommon/src/com/android/gallery3d/common/EntrySchema.java b/gallerycommon/src/com/android/gallery3d/common/EntrySchema.java
index 46de03f..7bf7e43 100644
--- a/gallerycommon/src/com/android/gallery3d/common/EntrySchema.java
+++ b/gallerycommon/src/com/android/gallery3d/common/EntrySchema.java
@@ -300,6 +300,7 @@
StringBuilder sql = new StringBuilder("CREATE TABLE ");
sql.append(tableName);
sql.append(" (_id INTEGER PRIMARY KEY AUTOINCREMENT");
+ StringBuilder unique = new StringBuilder();
for (ColumnInfo column : mColumnInfo) {
if (!column.isId()) {
sql.append(',');
@@ -310,8 +311,18 @@
sql.append(" DEFAULT ");
sql.append(column.defaultValue);
}
+ if (column.unique) {
+ if (unique.length() == 0) {
+ unique.append(column.name);
+ } else {
+ unique.append(',').append(column.name);
+ }
+ }
}
}
+ if (unique.length() > 0) {
+ sql.append(",UNIQUE(").append(unique).append(')');
+ }
sql.append(");");
logExecSql(db, sql.toString());
sql.setLength(0);
@@ -493,7 +504,7 @@
// Add the column to the array.
int index = columns.size();
- columns.add(new ColumnInfo(info.value(), type, info.indexed(),
+ columns.add(new ColumnInfo(info.value(), type, info.indexed(), info.unique(),
info.fullText(), info.defaultValue(), field, index));
}
}
@@ -504,16 +515,18 @@
public final String name;
public final int type;
public final boolean indexed;
+ public final boolean unique;
public final boolean fullText;
public final String defaultValue;
public final Field field;
public final int projectionIndex;
- public ColumnInfo(String name, int type, boolean indexed,
+ public ColumnInfo(String name, int type, boolean indexed, boolean unique,
boolean fullText, String defaultValue, Field field, int projectionIndex) {
this.name = name.toLowerCase();
this.type = type;
this.indexed = indexed;
+ this.unique = unique;
this.fullText = fullText;
this.defaultValue = defaultValue;
this.field = field;
diff --git a/gallerycommon/src/com/android/gallery3d/common/FileCache.java b/gallerycommon/src/com/android/gallery3d/common/FileCache.java
index 938ef8c..a69487f 100644
--- a/gallerycommon/src/com/android/gallery3d/common/FileCache.java
+++ b/gallerycommon/src/com/android/gallery3d/common/FileCache.java
@@ -191,7 +191,6 @@
private synchronized void initialize() {
if (mInitialized) return;
- mInitialized = true;
if (!mRootDir.isDirectory()) {
mRootDir.mkdirs();
@@ -209,6 +208,10 @@
cursor.close();
}
if (mTotalBytes > mCapacity) freeSomeSpaceIfNeed(MAX_DELETE_COUNT);
+
+ // Mark initialized when everything above went through. If an exception was thrown,
+ // initialize() will be retried later.
+ mInitialized = true;
}
private void freeSomeSpaceIfNeed(int maxDeleteFileCount) {
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/jni/Android.mk b/jni/Android.mk
new file mode 100644
index 0000000..eedfa0b
--- /dev/null
+++ b/jni/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES
+
+LOCAL_SRC_FILES := jni_egl_fence.cpp
+
+LOCAL_SHARED_LIBRARIES := libcutils libEGL
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := libjni_eglfence
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/jni/jni_egl_fence.cpp b/jni/jni_egl_fence.cpp
new file mode 100644
index 0000000..8d557df
--- /dev/null
+++ b/jni/jni_egl_fence.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#include "jni_egl_fence.h"
+
+#include <cutils/log.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+void
+Java_com_android_gallery3d_photoeditor_FilterStack_nativeEglSetFenceAndWait(JNIEnv* env,
+ jobject thiz) {
+ EGLDisplay display = eglGetCurrentDisplay();
+
+ // Create a egl fence and wait for egl to return it.
+ // Additional reference on egl fence sync can be found in:
+ // http://www.khronos.org/registry/vg/extensions/KHR/EGL_KHR_fence_sync.txt
+ EGLSyncKHR fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL);
+ if (fence == EGL_NO_SYNC_KHR) {
+ return;
+ }
+
+ EGLint result = eglClientWaitSyncKHR(display,
+ fence,
+ EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
+ EGL_FOREVER_KHR);
+ if (result == EGL_FALSE) {
+ ALOGE("EGL FENCE: error waiting for fence: %#x", eglGetError());
+ }
+ eglDestroySyncKHR(display, fence);
+}
diff --git a/jni/jni_egl_fence.h b/jni/jni_egl_fence.h
new file mode 100644
index 0000000..6b2c20a
--- /dev/null
+++ b/jni/jni_egl_fence.h
@@ -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.
+ */
+
+#ifndef COM_ANDROID_GALLERY3D_PHOTOEDITOR_JNI_EGL_FENSE_H
+#define COM_ANDROID_GALLERY3D_PHOTOEDITOR_JNI_EGL_FENSE_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT void JNICALL
+Java_com_android_gallery3d_photoeditor_FilterStack_nativeEglSetFenceAndWait(JNIEnv* env,
+ jobject thiz);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* COM_ANDROID_GALLERY3D_PHOTOEDITOR_JNI_EGL_FENSE_H */
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/camera_crop_holo.png b/res/drawable-hdpi/camera_crop_holo.png
index 477e67c..0c3506c 100644
--- a/res/drawable-hdpi/camera_crop_holo.png
+++ b/res/drawable-hdpi/camera_crop_holo.png
Binary files differ
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/grid_pressed.9.png b/res/drawable-hdpi/grid_pressed.9.png
index 5e1d2e3..dff62c7 100644
--- a/res/drawable-hdpi/grid_pressed.9.png
+++ b/res/drawable-hdpi/grid_pressed.9.png
Binary files differ
diff --git a/res/drawable-hdpi/grid_selected.9.png b/res/drawable-hdpi/grid_selected.9.png
index 24a78d2..d8867ac 100644
--- a/res/drawable-hdpi/grid_selected.9.png
+++ b/res/drawable-hdpi/grid_selected.9.png
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/camera_crop_holo.png b/res/drawable-mdpi/camera_crop_holo.png
index ac56823..7cf744e 100644
--- a/res/drawable-mdpi/camera_crop_holo.png
+++ b/res/drawable-mdpi/camera_crop_holo.png
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/grid_pressed.9.png b/res/drawable-mdpi/grid_pressed.9.png
index 74ba39c..68a957f 100644
--- a/res/drawable-mdpi/grid_pressed.9.png
+++ b/res/drawable-mdpi/grid_pressed.9.png
Binary files differ
diff --git a/res/drawable-mdpi/grid_selected.9.png b/res/drawable-mdpi/grid_selected.9.png
index 0310273..a729636 100644
--- a/res/drawable-mdpi/grid_selected.9.png
+++ b/res/drawable-mdpi/grid_selected.9.png
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-sw600dp-mdpi/bg_vidcontrol.png b/res/drawable-sw600dp-mdpi/bg_vidcontrol.png
deleted file mode 100644
index dfe2da1..0000000
--- a/res/drawable-sw600dp-mdpi/bg_vidcontrol.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png b/res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png
deleted file mode 100644
index 3d1a8bf..0000000
--- a/res/drawable-sw600dp-mdpi/ic_vidcontrol_pause.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png b/res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png
deleted file mode 100644
index cc02166..0000000
--- a/res/drawable-sw600dp-mdpi/ic_vidcontrol_play.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png b/res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png
deleted file mode 100644
index bf8d529..0000000
--- a/res/drawable-sw600dp-mdpi/ic_vidcontrol_reload.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/scrubber_knob.png b/res/drawable-sw600dp-mdpi/scrubber_knob.png
deleted file mode 100644
index 426e3da..0000000
--- a/res/drawable-sw600dp-mdpi/scrubber_knob.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp/bg_vidcontrol.png b/res/drawable-sw600dp/bg_vidcontrol.png
index 0eb8148..dfe2da1 100644
--- a/res/drawable-sw600dp/bg_vidcontrol.png
+++ b/res/drawable-sw600dp/bg_vidcontrol.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_pan_thumb.9.png b/res/drawable-sw600dp/ic_pan_thumb.9.png
similarity index 100%
rename from res/drawable-sw600dp-mdpi/ic_pan_thumb.9.png
rename to res/drawable-sw600dp/ic_pan_thumb.9.png
Binary files differ
diff --git a/res/drawable-sw600dp/ic_vidcontrol_pause.png b/res/drawable-sw600dp/ic_vidcontrol_pause.png
index 4d274c0..3d1a8bf 100644
--- a/res/drawable-sw600dp/ic_vidcontrol_pause.png
+++ b/res/drawable-sw600dp/ic_vidcontrol_pause.png
Binary files differ
diff --git a/res/drawable-sw600dp/ic_vidcontrol_play.png b/res/drawable-sw600dp/ic_vidcontrol_play.png
index 6f97a64..cc02166 100644
--- a/res/drawable-sw600dp/ic_vidcontrol_play.png
+++ b/res/drawable-sw600dp/ic_vidcontrol_play.png
Binary files differ
diff --git a/res/drawable-sw600dp/ic_vidcontrol_reload.png b/res/drawable-sw600dp/ic_vidcontrol_reload.png
index 2aaf491..bf8d529 100644
--- a/res/drawable-sw600dp/ic_vidcontrol_reload.png
+++ b/res/drawable-sw600dp/ic_vidcontrol_reload.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/ic_video_thumb.png b/res/drawable-sw600dp/ic_video_thumb.png
similarity index 100%
rename from res/drawable-sw600dp-mdpi/ic_video_thumb.png
rename to res/drawable-sw600dp/ic_video_thumb.png
Binary files differ
diff --git a/res/drawable-sw600dp/scrubber_knob.png b/res/drawable-sw600dp/scrubber_knob.png
index 4e415a2..426e3da 100644
--- a/res/drawable-sw600dp/scrubber_knob.png
+++ b/res/drawable-sw600dp/scrubber_knob.png
Binary files differ
diff --git a/res/drawable-xhdpi/camera_crop_holo.png b/res/drawable-xhdpi/camera_crop_holo.png
index 2c4eb5c..2987410 100644
--- a/res/drawable-xhdpi/camera_crop_holo.png
+++ b/res/drawable-xhdpi/camera_crop_holo.png
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/grid_pressed.9.png b/res/drawable-xhdpi/grid_pressed.9.png
index 4e50532..ea317b5 100644
--- a/res/drawable-xhdpi/grid_pressed.9.png
+++ b/res/drawable-xhdpi/grid_pressed.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/grid_selected.9.png b/res/drawable-xhdpi/grid_selected.9.png
index 5609df0..dec94ce 100644
--- a/res/drawable-xhdpi/grid_selected.9.png
+++ b/res/drawable-xhdpi/grid_selected.9.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/cropimage.xml b/res/layout/cropimage.xml
index aefebe8..c434fb6 100644
--- a/res/layout/cropimage.xml
+++ b/res/layout/cropimage.xml
@@ -17,9 +17,5 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <view class="com.android.gallery3d.ui.GLRootView"
- android:id="@+id/gl_root_view"
- android:background="@null"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ <include layout="@layout/gl_root_group"/>
</FrameLayout>
diff --git a/res/layout/dialog_picker.xml b/res/layout/dialog_picker.xml
index ba3f500..4a625a1 100644
--- a/res/layout/dialog_picker.xml
+++ b/res/layout/dialog_picker.xml
@@ -18,8 +18,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.android.gallery3d.ui.GLRootView
- android:id="@+id/gl_root_view"
+ <include layout="@layout/gl_root_group"
android:layout_weight="1"
android:layout_height="0dp"
android:layout_width="match_parent"/>
diff --git a/res/layout/gl_root_group.xml b/res/layout/gl_root_group.xml
new file mode 100644
index 0000000..76ff33b
--- /dev/null
+++ b/res/layout/gl_root_group.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <com.android.gallery3d.ui.GLRootView
+ android:id="@+id/gl_root_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ <View android:id="@+id/gl_root_cover"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"/>
+</merge>
diff --git a/res/layout/main.xml b/res/layout/main.xml
index b71ea50..d367301 100644
--- a/res/layout/main.xml
+++ b/res/layout/main.xml
@@ -1,16 +1,10 @@
<?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">
- <com.android.gallery3d.ui.GLRootView
- android:id="@+id/gl_root_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentBottom="true"
- android:layout_alignParentLeft="true"
- android:layout_alignParentRight="true"
- android:layout_alignParentTop="true"/>
+ <include layout="@layout/gl_root_group"/>
<FrameLayout android:id="@+id/footer"
android:visibility="gone"
android:layout_alignParentBottom="true"
diff --git a/res/layout/movie_view.xml b/res/layout/movie_view.xml
index bd0415c..75b8dfd 100644
--- a/res/layout/movie_view.xml
+++ b/res/layout/movie_view.xml
@@ -15,13 +15,13 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/root"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
+ android:id="@+id/movie_view_root"
+ android:background="@android:color/black"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<VideoView android:id="@+id/surface_view"
+ android:visibility="invisible"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
-
</RelativeLayout>
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
deleted file mode 100644
index 023a93b..0000000
--- a/res/menu-land/photo.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 Google Inc.
-
- 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.
--->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/action_import"
- android:title="@string/Import"
- android:icon="@drawable/ic_menu_ptp_holo_light"
- android:showAsAction="always|withText"
- android:visible="false" />
- <item android:id="@+id/action_share"
- android:icon="@drawable/ic_menu_share_holo_light"
- android:title="@string/share"
- android:enabled="true"
- android:actionProviderClass="android.widget.ShareActionProvider"
- android:visible="false"
- android:showAsAction="ifRoom" />
- <item android:id="@+id/action_delete"
- 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>
- <item android:id="@+id/action_slideshow"
- android:icon="@drawable/ic_menu_slideshow_holo_light"
- android:title="@string/slideshow"
- android:showAsAction="never" />
- <item android:id="@+id/action_edit"
- android:title="@string/edit"
- android:showAsAction="never"
- android:visible="false" />
- <item android:id="@+id/action_rotate_ccw"
- android:showAsAction="never"
- android:title="@string/rotate_left" />
- <item android:id="@+id/action_rotate_cw"
- android:showAsAction="never"
- android:title="@string/rotate_right" />
- <item android:id="@+id/action_crop"
- android:title="@string/crop_action"
- android:showAsAction="never" />
- <item android:id="@+id/action_setas"
- android:title="@string/set_image"
- android:showAsAction="never" />
- <item android:id="@+id/action_details"
- android:title="@string/details"
- android:showAsAction="never" />
- <item android:id="@+id/action_show_on_map"
- android:title="@string/show_on_map"
- android:showAsAction="never" />
-</menu>
diff --git a/res/menu/album.xml b/res/menu/album.xml
index 1e1f6ef..93abcca 100644
--- a/res/menu/album.xml
+++ b/res/menu/album.xml
@@ -14,6 +14,10 @@
limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/action_camera"
+ android:icon="@drawable/ic_menu_camera_holo_light"
+ android:title="@string/switch_to_camera"
+ 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..8ac8cbb 100644
--- a/res/menu/albumset.xml
+++ b/res/menu/albumset.xml
@@ -30,4 +30,8 @@
<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:visible="false"
+ 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..21f802a 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="ifRoom" />
<item android:id="@+id/action_slideshow"
android:icon="@drawable/ic_menu_slideshow_holo_light"
android:title="@string/slideshow"
@@ -57,12 +48,12 @@
<item android:id="@+id/action_crop"
android:title="@string/crop_action"
android:showAsAction="never" />
- <item android:id="@+id/action_details"
- android:title="@string/details"
- android:showAsAction="never" />
<item android:id="@+id/action_setas"
android:title="@string/set_image"
android:showAsAction="never" />
+ <item android:id="@+id/action_details"
+ android:title="@string/details"
+ android:showAsAction="never" />
<item android:id="@+id/action_show_on_map"
android:title="@string/show_on_map"
android:showAsAction="never" />
diff --git a/res/mipmap-hdpi/ic_launcher_gallery.png b/res/mipmap-hdpi/ic_launcher_gallery.png
index 34410f8..23ea998 100644
--- a/res/mipmap-hdpi/ic_launcher_gallery.png
+++ b/res/mipmap-hdpi/ic_launcher_gallery.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_gallery.png b/res/mipmap-mdpi/ic_launcher_gallery.png
index 3a701bc..e1a9949 100644
--- a/res/mipmap-mdpi/ic_launcher_gallery.png
+++ b/res/mipmap-mdpi/ic_launcher_gallery.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_gallery.png b/res/mipmap-xhdpi/ic_launcher_gallery.png
index 0c3c2ef..79544a2 100644
--- a/res/mipmap-xhdpi/ic_launcher_gallery.png
+++ b/res/mipmap-xhdpi/ic_launcher_gallery.png
Binary files differ
diff --git a/res/values-af/photoeditor_strings.xml b/res/values-af/photoeditor_strings.xml
index f9e87d8..c180110 100644
--- a/res/values-af/photoeditor_strings.xml
+++ b/res/values-af/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Fotoateljee"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Foto kan nie gelaai word nie"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Foto kan nie gestoor word nie"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto gestoor in <xliff:g id="ALBUM_NAME">%s</xliff:g> album"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Geredigeer"</string>
- <string name="save_photo" msgid="3125109368779997862">"Stoor geredigeerde foto?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Kon nie die foto laai nie"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Kon nie geredigeerde foto stoor nie"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Geredigeerde foto gestoor in <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Raak ontslae van ongestoorde veranderinge?"</string>
<string name="yes" msgid="5402582493291792293">"Ja"</string>
- <string name="no" msgid="5595408018304861875">"Nee"</string>
<string name="save" msgid="5516670392524294967">"Stoor"</string>
<string name="autofix" msgid="1663414996270538748">"Outokorrigeer"</string>
<string name="crop" msgid="7598378507763334041">"Snoei"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Trek merkers om foto te sny"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Teken op foto om te krabbel"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Trek foto om dit om te keer"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Tik om rooi oë te verwyder"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Tik op rooi oë om hulle te verwyder"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Trek foto om dit te draai"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Trek foto om dit reguit te trek"</string>
</resources>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 8feab87..e3c1e8c 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Kies album"</string>
<string name="select_group" msgid="6744208543323307114">"Kies groep"</string>
<string name="set_image" msgid="2331476809308010401">"Stel prent as"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Stel muurpapier"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Vee gekose item uit?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Vee gekose items uit?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Bevestig"</string>
<string name="cancel" msgid="3637516880917356226">"Kanselleer"</string>
<string name="share" msgid="3619042788254195341">"Deling"</string>
<string name="select_all" msgid="3403283025220282175">"Kies almal"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Draai na regs"</string>
<string name="no_such_item" msgid="5315144556325243400">"Kon nie die item vind nie."</string>
<string name="edit" msgid="1502273844748580847">"Redigeer"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Geen program is beskikbaar om die handeling te voltooi nie."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Verwerk kasversoeke"</string>
<string name="caching_label" msgid="4521059045896269095">"Kas tans..."</string>
<string name="crop_action" msgid="3427470284074377001">"Snoei"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Gesnoeide prent is nie gestoor nie."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Gesnoeide prent gestoor in <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Geen albums beskikbaar nie."</string>
<string name="empty_album" msgid="4542880442593595494">"O prente/video\'s beskikbaar."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Plasings"</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/photoeditor_strings.xml b/res/values-am/photoeditor_strings.xml
index 83971d6..7575930 100644
--- a/res/values-am/photoeditor_strings.xml
+++ b/res/values-am/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"ፎቶ ሊሰቀል አይችልም"</string>
- <string name="saving_failure" msgid="8596600582621364077">"ፎቶ ሊሰቀል አልተቻለም"</string>
- <string name="photo_saved" msgid="3494363661299484657">"ፎቶ ወደ <xliff:g id="ALBUM_NAME">%s</xliff:g> አልበም ተቀምጧል"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"አርትእ ተደርጓል"</string>
- <string name="save_photo" msgid="3125109368779997862">"ማስተካከያ የተደረገበት ፎቶ ይቀመጥ?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"ፎቶውን መጫን አልተቻለም"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"አርትዖት የተደረገበት ፎቶ ማስቀመጥ አልተቻለም"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"አርትዖት የተደረገበት ፎቶ <xliff:g id="FOLDER_NAME">%s</xliff:g> ላይ ተቀምጧል"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"ያልተቀመጡ ለውጦች ይወገዱ?"</string>
<string name="yes" msgid="5402582493291792293">"አዎ"</string>
- <string name="no" msgid="5595408018304861875">"አይ"</string>
<string name="save" msgid="5516670392524294967">"አስቀምጥ"</string>
<string name="autofix" msgid="1663414996270538748">"በራስ-ጠግን"</string>
<string name="crop" msgid="7598378507763334041">"ክፈፍ"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"ፎቶ ለመከርከም አመልካቾችን ጎትት"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"ፎቶውን doodle ለማድረግ ሳል"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"ፎቶውን በፍጥነት ለመግለጥ ጎትት"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"ቀይነጥብ ለማስወገድ ሁለቴ ንካ"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"ቀይ አይኖችን ለማስወገድ እነሱ ላይ መታ አድርግ"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"ፎቶውንለማሽከርከር ጎትት"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"ፎቶውንቀጥ ለማደረግ ጎትት"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index e93b7d9..b88187f 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"እሺ"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"ለመጀመር ፊት ንካ::"</string>
<string name="saving_image" msgid="7270334453636349407">"ምስል በማስቀመጥ ላይ..."</string>
<string name="save_error" msgid="6857408774183654970">"የተቀመቀመውን ምስል ማስቀመጥ አልተቻለም::"</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"አልበም ምረጥ"</string>
<string name="select_group" msgid="6744208543323307114">"ቡድን ምረጥ"</string>
<string name="set_image" msgid="2331476809308010401">"ምስል እንደ አዘጋጅ"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"ልጣፍ አዘጋጅ"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"የተመረጠው ንጥል ይሰረዝ?"</item>
+ <item quantity="other" msgid="5874316486520635333">"የተመረጡት ንጥሎች ይሰረዙ?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"አረጋግጥ"</string>
<string name="cancel" msgid="3637516880917356226">"ይቅር"</string>
<string name="share" msgid="3619042788254195341">"አጋራ"</string>
<string name="select_all" msgid="3403283025220282175">"ሁሉንም ምረጥ"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"መሸጎጫ ጥየቃዎች ሂደት"</string>
<string name="caching_label" msgid="4521059045896269095">"በመሸጎጥ ላይ..."</string>
<string name="crop_action" msgid="3427470284074377001">"ከርክም"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"የተከረከመው ምስል አልተቀመጠም::"</string>
+ <string name="crop_saved" msgid="1595985909779105158">"የተከረከመ ምስል <xliff:g id="FOLDER_NAME">%s</xliff:g> ላይ ተቀምጧል።"</string>
<string name="no_albums_alert" msgid="4111744447491690896">"ምንም አልበሞች አልተገኙም::"</string>
<string name="empty_album" msgid="4542880442593595494">"O ምስሎች/ ቪዲዮዎች ማግኘት ይቻላል::"</string>
<string name="picasa_posts" msgid="1497721615718760613">"ልኡክ ጽሁፎች"</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/photoeditor_strings.xml b/res/values-ar/photoeditor_strings.xml
index 301481c..7f75cc7 100644
--- a/res/values-ar/photoeditor_strings.xml
+++ b/res/values-ar/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"لا يمكن تحميل الصورة"</string>
- <string name="saving_failure" msgid="8596600582621364077">"لا يمكن حفظ الصورة"</string>
- <string name="photo_saved" msgid="3494363661299484657">"تم حفظ الصورة إلى الألبوم <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"صور معدلة"</string>
- <string name="save_photo" msgid="3125109368779997862">"هل تريد حفظ الصورة التي تم تعديلها؟"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"تعذر تحميل الصورة"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"تعذر حفظ الصورة المعدلة"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"تم حفظ الصورة المعدلة في <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"هل تريد إلغاء أي تغييرات غير محفوظة؟"</string>
<string name="yes" msgid="5402582493291792293">"نعم"</string>
- <string name="no" msgid="5595408018304861875">"لا"</string>
<string name="save" msgid="5516670392524294967">"حفظ"</string>
<string name="autofix" msgid="1663414996270538748">"إصلاح تلقائي"</string>
<string name="crop" msgid="7598378507763334041">"اقتصاص"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"اسحب العلامات لاقتصاص الصورة"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"اسحب الصورة لإنشاء رسومات مبتكرة"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"اسحب الصورة لقلبها"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"انقر لإزالة الأعين الحمراء"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"انقر على العيون الحمراء لإزالتها"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"اسحب الصورة لتدويرها"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"اسحب الصورة لتسويتها"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 922919c..fe3a211 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"موافق"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"المس وجهًا للبدء."</string>
<string name="saving_image" msgid="7270334453636349407">"جارٍ حفظ الصورة..."</string>
<string name="save_error" msgid="6857408774183654970">"تعذر حفظ الصورة التي تم اقتصاصها."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"تحديد ألبوم"</string>
<string name="select_group" msgid="6744208543323307114">"تحديد مجموعة"</string>
<string name="set_image" msgid="2331476809308010401">"تعيين الصورة كـ"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"تعيين خلفية"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"هل تريد حذف العنصر المحدد؟"</item>
+ <item quantity="other" msgid="5874316486520635333">"هل تريد حذف العناصر المحددة؟"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"تأكيد"</string>
<string name="cancel" msgid="3637516880917356226">"إلغاء"</string>
<string name="share" msgid="3619042788254195341">"مشاركة"</string>
<string name="select_all" msgid="3403283025220282175">"تحديد الكل"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"معالجة طلبات التخزين المؤقت"</string>
<string name="caching_label" msgid="4521059045896269095">"تخزين مؤقت..."</string>
<string name="crop_action" msgid="3427470284074377001">"اقتصاص"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"لم يتم حفظ الصورة التي تم اقتصاصها."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"تم حفظ الصورة التي تم اقتصاصها في <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"ليست هناك أية ألبومات متاحة."</string>
<string name="empty_album" msgid="4542880442593595494">"لا تتوفر أية صور/مقاطع فيديو."</string>
<string name="picasa_posts" msgid="1497721615718760613">"المشاركات"</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/photoeditor_strings.xml b/res/values-be/photoeditor_strings.xml
index 8984b14..6fa477b 100644
--- a/res/values-be/photoeditor_strings.xml
+++ b/res/values-be/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Фотастудыя"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Немагчыма загрузіць фатаграфію"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Немагчыма захаваць фатаграфію"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Фатаграфія захаваная ў альбом <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Рэдагаванае"</string>
- <string name="save_photo" msgid="3125109368779997862">"Захаваць адрэдагаванае фота?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Немагчыма загрузіць фота"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Немагчыма захаваць адрэдагаванае фота"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Адрэдагаванае фота захавана ў тэчцы <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Скасаваць незахаваныя змены?"</string>
<string name="yes" msgid="5402582493291792293">"Так"</string>
- <string name="no" msgid="5595408018304861875">"Не"</string>
<string name="save" msgid="5516670392524294967">"ЗАХАВАЦЬ"</string>
<string name="autofix" msgid="1663414996270538748">"Аўтафікс."</string>
<string name="crop" msgid="7598378507763334041">"Абрэзаць"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Перацягнуць маркеры, каб абрэзаць фота"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Перац. фота, каб пераўтв. яго ў святоч. лагатып"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Перацягніце фатаграфію, каб перавярнуць яе"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Націсніце, каб лiквiдаваць эфект чырвоных вачэй"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Нац. на \"чырвоныя вочы\", каб выдаліць іх"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Перацягніце фатаграфію, каб павярнуць яе"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Перацягніце фатаграфію, каб выпраміць яе"</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 718c81f..1454704 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"ОК"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Дакранiцеся да твару, каб пачаць."</string>
<string name="saving_image" msgid="7270334453636349407">"Захаванне выявы..."</string>
<string name="save_error" msgid="6857408774183654970">"Немагчыма захаваць абрэзаную выяву."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Выбраць альбом"</string>
<string name="select_group" msgid="6744208543323307114">"Выбар групы"</string>
<string name="set_image" msgid="2331476809308010401">"Усталяваць малюнак як"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Усталяваць шпалеры"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Выдаліць выбраны элемент?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Выдаліць выбраныя элементы?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Пацвердзіць"</string>
<string name="cancel" msgid="3637516880917356226">"Адмяніць"</string>
<string name="share" msgid="3619042788254195341">"Апублікаваць"</string>
<string name="select_all" msgid="3403283025220282175">"Выбраць усё"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"Запыты на кэшаванне працэсу"</string>
<string name="caching_label" msgid="4521059045896269095">"Кэшаванне..."</string>
<string name="crop_action" msgid="3427470284074377001">"Абрэзаць"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Абрэзаная выява не захавана."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Абрэзаная выява захавана ў тэчцы <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Няма альбомаў."</string>
<string name="empty_album" msgid="4542880442593595494">"Выяў/вiдэа: 0."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Паведамленнi"</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/photoeditor_strings.xml b/res/values-bg/photoeditor_strings.xml
index facb0d1..b31bf21 100644
--- a/res/values-bg/photoeditor_strings.xml
+++ b/res/values-bg/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Снимката не може да бъде заредена"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Снимката не може да бъде запазена"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Снимката е запазена в албума „<xliff:g id="ALBUM_NAME">%s</xliff:g>“"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Редактирани"</string>
- <string name="save_photo" msgid="3125109368779997862">"Да се запази ли редактираната снимка?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Снимката не можа да бъде заредена"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Редакт. снимка не можа да бъде запазена"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Редактираната снимка е запазена в/ъв „<xliff:g id="FOLDER_NAME">%s</xliff:g>“"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Да се отхвърлят ли незапазените промени?"</string>
<string name="yes" msgid="5402582493291792293">"Да"</string>
- <string name="no" msgid="5595408018304861875">"Нe"</string>
<string name="save" msgid="5516670392524294967">"ЗАПАЗВ."</string>
<string name="autofix" msgid="1663414996270538748">"Автокоригиране"</string>
<string name="crop" msgid="7598378507763334041">"Подрязване"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Плъзнете маркерите, за да подрежете снимката"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Рисувайте директно върху снимката"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Плъзнете с пръст върху снимката, за да я обърнете"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Докоснете, за да премахнете „червени очи“"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Докоснете „черв. очи“ за премахването им"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Плъзнете с пръст върху снимката, за да я завъртите"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Плъзнете с пръст върху снимката, за да я изправите"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index b4f7474..da21938 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Докоснете лице за начало."</string>
<string name="saving_image" msgid="7270334453636349407">"Снимката се запазва..."</string>
<string name="save_error" msgid="6857408774183654970">"Подрязаното изобр. не можа да се запази."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Изберете албум"</string>
<string name="select_group" msgid="6744208543323307114">"Изберете група"</string>
<string name="set_image" msgid="2331476809308010401">"Задаване на снимката като"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Задаване на тапет"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Да се изтрие ли избраното?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Да се изтрият ли избраните?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Потвърждаване"</string>
<string name="cancel" msgid="3637516880917356226">"Отказ"</string>
<string name="share" msgid="3619042788254195341">"Споделяне"</string>
<string name="select_all" msgid="3403283025220282175">"Избиране на всички"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"Заявките за кеширане се обработват"</string>
<string name="caching_label" msgid="4521059045896269095">"Кешира се..."</string>
<string name="crop_action" msgid="3427470284074377001">"Подрязване"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Подрязаното изображение не бе запазено."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Подрязаното изображение е запазено в/ъв „<xliff:g id="FOLDER_NAME">%s</xliff:g>“."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Няма налични албуми."</string>
<string name="empty_album" msgid="4542880442593595494">"0 изображения/видеоклипове са налице."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Публикации"</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/photoeditor_strings.xml b/res/values-ca/photoeditor_strings.xml
index 9c2d31d..8313965 100644
--- a/res/values-ca/photoeditor_strings.xml
+++ b/res/values-ca/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Estudi de fotografia"</string>
- <string name="loading_failure" msgid="5990741970574295950">"La foto no es pot carregar"</string>
- <string name="saving_failure" msgid="8596600582621364077">"La foto no es pot desar"</string>
- <string name="photo_saved" msgid="3494363661299484657">"S\'ha desat la foto a l\'àlbum <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editades"</string>
- <string name="save_photo" msgid="3125109368779997862">"Vols desar la foto editada?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"No s\'ha pogut carregar la foto"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"No s\'ha pogut desar la foto editada"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"S\'ha desat la foto editada a <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Vols descartar els canvis no desats?"</string>
<string name="yes" msgid="5402582493291792293">"Sí"</string>
- <string name="no" msgid="5595408018304861875">"No"</string>
<string name="save" msgid="5516670392524294967">"DESA"</string>
<string name="autofix" msgid="1663414996270538748">"Correcció automàtica"</string>
<string name="crop" msgid="7598378507763334041">"Retalla"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Arrossega els marcadors per retallar la foto"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Arrossega el cursor sobre la foto per dibuixar"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Arrossega la foto per invertir-la"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Toca per eliminar els ulls vermells"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Toca els ulls vermells per eliminar-los"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Arrossega la foto per girar-la"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Arrossega la foto per redreçar-la"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index e053e4e..e2a8359 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"Accepta"</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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Selecciona un àlbum"</string>
<string name="select_group" msgid="6744208543323307114">"Selecciona un grup"</string>
<string name="set_image" msgid="2331476809308010401">"Defineix la imatge com a"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Defineix el fons de pantalla"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Vols supr. l\'element selecc.?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Vols supr. els elements sel.?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Confirma"</string>
<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>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Gira a la dreta"</string>
<string name="no_such_item" msgid="5315144556325243400">"No s\'ha trobat l\'element."</string>
<string name="edit" msgid="1502273844748580847">"Edita"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"No hi ha cap aplicació disponible per completar l\'acció."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"S\'estan processant les sol·licituds de memòria cau"</string>
<string name="caching_label" msgid="4521059045896269095">"S\'està desant a la memòria cau..."</string>
<string name="crop_action" msgid="3427470284074377001">"Retalla"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"La imatge retallada no s\'ha desat."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"S\'ha desat la imatge retallada a <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"No hi ha àlbums disponibles."</string>
<string name="empty_album" msgid="4542880442593595494">"O imatges/vídeos disponibles."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Publicacions"</string>
@@ -155,11 +159,16 @@
<string name="widget_type" msgid="1364653978966343448">"Selecció d\'imatges"</string>
<string name="slideshow_dream_name" msgid="6915963319933437083">"Pres. diapositives"</string>
<string name="albums" msgid="7320787705180057947">"Àlbums"</string>
- <string name="times" msgid="2023033894889499219">"Vegades"</string>
+ <string name="times" msgid="2023033894889499219">"Dates"</string>
<string name="locations" msgid="6649297994083130305">"Ubicacions"</string>
<string name="people" msgid="4114003823747292747">"Persones"</string>
<string name="tags" msgid="5539648765482935955">"Etiquetes"</string>
<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/photoeditor_strings.xml b/res/values-cs/photoeditor_strings.xml
index 48ef932..187c236 100644
--- a/res/values-cs/photoeditor_strings.xml
+++ b/res/values-cs/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Fotku nelze nahrát."</string>
- <string name="saving_failure" msgid="8596600582621364077">"Fotku nelze uložit."</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotka uložena do alba <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Upraveno"</string>
- <string name="save_photo" msgid="3125109368779997862">"Chcete upravenou fotografii uložit?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Fotografii nelze načíst"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Upravená fotografie nelze uložit"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Upravená fotografie uložena do složky <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Zahodit neuložené změny?"</string>
<string name="yes" msgid="5402582493291792293">"Ano"</string>
- <string name="no" msgid="5595408018304861875">"Ne"</string>
<string name="save" msgid="5516670392524294967">"ULOŽIT"</string>
<string name="autofix" msgid="1663414996270538748">"Automat. oprava"</string>
<string name="crop" msgid="7598378507763334041">"Oříznout"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Táhnutím značek fotografii oříznete"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Na fotografii můžete kreslit prstem"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Táhnutím prstem fotografii převrátíte"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Klepnutím odstraníte červené oči"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Červené oči odstraníte klepnutím na oči"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Táhnutím prstem fotografii otočíte"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Táhnutím prstem fotografii narovnáte"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 780295e..9cb8835 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -29,23 +29,30 @@
<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="ok" msgid="5296833083983263293">"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>
<string name="crop_label" msgid="521114301871349328">"Oříznout fotografii"</string>
- <string name="select_image" msgid="7841406150484742140">"Vyberte fotografii"</string>
- <string name="select_video" msgid="4859510992798615076">"Vyberte video"</string>
- <string name="select_item" msgid="2816923896202086390">"Vyberte položku"</string>
- <string name="select_album" msgid="1557063764849434077">"Vyberte album"</string>
- <string name="select_group" msgid="6744208543323307114">"Vyberte skupinu"</string>
- <string name="set_image" msgid="2331476809308010401">"Fotografie bude použita jako"</string>
+ <string name="select_image" msgid="7841406150484742140">"Vybrat fotografii"</string>
+ <string name="select_video" msgid="4859510992798615076">"Vybrat video"</string>
+ <string name="select_item" msgid="2816923896202086390">"Vybrat položku"</string>
+ <string name="select_album" msgid="1557063764849434077">"Vybrat album"</string>
+ <string name="select_group" msgid="6744208543323307114">"Vybrat skupinu"</string>
+ <string name="set_image" msgid="2331476809308010401">"Nastavit fotografii jako"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Nastavit tapetu"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Smazat vybranou položku?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Smazat vybrané položky?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Potvrdit"</string>
<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>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Otočit doprava"</string>
<string name="no_such_item" msgid="5315144556325243400">"Položku nelze najít."</string>
<string name="edit" msgid="1502273844748580847">"Upravit"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Není k dispozici žádná aplikace k dokončení akce."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Zpracování požadavků na uložení do mezipaměti"</string>
<string name="caching_label" msgid="4521059045896269095">"Ukládání do mezipaměti..."</string>
<string name="crop_action" msgid="3427470284074377001">"Oříznout"</string>
@@ -84,26 +90,24 @@
<string name="group_by_location" msgid="316641628989023253">"Podle místa"</string>
<string name="group_by_time" msgid="9046168567717963573">"Podle času"</string>
<string name="group_by_tags" msgid="3568731317210676160">"Podle tagů"</string>
- <string name="group_by_faces" msgid="1566351636227274906">"Podle osob"</string>
+ <string name="group_by_faces" msgid="1566351636227274906">"Podle lidí"</string>
<string name="group_by_album" msgid="1532818636053818958">"Podle alba"</string>
<string name="group_by_size" msgid="153766174950394155">"Podle velikosti"</string>
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Ořezaný snímek není uložen."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Ořezaný snímek byl uložen do složky <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Žádné album není k dispozici."</string>
<string name="empty_album" msgid="4542880442593595494">"Žádné obrázky ani videa nejsou k dispozici."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Příspěvky"</string>
<string name="make_available_offline" msgid="5157950985488297112">"Zpřístupnit offline"</string>
- <string name="sync_picasa_albums" msgid="8522572542111169872">"Obnovit"</string>
+ <string name="sync_picasa_albums" msgid="8522572542111169872">"Aktualizovat"</string>
<string name="done" msgid="217672440064436595">"Hotovo"</string>
<string name="sequence_in_set" msgid="7235465319919457488">"%1$d z %2$d položek:"</string>
<string name="title" msgid="7622928349908052569">"Název"</string>
@@ -121,9 +125,9 @@
<string name="model" msgid="8240207064064337366">"Model"</string>
<string name="flash" msgid="2816779031261147723">"Blesk"</string>
<string name="aperture" msgid="5920657630303915195">"Clona"</string>
- <string name="focal_length" msgid="1291383769749877010">"Ohnisk. vzdál."</string>
+ <string name="focal_length" msgid="1291383769749877010">"Ohnisk. vzdálenost"</string>
<string name="white_balance" msgid="1582509289994216078">"Vyvážení bílé"</string>
- <string name="exposure_time" msgid="3990163680281058826">"Doba expozice"</string>
+ <string name="exposure_time" msgid="3990163680281058826">"Expoziční čas"</string>
<string name="iso" msgid="5028296664327335940">"ISO"</string>
<string name="unit_mm" msgid="1125768433254329136">"mm"</string>
<string name="manual" msgid="6608905477477607865">"Ručně"</string>
@@ -131,7 +135,7 @@
<string name="flash_on" msgid="7891556231891837284">"S bleskem"</string>
<string name="flash_off" msgid="1445443413822680010">"Bez blesku"</string>
<plurals name="make_albums_available_offline">
- <item quantity="one" msgid="2171596356101611086">"Zpřístupnění alba offline."</item>
+ <item quantity="one" msgid="2171596356101611086">"Zpřístupňování alba offline"</item>
<item quantity="other" msgid="4948604338155959389">"Zpřístupnění alb offline"</item>
</plurals>
<string name="try_to_set_local_album_available_offline" msgid="2184754031896160755">"Tato položka je uložena v místním úložišti a je k dispozici offline."</string>
@@ -159,7 +163,12 @@
<string name="locations" msgid="6649297994083130305">"Lokality"</string>
<string name="people" msgid="4114003823747292747">"Lidé"</string>
<string name="tags" msgid="5539648765482935955">"Tagy"</string>
- <string name="group_by" msgid="4308299657902209357">"Seskupit podle"</string>
+ <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ímky obrazovky"</string>
+ <string name="help" msgid="7368960711153618354">"Nápověda"</string>
</resources>
diff --git a/res/values-da/photoeditor_strings.xml b/res/values-da/photoeditor_strings.xml
index 447fabf..0d96d43 100644
--- a/res/values-da/photoeditor_strings.xml
+++ b/res/values-da/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Fotostudie"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Billedet kan ikke indlæses"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Billedet kan ikke gemmes"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Billedet gemmes i albummet <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Redigeret"</string>
- <string name="save_photo" msgid="3125109368779997862">"Vil du gemme det redigerede billede?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Fotoet kunne ikke indlæses"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Det redigerede foto kunne ikke gemmes"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Det redigerede foto er gemt i <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Kassér ændringer, som ikke er gemt?"</string>
<string name="yes" msgid="5402582493291792293">"Ja"</string>
- <string name="no" msgid="5595408018304861875">"Nej"</string>
<string name="save" msgid="5516670392524294967">"GEM"</string>
<string name="autofix" msgid="1663414996270538748">"Autojustering"</string>
<string name="crop" msgid="7598378507763334041">"Beskær"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Træk markører for at beskære billedet"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Tegn på billedet for at lave kruseduller"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Træk billedet for at vende det"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Tryk for at fjerne røde øjne"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Tryk på røde øjne for at fjerne dem"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Træk billedet for at rotere det"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Træk i billedet for at rette det op"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 0c68b03..2fcb2fc 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -29,11 +29,13 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Vælg album"</string>
<string name="select_group" msgid="6744208543323307114">"Vælg gruppe"</string>
<string name="set_image" msgid="2331476809308010401">"Angiv billedet som"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Angiv baggrund"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Vil du slette det valgte element?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Vil du slette valgte elementer?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Bekræft"</string>
<string name="cancel" msgid="3637516880917356226">"Annuller"</string>
<string name="share" msgid="3619042788254195341">"Del"</string>
<string name="select_all" msgid="3403283025220282175">"Markér alle"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Roter til højre"</string>
<string name="no_such_item" msgid="5315144556325243400">"Elementet blev ikke fundet."</string>
<string name="edit" msgid="1502273844748580847">"Rediger"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Der er ingen apps, der kan gennemføre handlingen."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Håndterer anmodninger om cachelagring"</string>
<string name="caching_label" msgid="4521059045896269095">"Cachelagrer..."</string>
<string name="crop_action" msgid="3427470284074377001">"Beskær"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Det beskårne billede blev ikke gemt."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Det beskårne billede er gemt i <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Ingen tilgængelige albummer."</string>
<string name="empty_album" msgid="4542880442593595494">"O tilgængelige billeder/videoer."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Indlæg"</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/photoeditor_strings.xml b/res/values-de/photoeditor_strings.xml
index b57502c..a00fa53 100644
--- a/res/values-de/photoeditor_strings.xml
+++ b/res/values-de/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Foto kann nicht geladen werden."</string>
- <string name="saving_failure" msgid="8596600582621364077">"Foto kann nicht gespeichert werden."</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto in Album <xliff:g id="ALBUM_NAME">%s</xliff:g> gespeichert"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Bearbeitet"</string>
- <string name="save_photo" msgid="3125109368779997862">"Bearbeitetes Foto speichern?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Foto konnte nicht geladen werden."</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Speichern des bearbeiteten Fotos nicht möglich"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Das bearbeitete Foto wurde in <xliff:g id="FOLDER_NAME">%s</xliff:g> gespeichert."</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Nicht gespeicherte Änderungen verwerfen?"</string>
<string name="yes" msgid="5402582493291792293">"Ja"</string>
- <string name="no" msgid="5595408018304861875">"Nein"</string>
<string name="save" msgid="5516670392524294967">"Speichern"</string>
<string name="autofix" msgid="1663414996270538748">"Auto-Fix"</string>
<string name="crop" msgid="7598378507763334041">"Zuschneiden"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Zum Zuschneiden des Fotos Markierungen ziehen"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Zum Malen auf dem Foto malen"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Foto ziehen, um es umzudrehen"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Zum Entfernen roter Augen tippen"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Zur Korrektur auf rote Augen tippen"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Foto ziehen, um es zu drehen"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Foto ziehen, um es zu strecken"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 3cfe33b..cce0dd7 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Album auswählen"</string>
<string name="select_group" msgid="6744208543323307114">"Gruppe auswählen"</string>
<string name="set_image" msgid="2331476809308010401">"Bild festlegen als"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Hintergrund wählen"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Ausgewähltes Element löschen?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Ausgewählte Elemente löschen?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Bestätigen"</string>
<string name="cancel" msgid="3637516880917356226">"Abbrechen"</string>
<string name="share" msgid="3619042788254195341">"Teilen"</string>
<string name="select_all" msgid="3403283025220282175">"Alle auswählen"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Nach rechts drehen"</string>
<string name="no_such_item" msgid="5315144556325243400">"Bild wurde nicht gefunden."</string>
<string name="edit" msgid="1502273844748580847">"Bearbeiten"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Für diese Aktion ist keine App verfügbar."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Caching-Anfragen werden verarbeitet."</string>
<string name="caching_label" msgid="4521059045896269095">"Caching läuft..."</string>
<string name="crop_action" msgid="3427470284074377001">"Zuschneiden"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Zugeschnittenes Bild wurde nicht gespeichert."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Zugeschnittenes Bild gespeichert in <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Keine Alben verfügbar"</string>
<string name="empty_album" msgid="4542880442593595494">"Keine Bilder oder Videos vorhanden"</string>
<string name="picasa_posts" msgid="1497721615718760613">"Posts"</string>
@@ -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/photoeditor_strings.xml b/res/values-el/photoeditor_strings.xml
index 88ece26..8ec8c5a 100644
--- a/res/values-el/photoeditor_strings.xml
+++ b/res/values-el/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Αδύν.φόρτ.φωτογραφ."</string>
- <string name="saving_failure" msgid="8596600582621364077">"Αδύν.αποθ.φωτογρ."</string>
- <string name="photo_saved" msgid="3494363661299484657">"Η φωτ. αποθηκεύτηκε στο λεύκωμα <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Επεξεργασμ."</string>
- <string name="save_photo" msgid="3125109368779997862">"Αποθήκευση επεξεργασμένης φωτογραφίας;"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Αδύνατη η φόρτωση της φωτογραφίας"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Αδύνατη η αποθήκευση της επεξ. φωτογρ."</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Η επεξ. φωτογραφία αποθηκ. στον φάκελο <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Απόρριψη μη αποθηκευμένων αλλαγών;"</string>
<string name="yes" msgid="5402582493291792293">"Ναι"</string>
- <string name="no" msgid="5595408018304861875">"Όχι"</string>
<string name="save" msgid="5516670392524294967">"ΑΠΟΘΗΚΕΥΣΗ"</string>
<string name="autofix" msgid="1663414996270538748">"Αυτ.επιδ."</string>
<string name="crop" msgid="7598378507763334041">"Περικοπή"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Σύρετε τους δείκτες για την περικοπή της φωτογραφίας"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Σχεδιάστε στη φωτογραφία για να δημιουργήσετε ένα σκετσάκι"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Σύρετε τη φωτογραφία για να την αναστρέψετε"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Πατήστε για να αφαιρέσετε τα κόκκινα μάτια"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Πατήστε στα κόκκινα μάτια για διόρθωση"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Σύρετε τη φωτογραφία για να την περιστρέψετε"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Σύρετε τη φωτογραφία για να την ευθυγραμμίσετε"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 4d1e2e0..7435abe 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"ΟΚ"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Αγγίξτε κάποιο πρόσωπο για να ξεκινήσετε."</string>
<string name="saving_image" msgid="7270334453636349407">"Αποθήκευση εικόνας..."</string>
<string name="save_error" msgid="6857408774183654970">"Αδυναμία αποθήκευσης αποκομμένης εικόνας"</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Επιλογή λευκώματος"</string>
<string name="select_group" msgid="6744208543323307114">"Επιλογή ομάδας"</string>
<string name="set_image" msgid="2331476809308010401">"Ορισμός εικόνας ως"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Ορισμός ταπετσαρίας"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Διαγραφή επιλεγμέν. στοιχείου;"</item>
+ <item quantity="other" msgid="5874316486520635333">"Διαγραφή επιλεγμέν. στοιχείων;"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Επιβεβαίωση"</string>
<string name="cancel" msgid="3637516880917356226">"Ακύρωση"</string>
<string name="share" msgid="3619042788254195341">"Κοινή χρήση"</string>
<string name="select_all" msgid="3403283025220282175">"Επιλογή όλων"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"Επεξεργασία αιτημάτων προσωρινής αποθήκευσης"</string>
<string name="caching_label" msgid="4521059045896269095">"Προσωρ. αποθ..."</string>
<string name="crop_action" msgid="3427470284074377001">"Περικοπή"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Δεν αποθηκεύτηκε η αποκομένη εικόνα."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Η αποκομμένη εικόνα αποθηκεύτηκε στον φάκελο <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Δεν υπάρχουν διαθέσιμα λευκώματα."</string>
<string name="empty_album" msgid="4542880442593595494">"Δεν υπάρχουν διαθέσιμες εικόνες/βίντεο."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Αναρτήσεις"</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/photoeditor_strings.xml b/res/values-en-rGB/photoeditor_strings.xml
index 04660ae..6dde2f8 100644
--- a/res/values-en-rGB/photoeditor_strings.xml
+++ b/res/values-en-rGB/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Photo cannot be loaded."</string>
- <string name="saving_failure" msgid="8596600582621364077">"Photo cannot be saved"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Photo saved to <xliff:g id="ALBUM_NAME">%s</xliff:g> album"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Edited"</string>
- <string name="save_photo" msgid="3125109368779997862">"Save edited photo?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Couldn\'t load the photo"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Couldn\'t save edited photo"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Edited photo saved to <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Discard unsaved changes?"</string>
<string name="yes" msgid="5402582493291792293">"Yes"</string>
- <string name="no" msgid="5595408018304861875">"No"</string>
<string name="save" msgid="5516670392524294967">"SAVE"</string>
<string name="autofix" msgid="1663414996270538748">"Auto-fix"</string>
<string name="crop" msgid="7598378507763334041">"Crop"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Drag markers to crop photo"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Draw on photo to doodle"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Drag photo to flip it"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Tap to remove red eyes"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Tap on red eyes to remove them"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Drag photo to rotate it"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Drag photo to straighten it"</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index ca72583..c7bbcea 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Select album"</string>
<string name="select_group" msgid="6744208543323307114">"Select group"</string>
<string name="set_image" msgid="2331476809308010401">"Set picture as"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Set wallpaper"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Delete selected item?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Delete selected items?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Confirm"</string>
<string name="cancel" msgid="3637516880917356226">"Cancel"</string>
<string name="share" msgid="3619042788254195341">"Share"</string>
<string name="select_all" msgid="3403283025220282175">"Select all"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Rotate right"</string>
<string name="no_such_item" msgid="5315144556325243400">"Couldn\'t find item."</string>
<string name="edit" msgid="1502273844748580847">"Edit"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"No app is available to complete the action."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Process Caching Requests"</string>
<string name="caching_label" msgid="4521059045896269095">"Caching..."</string>
<string name="crop_action" msgid="3427470284074377001">"Crop"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Cropped image wasn\'t saved."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Cropped image saved to <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"No albums available."</string>
<string name="empty_album" msgid="4542880442593595494">"O images/videos available."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Posts"</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">"Help"</string>
</resources>
diff --git a/res/values-es-rUS/photoeditor_strings.xml b/res/values-es-rUS/photoeditor_strings.xml
index d6bd2b8..f0a38ed 100644
--- a/res/values-es-rUS/photoeditor_strings.xml
+++ b/res/values-es-rUS/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Estudio de fotografía"</string>
- <string name="loading_failure" msgid="5990741970574295950">"No se puede cargar la foto."</string>
- <string name="saving_failure" msgid="8596600582621364077">"No se puede guardar la foto."</string>
- <string name="photo_saved" msgid="3494363661299484657">"La foto se guardó en el álbum <xliff:g id="ALBUM_NAME">%s</xliff:g>."</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editadas"</string>
- <string name="save_photo" msgid="3125109368779997862">"¿Guardar foto editada?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"No se puede cargar la foto"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"No se pudo guardar la foto editada"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"La foto editada se guardó en <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"¿Descartar cambios sin guardar?"</string>
<string name="yes" msgid="5402582493291792293">"Sí"</string>
- <string name="no" msgid="5595408018304861875">"No"</string>
<string name="save" msgid="5516670392524294967">"GUARDAR"</string>
<string name="autofix" msgid="1663414996270538748">"Autocorrección"</string>
<string name="crop" msgid="7598378507763334041">"Recortar"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Arrastrar los marcadores para recortar la foto"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Dibuja en la foto para hacer garabatos"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Arrastra la foto para darla vuelta"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Toca para eliminar los ojos rojos"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Elimina los ojos rojos tocando el icono"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Arrastra la foto para hacerla girar"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Arrastra la foto para enderezarla"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 200e1a6..cf800b7 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Seleccionar álbum"</string>
<string name="select_group" msgid="6744208543323307114">"Seleccionar un grupo"</string>
<string name="set_image" msgid="2331476809308010401">"Establecer imagen como"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Establecer como fondo de pantalla"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"¿Eliminar elemento?"</item>
+ <item quantity="other" msgid="5874316486520635333">"¿Eliminar elementos?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Confirmar"</string>
<string name="cancel" msgid="3637516880917356226">"Cancelar"</string>
<string name="share" msgid="3619042788254195341">"Compartir"</string>
<string name="select_all" msgid="3403283025220282175">"Seleccionar todo"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Rotar hacia la derecha"</string>
<string name="no_such_item" msgid="5315144556325243400">"No se pudo encontrar el elemento."</string>
<string name="edit" msgid="1502273844748580847">"Editar"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"No hay aplicaciones para completar la acción."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Procesando solicitudes de almacenamiento en caché"</string>
<string name="caching_label" msgid="4521059045896269095">"Alm en caché..."</string>
<string name="crop_action" msgid="3427470284074377001">"Recortar"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"No se ha guardado la imagen recortada."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"La imagen recortada se guardó en <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"No hay álbumes disponibles."</string>
<string name="empty_album" msgid="4542880442593595494">"No hay imágenes/videos disponibles."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Publicaciones"</string>
@@ -162,4 +166,9 @@
<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/photoeditor_strings.xml b/res/values-es/photoeditor_strings.xml
index e86d69f..4b4f7eb 100644
--- a/res/values-es/photoeditor_strings.xml
+++ b/res/values-es/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"No se puede cargar la foto."</string>
- <string name="saving_failure" msgid="8596600582621364077">"No se puede guardar la foto."</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto guardada en el álbum <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editadas"</string>
- <string name="save_photo" msgid="3125109368779997862">"¿Quieres guardar la foto editada?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"No se puede cargar la foto."</string>
+ <string name="saving_failure" msgid="8229491575433743974">"No se ha podido guardar la foto editada."</string>
+ <string name="photo_saved" msgid="6163006724627682202">"La foto editada se ha guardado en <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"¿Descartar los cambios no guardados?"</string>
<string name="yes" msgid="5402582493291792293">"Sí"</string>
- <string name="no" msgid="5595408018304861875">"No"</string>
<string name="save" msgid="5516670392524294967">"GUARDAR"</string>
<string name="autofix" msgid="1663414996270538748">"Ajuste automático"</string>
<string name="crop" msgid="7598378507763334041">"Recortar"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Arrastra los marcadores para recortar la foto"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Dibuja en la foto para hacer garabatos"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Arrastra la foto para darle la vuelta"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Toca para eliminar los ojos rojos"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Elimina los ojos rojos tocando el icono"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Arrastra la foto para girarla"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Arrastra la foto para enderezarla"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index b0b2178..eb473ab 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Seleccionar álbum"</string>
<string name="select_group" msgid="6744208543323307114">"Seleccionar grupo"</string>
<string name="set_image" msgid="2331476809308010401">"Establecer imagen como"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Establecer fondo de pantalla"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"¿Eliminar elemento seleccionado?"</item>
+ <item quantity="other" msgid="5874316486520635333">"¿Eliminar elementos seleccionados?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Confirmar"</string>
<string name="cancel" msgid="3637516880917356226">"Cancelar"</string>
<string name="share" msgid="3619042788254195341">"Compartir"</string>
<string name="select_all" msgid="3403283025220282175">"Seleccionar todo"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Girar a la derecha"</string>
<string name="no_such_item" msgid="5315144556325243400">"No se ha podido encontrar el elemento."</string>
<string name="edit" msgid="1502273844748580847">"Editar"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"No hay aplicaciones para completar la acción."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Procesando solicitudes de almacenamiento en caché"</string>
<string name="caching_label" msgid="4521059045896269095">"Almacenando en caché..."</string>
<string name="crop_action" msgid="3427470284074377001">"Recortar"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"No se ha guardado la imagen recortada."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"La imagen recortada se ha guardado en <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"No hay álbumes disponibles."</string>
<string name="empty_album" msgid="4542880442593595494">"No hay imágenes ni vídeos disponibles."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Publicaciones"</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/photoeditor_strings.xml b/res/values-et/photoeditor_strings.xml
index eea16a2..c924486 100644
--- a/res/values-et/photoeditor_strings.xml
+++ b/res/values-et/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Fotostuudio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Fotot ei saa laadida"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Fotot ei saa salvestada"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto salvestati albumisse <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Muudetud"</string>
- <string name="save_photo" msgid="3125109368779997862">"Kas salvestada muudetud foto?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Fotot ei õnnestunud laadida"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Muudetud fotot ei õnnestunud salvestada"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Muudetud foto salvestati kausta <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Kas loobute salvestamata muudatustest?"</string>
<string name="yes" msgid="5402582493291792293">"Jah"</string>
- <string name="no" msgid="5595408018304861875">"Ei"</string>
<string name="save" msgid="5516670392524294967">"SALVESTA"</string>
<string name="autofix" msgid="1663414996270538748">"Automaatparandus"</string>
<string name="crop" msgid="7598378507763334041">"Kärpimine"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Foto kärpimiseks lohistage markereid"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Kritseldamiseks joonistage fotol"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Ümberpööramiseks lohistage fotot"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Puudutage punasilmsuse eemaldamiseks"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Punasilmsuse eemaldam. puudutage silma"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Pööramiseks lohistage fotot"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Rihtimiseks lohistage fotot"</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 1072aa7..6ae9bba 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Albumi valimine"</string>
<string name="select_group" msgid="6744208543323307114">"Rühma valimine"</string>
<string name="set_image" msgid="2331476809308010401">"Seadke pilt kui"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Taustapildiks määr."</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Kas kustutada valitud üksus?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Kas kustutada valitud üksused?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Kinnitamine"</string>
<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>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Pööra paremale"</string>
<string name="no_such_item" msgid="5315144556325243400">"Üksust ei leitud."</string>
<string name="edit" msgid="1502273844748580847">"Muuda"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Toimingu lõpetamiseks pole sobivat rakendust saadaval."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Vahemällu lisamise taotluste töötlemine"</string>
<string name="caching_label" msgid="4521059045896269095">"Vahemällu ..."</string>
<string name="crop_action" msgid="3427470284074377001">"Kärbi"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Kärbitud kujutist ei salvestatud."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Kärbitud kujutis salvestati kausta <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Ühtegi albumit pole saadaval."</string>
<string name="empty_album" msgid="4542880442593595494">"O kujutist/videot on saadaval."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Postitused"</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/photoeditor_strings.xml b/res/values-fa/photoeditor_strings.xml
index 16b1546..177c646 100644
--- a/res/values-fa/photoeditor_strings.xml
+++ b/res/values-fa/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"عکس بارگیری نمیشود"</string>
- <string name="saving_failure" msgid="8596600582621364077">"عکس ذخیره نمیشود"</string>
- <string name="photo_saved" msgid="3494363661299484657">"عکس در آلبوم <xliff:g id="ALBUM_NAME">%s</xliff:g> ذخیره شد"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"ویرایش شده"</string>
- <string name="save_photo" msgid="3125109368779997862">"عکس ویرایش شده ذخیره شود؟"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"بارگیری عکس انجام نشد"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"عکس ویرایش شده ذخیره نشد"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"عکس ویرایش شده در <xliff:g id="FOLDER_NAME">%s</xliff:g> ذخیره شد"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"از تغییرات ذخیره نشده صرفنظر شود؟"</string>
<string name="yes" msgid="5402582493291792293">"بله"</string>
- <string name="no" msgid="5595408018304861875">"خیر"</string>
<string name="save" msgid="5516670392524294967">"ذخیره"</string>
<string name="autofix" msgid="1663414996270538748">"تصحیح خودکار"</string>
<string name="crop" msgid="7598378507763334041">"برش"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"برای بریدن عکس نشانگرها را بکشید"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"برای doodle کردن روی عکس رسم کنید"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"برای وارونه کردن عکس آن را بکشید"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"برای حذف قرمزی چشم ضربه بزنید"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"روی قرمزی چشم برای رفع آن ضربه بزنید"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"برای چرخاندن عکس آن را بکشید"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"برای صاف کردن عکس آن را بکشید"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 9017b40..c51d8c4 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"تأیید"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"برای شروع یک چهره را لمس کنید."</string>
<string name="saving_image" msgid="7270334453636349407">"در حال ذخیره عکس..."</string>
<string name="save_error" msgid="6857408774183654970">"ذخیره تصویر برشخورده امکانپذیر نیست."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"آلبوم را انتخاب کنید"</string>
<string name="select_group" msgid="6744208543323307114">"انتخاب گروه"</string>
<string name="set_image" msgid="2331476809308010401">"تنظیم تصویر بعنوان"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"تنظیم تصویر زمینه"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"مورد انتخابی حذف شود؟"</item>
+ <item quantity="other" msgid="5874316486520635333">"موارد انتخابی حذف شوند؟"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"تأیید"</string>
<string name="cancel" msgid="3637516880917356226">"لغو"</string>
<string name="share" msgid="3619042788254195341">"اشتراک گذاری"</string>
<string name="select_all" msgid="3403283025220282175">"انتخاب همه"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"پردازش درخواستهای ذخیره در حافظه پنهان"</string>
<string name="caching_label" msgid="4521059045896269095">"در حال ذخیره در حافظه پنهان..."</string>
<string name="crop_action" msgid="3427470284074377001">"برش"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"تصویر برشخورده ذخیره نشد."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"تصویر بریده شده در <xliff:g id="FOLDER_NAME">%s</xliff:g> ذخیره شد."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"آلبومی موجود نیست."</string>
<string name="empty_album" msgid="4542880442593595494">"O تصویر/ویدیو موجود است."</string>
<string name="picasa_posts" msgid="1497721615718760613">"پستها"</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/photoeditor_strings.xml b/res/values-fi/photoeditor_strings.xml
index 2bb6485..ce60dc7 100644
--- a/res/values-fi/photoeditor_strings.xml
+++ b/res/values-fi/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Valokuvastudio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Kuvaa ei voi ladata."</string>
- <string name="saving_failure" msgid="8596600582621364077">"Kuvaa ei voi tallentaa."</string>
- <string name="photo_saved" msgid="3494363661299484657">"Kuva tallennettu albumiin <xliff:g id="ALBUM_NAME">%s</xliff:g>."</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Muokattu"</string>
- <string name="save_photo" msgid="3125109368779997862">"Tallennetaanko muokattu valokuva?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Kuvaa ei voi ladata"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Muokatun kuvan tallentaminen epäonnistui"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Muokattu kuva tallennettu kansioon <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Hylätäänkö tallentamattomat muutokset?"</string>
<string name="yes" msgid="5402582493291792293">"Kyllä"</string>
- <string name="no" msgid="5595408018304861875">"Ei"</string>
<string name="save" msgid="5516670392524294967">"Tallenna"</string>
<string name="autofix" msgid="1663414996270538748">"Autom. korjaus"</string>
<string name="crop" msgid="7598378507763334041">"Rajaa"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Leikkaa kuvaa vetämällä merkkejä"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Piirrä valokuvaan siirtämällä osoitin sen päälle"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Käännä kuva vetämällä"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Poista punaiset silmät napauttamalla"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Poista punasilmäisyys napautt. silmiä"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Käännä valokuvaa vetämällä"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Suorista valokuva vetämällä"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 2527a70..c1e7969 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Valitse albumi"</string>
<string name="select_group" msgid="6744208543323307114">"Valitse ryhmä"</string>
<string name="set_image" msgid="2331476809308010401">"Aseta kuva"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Aseta taustakuva"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Poistetaanko valittu kohde?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Poistetaanko valitut kohteet?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Vahvista"</string>
<string name="cancel" msgid="3637516880917356226">"Peruuta"</string>
<string name="share" msgid="3619042788254195341">"Jaa"</string>
<string name="select_all" msgid="3403283025220282175">"Valitse kaikki"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Kierrä myötäpäivään"</string>
<string name="no_such_item" msgid="5315144556325243400">"Kohdetta ei löytynyt."</string>
<string name="edit" msgid="1502273844748580847">"Muokkaa"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Toiminnon suorittam. vaad. sov. ei ole saatavilla."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Käsitellään välimuistipyyntöjä"</string>
<string name="caching_label" msgid="4521059045896269095">"Vie välimuist."</string>
<string name="crop_action" msgid="3427470284074377001">"Rajaa"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Rajattua kuvaa ei tallennettu."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Rajattu kuva tallennettu kansioon <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Ei albumeja käytettävissä."</string>
<string name="empty_album" msgid="4542880442593595494">"0 kuvaa/videota saatavilla."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Julkaisut"</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/photoeditor_strings.xml b/res/values-fr/photoeditor_strings.xml
index a4d59ea..193e3f0 100644
--- a/res/values-fr/photoeditor_strings.xml
+++ b/res/values-fr/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Impossible de charger la photo."</string>
- <string name="saving_failure" msgid="8596600582621364077">"Impossible d\'enregistrer la photo."</string>
- <string name="photo_saved" msgid="3494363661299484657">"Photo enregistrée dans l\'album \"<xliff:g id="ALBUM_NAME">%s</xliff:g>\"."</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Retouchées"</string>
- <string name="save_photo" msgid="3125109368779997862">"Enregistrer photo retouchée ?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Impossible de charger la photo."</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Imposs. enregistrer photo retouchée."</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Photo retouchée enregistrée dans <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Annuler modifications non enregistrées ?"</string>
<string name="yes" msgid="5402582493291792293">"Oui"</string>
- <string name="no" msgid="5595408018304861875">"Non"</string>
<string name="save" msgid="5516670392524294967">"ENREG."</string>
<string name="autofix" msgid="1663414996270538748">"Correction auto"</string>
<string name="crop" msgid="7598378507763334041">"Rogner"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Faire glisser les marqueurs pour rogner la photo"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Dessiner sur la photo pour gribouiller"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Faire glisser la photo pour la retourner"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Appuyer pour supprimer les yeux rouges"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Appuyer sur yeux rouges pour les effacer"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Faire glisser la photo pour la faire pivoter"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Faire glisser la photo pour la redresser"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 4873b9f..5a5d4ca 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Sélectionner album"</string>
<string name="select_group" msgid="6744208543323307114">"Sélectionnez groupe"</string>
<string name="set_image" msgid="2331476809308010401">"Utiliser l\'image comme"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Définir fond d\'écran"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Supprimer élément sélectionné ?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Suppr. éléments sélectionnés ?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Confirmer"</string>
<string name="cancel" msgid="3637516880917356226">"Annuler"</string>
<string name="share" msgid="3619042788254195341">"Partager"</string>
<string name="select_all" msgid="3403283025220282175">"Tout sélectionner"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Faire pivoter à droite"</string>
<string name="no_such_item" msgid="5315144556325243400">"Impossible de trouver l\'élément."</string>
<string name="edit" msgid="1502273844748580847">"Retoucher"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Aucune appli disponible pour effectuer l\'action."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Demandes de mise en cache en cours de traitement"</string>
<string name="caching_label" msgid="4521059045896269095">"Mise en cache…"</string>
<string name="crop_action" msgid="3427470284074377001">"Rogner"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"L\'image rognée n\'a pas été enregistrée."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Image rognée enregistrée dans <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Aucun album disponible."</string>
<string name="empty_album" msgid="4542880442593595494">"Aucune image/vidéo disponible."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Posts"</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/photoeditor_strings.xml b/res/values-hi/photoeditor_strings.xml
index 1f6ea06..f5286ec 100644
--- a/res/values-hi/photoeditor_strings.xml
+++ b/res/values-hi/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"फ़ोटो लोड नहीं हो सकता"</string>
- <string name="saving_failure" msgid="8596600582621364077">"फ़ोटो सहेजा नहीं जा सकता"</string>
- <string name="photo_saved" msgid="3494363661299484657">"फ़ोटो <xliff:g id="ALBUM_NAME">%s</xliff:g> एल्बम में सहेजा गया"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"संपादित"</string>
- <string name="save_photo" msgid="3125109368779997862">"संपादित फ़ोटो सहेजें?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"फ़ोटो लोड नहीं किया जा सका"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"संपादित फ़ोटो को सहेजा नहीं जा सका"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"संपादित फ़ोटो को <xliff:g id="FOLDER_NAME">%s</xliff:g> में सहेजा गया"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"न सहेजे गए परिवर्तनों को छोड़ें?"</string>
<string name="yes" msgid="5402582493291792293">"हां"</string>
- <string name="no" msgid="5595408018304861875">"नहीं"</string>
<string name="save" msgid="5516670392524294967">"सहेजें"</string>
<string name="autofix" msgid="1663414996270538748">"स्वत: समायोजन"</string>
<string name="crop" msgid="7598378507763334041">"काट-छांट करें"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"फ़ोटो की काट-छांट करने के लिए मार्कर को खींचें"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"doodle के लिए फ़ोटो पर आरेखित करें"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"फ़ोटो को फ़्लिप करने के लिए उसे खींचें"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"रेड आई निकालने के लिए टैप करें"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"रेड आई निकालने के लिए उन पर टैप करें"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"घुमाने के लिए फ़ोटो को खींचें"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"सीधा करने के लिए फ़ोटो को खींचें"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 7788858..0db281d 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"ठीक"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"शुरू करने के लिए कोई चेहरा स्पर्श करें."</string>
<string name="saving_image" msgid="7270334453636349407">"चित्र सहेज रहा है…"</string>
<string name="save_error" msgid="6857408774183654970">"काट-छांट की गई छवि को नहीं सहेज सका."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"एल्बम का चयन करें"</string>
<string name="select_group" msgid="6744208543323307114">"समूह का चयन करें"</string>
<string name="set_image" msgid="2331476809308010401">"चित्र इस रूप में सेट करें"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"वॉलपेपर सेट करें"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"चयनित आइटम हटाएं?"</item>
+ <item quantity="other" msgid="5874316486520635333">"चयनित आइटम हटाएं?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"पुष्टि करें"</string>
<string name="cancel" msgid="3637516880917356226">"रद्द करें"</string>
<string name="share" msgid="3619042788254195341">"शेयर करें"</string>
<string name="select_all" msgid="3403283025220282175">"सभी का चयन करें"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"संचय अनुरोध संसाधित कर रहा है"</string>
<string name="caching_label" msgid="4521059045896269095">"संचय कर रहा है..."</string>
<string name="crop_action" msgid="3427470284074377001">"काट-छांट करें"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"काट-छांट की गई छवि को नहीं सहेजा गया."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"काट-छांट की गई छवि को <xliff:g id="FOLDER_NAME">%s</xliff:g> में सहेजा गया."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"कोई एल्बम उपलब्ध नहीं."</string>
<string name="empty_album" msgid="4542880442593595494">"O छवियां/वीडियो उपलब्ध."</string>
<string name="picasa_posts" msgid="1497721615718760613">"पोस्ट"</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/photoeditor_strings.xml b/res/values-hr/photoeditor_strings.xml
index 077c194..6ba85f3 100644
--- a/res/values-hr/photoeditor_strings.xml
+++ b/res/values-hr/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Fotostudio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Fotografiju nije moguće učitati"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Fotografiju nije moguće spremiti"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotografija spremljena u album <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Uređeno"</string>
- <string name="save_photo" msgid="3125109368779997862">"Spremiti uređenu fotografiju?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Nije moguće učitati fotografiju"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Nije moguće spremiti uređenu fotografiju"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Uređena fotografija spremljena u mapu <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Odbaciti nespremljene promjene?"</string>
<string name="yes" msgid="5402582493291792293">"Da"</string>
- <string name="no" msgid="5595408018304861875">"Ne"</string>
<string name="save" msgid="5516670392524294967">"SPREMI"</string>
<string name="autofix" msgid="1663414996270538748">"Aut. popravak"</string>
<string name="crop" msgid="7598378507763334041">"Obreži"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Povucite oznake da biste obrezali sliku"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Crtanje na fotografiju za doodle logotip"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Povucite sliku da biste je preokrenuli"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Dotaknite za uklanjanje crvenih očiju"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Dotaknite oči da biste uklonili crvenilo"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Povucite fotografiju da biste je rotirali"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Povucite fotografiju da biste je izravnali"</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 6c57869..ccc3856 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Odaberi album"</string>
<string name="select_group" msgid="6744208543323307114">"Odabir grupe"</string>
<string name="set_image" msgid="2331476809308010401">"Postavi sliku kao"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Postavljanje pozadine"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Izbrisati odabranu stavku?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Izbrisati odabrane stavke?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Potvrdi"</string>
<string name="cancel" msgid="3637516880917356226">"Odustani"</string>
<string name="share" msgid="3619042788254195341">"Podijeli"</string>
<string name="select_all" msgid="3403283025220282175">"Odaberi sve"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Rotiraj udesno"</string>
<string name="no_such_item" msgid="5315144556325243400">"Nije moguće pronaći stavku."</string>
<string name="edit" msgid="1502273844748580847">"Uredi"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Nema dostupnih aplikacija za dovršavanje radnje."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Obrada zahtjeva za predmemoriju"</string>
<string name="caching_label" msgid="4521059045896269095">"U predmemoriju…"</string>
<string name="crop_action" msgid="3427470284074377001">"Obreži"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Obrezana slika nije spremljena."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Obrezana slika spremljena u mapu <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Nema dostupnih albuma."</string>
<string name="empty_album" msgid="4542880442593595494">"Dostupno je 0 slika/videozapisa."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Postovi"</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/photoeditor_strings.xml b/res/values-hu/photoeditor_strings.xml
index 0b60f1c..4136478 100644
--- a/res/values-hu/photoeditor_strings.xml
+++ b/res/values-hu/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"A fotót nem lehet betölteni"</string>
- <string name="saving_failure" msgid="8596600582621364077">"A fotót nem lehet menteni"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotó mentve a(z) <xliff:g id="ALBUM_NAME">%s</xliff:g> albumba"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Szerkesztve"</string>
- <string name="save_photo" msgid="3125109368779997862">"A szerkesztett kép mentése?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Nem sikerült betölteni a fotót"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"A szerkesztett fotót nem tudtuk menteni"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"A Szerkesztett fotót <xliff:g id="FOLDER_NAME">%s</xliff:g> mappába mentettük"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Elveti a nem mentett módosításokat?"</string>
<string name="yes" msgid="5402582493291792293">"Igen"</string>
- <string name="no" msgid="5595408018304861875">"Nem"</string>
<string name="save" msgid="5516670392524294967">"MENTÉS"</string>
<string name="autofix" msgid="1663414996270538748">"Autom. javítás"</string>
<string name="crop" msgid="7598378507763334041">"Körbevágás"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Húzza a jelölőket a fotó vágásához"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Rajzoljon a fotóra embléma létrehozásához"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Húzza a fotót annak tükrözéséhez"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Érintse meg a vörös szem hatás eltávolításához"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Vörös szemek eltávolítása megérintéssel"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Húzza a fényképet annak forgatásához"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Húzza a fényképet annak kiegyenesítéséhez"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index dd905ae..e0d360b 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Album kiválasztása"</string>
<string name="select_group" msgid="6744208543323307114">"Csoport kiválasztása"</string>
<string name="set_image" msgid="2331476809308010401">"Kép beállítása, mint"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Háttérkép beállítása"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Törli a kiválasztott elemet?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Törli a kiválasztott elemeket?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Megerősítés"</string>
<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>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Forgatás jobbra"</string>
<string name="no_such_item" msgid="5315144556325243400">"Nem található az elem."</string>
<string name="edit" msgid="1502273844748580847">"Szerkesztés"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Nincs elérhető alkalmazás a művelet befejezéséhez."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Tárolási kérelmek feldolgozása"</string>
<string name="caching_label" msgid="4521059045896269095">"Gyorsítótárazás..."</string>
<string name="crop_action" msgid="3427470284074377001">"Körbevágás"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"A vágott kép nincs elmentve."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"A vágott képet a(z) <xliff:g id="FOLDER_NAME">%s</xliff:g> mappába mentettük."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Nincs elérhető album."</string>
<string name="empty_album" msgid="4542880442593595494">"Nincs elérhető kép/videó."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Bejegyzések"</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/photoeditor_strings.xml b/res/values-in/photoeditor_strings.xml
index f29c0ab..3aea629 100644
--- a/res/values-in/photoeditor_strings.xml
+++ b/res/values-in/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Foto tidak dapat dimuat"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Foto tidak dapat disimpan"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto disimpan ke album <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Hasil edit"</string>
- <string name="save_photo" msgid="3125109368779997862">"Simpan foto yang diedit?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Tidak dapat memuat foto"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Tidak dapat menyimpan foto yang diedit"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Foto yang diedit disimpan di <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Buang perubahan yang tidak tersimpan?"</string>
<string name="yes" msgid="5402582493291792293">"Ya"</string>
- <string name="no" msgid="5595408018304861875">"Tidak"</string>
<string name="save" msgid="5516670392524294967">"SIMPAN"</string>
<string name="autofix" msgid="1663414996270538748">"Prbaikan Otomts"</string>
<string name="crop" msgid="7598378507763334041">"Pangkas"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Seret penanda untuk memangkas foto"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Menggambar pada foto untuk membuat orat-oret"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Seret foto untuk membaliknya"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Ketuk untuk menghapus mata merah"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Ketuk mata merah untuk menghapusnya"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Seret foto untuk memutarnya"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Seret foto untuk meluruskannya"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 9f62b25..d79aa33 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,15 +44,20 @@
<string name="select_album" msgid="1557063764849434077">"Pilih album"</string>
<string name="select_group" msgid="6744208543323307114">"Pilih grup"</string>
<string name="set_image" msgid="2331476809308010401">"Setel gambar sebagai"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Setel wallpaper"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Hapus item yang dipilih?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Hapus item yang dipilih?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Konfirmasi"</string>
<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>
@@ -75,8 +82,7 @@
<string name="rotate_right" msgid="6776325835923384839">"Putar ke kanan"</string>
<string name="no_such_item" msgid="5315144556325243400">"Tidak dapat menemukan item."</string>
<string name="edit" msgid="1502273844748580847">"Edit"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Tak ada apl tersedia untuk menyelesaikan tindakan."</string>
- <string name="process_caching_requests" msgid="8722939570307386071">"Memproses permintaan menyimpan ke tembolok"</string>
+ <string name="process_caching_requests" msgid="8722939570307386071">"Memproses permintaan menyimpan ke cache"</string>
<string name="caching_label" msgid="4521059045896269095">"Smpn ke tmbolok"</string>
<string name="crop_action" msgid="3427470284074377001">"Pangkas"</string>
<string name="set_as" msgid="3636764710790507868">"Setel sebagai"</string>
@@ -90,24 +96,22 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Gambar yang dipangkas tidak disimpan."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Gambar yang dipotong disimpan ke <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<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/photoeditor_strings.xml b/res/values-it/photoeditor_strings.xml
index 49044ef..1e42e79 100644
--- a/res/values-it/photoeditor_strings.xml
+++ b/res/values-it/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Impossibile caricare la foto"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Impossibile salvare la foto"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto salvate nell\'album <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Modificata"</string>
- <string name="save_photo" msgid="3125109368779997862">"Salvare la foto modificata?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Impossibile caricare la foto"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Impossibile salvare la foto modificata"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Foto modificata salvata in <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Ignorare le modifiche non salvate?"</string>
<string name="yes" msgid="5402582493291792293">"Sì"</string>
- <string name="no" msgid="5595408018304861875">"No"</string>
<string name="save" msgid="5516670392524294967">"SALVA"</string>
<string name="autofix" msgid="1663414996270538748">"Correzione autom."</string>
<string name="crop" msgid="7598378507763334041">"Ritaglia"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Trascina gli indicatori per ritagliare la foto"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Disegna sulla foto per scarabocchiarla"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Trascina la foto per girarla"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Tocca per rimuovere gli occhi rossi"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Tocca gli occhi rossi per rimuoverli"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Trascina la foto per ruotarla"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Trascina la foto per raddrizzarla"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 321bf67..88d57ab 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Seleziona album"</string>
<string name="select_group" msgid="6744208543323307114">"Seleziona gruppo"</string>
<string name="set_image" msgid="2331476809308010401">"Imposta foto come"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Imposta sfondo"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Eliminare elemento selezionato?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Eliminare elementi selezionati?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Conferma"</string>
<string name="cancel" msgid="3637516880917356226">"Annulla"</string>
<string name="share" msgid="3619042788254195341">"Condividi"</string>
<string name="select_all" msgid="3403283025220282175">"Seleziona tutti"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Ruota a destra"</string>
<string name="no_such_item" msgid="5315144556325243400">"Impossibile trovare l\'elemento."</string>
<string name="edit" msgid="1502273844748580847">"Modifica"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Nessuna appl. disponibile per completare azione."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Elaborazione richieste memorizzazione cache"</string>
<string name="caching_label" msgid="4521059045896269095">"Memorizzazione..."</string>
<string name="crop_action" msgid="3427470284074377001">"Ritaglia"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"L\'immagine ritagliata non è stata salvata."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Immagine ritagliata salvata in <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Nessun album disponibile."</string>
<string name="empty_album" msgid="4542880442593595494">"Nessun video/immagine disponibile."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Post"</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/photoeditor_strings.xml b/res/values-iw/photoeditor_strings.xml
index 1cc55b3..bde3689 100644
--- a/res/values-iw/photoeditor_strings.xml
+++ b/res/values-iw/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"לא ניתן לטעון את התמונה"</string>
- <string name="saving_failure" msgid="8596600582621364077">"לא ניתן לשמור את התמונה"</string>
- <string name="photo_saved" msgid="3494363661299484657">"התמונה נשמרה באלבום <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"תמונות ערוכות"</string>
- <string name="save_photo" msgid="3125109368779997862">"לשמור את התמונה הערוכה?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"לא ניתן היה לטעון את התמונה"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"לא ניתן היה לשמור את התמונה שנערכה"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"התמונה שנערכה נשמרה ב-<xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"למחוק שינויים שלא נשמרו?"</string>
<string name="yes" msgid="5402582493291792293">"כן"</string>
- <string name="no" msgid="5595408018304861875">"לא"</string>
<string name="save" msgid="5516670392524294967">"שמור"</string>
<string name="autofix" msgid="1663414996270538748">"תיקון אוטומטי"</string>
<string name="crop" msgid="7598378507763334041">"חתוך"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"גרור סמנים כדי לחתוך תמונה"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"גרור על גבי התמונה כדי לצייר עליה"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"גרור את התמונה כדי להפוך אותה"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"הקש כדי להסיר עיניים אדומות"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"הקש על עיניים אדומות כדי להסיר אותן"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"גרור את התמונה כדי לסובב אותה"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"גרור את התמונה כדי ליישר אותה"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 76e7133..c62aad4 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"אישור"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"גע בפנים כלשהם כדי להתחיל."</string>
<string name="saving_image" msgid="7270334453636349407">"שומר תמונה..."</string>
<string name="save_error" msgid="6857408774183654970">"לא ניתן לשמור את התמונה החתוכה."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"בחר אלבום"</string>
<string name="select_group" msgid="6744208543323307114">"בחר קבוצה"</string>
<string name="set_image" msgid="2331476809308010401">"הגדר תמונה בתור"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"הגדר טפט"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"האם למחוק את הפריט שנבחר?"</item>
+ <item quantity="other" msgid="5874316486520635333">"האם למחוק את הפריטים שנבחרו?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"אשר"</string>
<string name="cancel" msgid="3637516880917356226">"ביטול"</string>
<string name="share" msgid="3619042788254195341">"שיתוף"</string>
<string name="select_all" msgid="3403283025220282175">"בחר הכול"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"מעבד בקשות העברה למטמון"</string>
<string name="caching_label" msgid="4521059045896269095">"מעביר למטמון..."</string>
<string name="crop_action" msgid="3427470284074377001">"חתוך"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"התמונה החתוכה לא נשמרה."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"התמונה שנחתכה נשמרה ב-<xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"אין אלבומים זמינים."</string>
<string name="empty_album" msgid="4542880442593595494">"O תמונות/סרטוני וידאו זמינים."</string>
<string name="picasa_posts" msgid="1497721615718760613">"פוסטים"</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/photoeditor_strings.xml b/res/values-ja/photoeditor_strings.xml
index 7686611..5fab603 100644
--- a/res/values-ja/photoeditor_strings.xml
+++ b/res/values-ja/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"フォトスタジオ"</string>
- <string name="loading_failure" msgid="5990741970574295950">"画像を読み込めません"</string>
- <string name="saving_failure" msgid="8596600582621364077">"画像を保存できません"</string>
- <string name="photo_saved" msgid="3494363661299484657">"写真をアルバム「<xliff:g id="ALBUM_NAME">%s</xliff:g>」に保存しました"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"編集済み"</string>
- <string name="save_photo" msgid="3125109368779997862">"編集した写真を保存しますか?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"画像を読み込めませんでした"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"編集した画像を保存できませんでした"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"編集した画像を<xliff:g id="FOLDER_NAME">%s</xliff:g>に保存しました"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"保存されていない変更を破棄しますか?"</string>
<string name="yes" msgid="5402582493291792293">"はい"</string>
- <string name="no" msgid="5595408018304861875">"いいえ"</string>
<string name="save" msgid="5516670392524294967">"保存"</string>
<string name="autofix" msgid="1663414996270538748">"自動修正"</string>
<string name="crop" msgid="7598378507763334041">"トリミング"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"写真をトリミングするにはマーカーをドラッグ"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"画像の上に書き込みます"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"画像をめくるにはドラッグします"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"赤目を補正するにはタップします"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"赤目を補正するには赤目部分をタップします"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"画像を回転させるにはドラッグします"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"画像をまっすぐにするにはドラッグします"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 460fe6d..8cef890 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"OK"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"始めるには顔をタップします。"</string>
<string name="saving_image" msgid="7270334453636349407">"写真を保存中…"</string>
<string name="save_error" msgid="6857408774183654970">"トリミングした画像を保存できません。"</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"アルバムを選択"</string>
<string name="select_group" msgid="6744208543323307114">"グループの選択"</string>
<string name="set_image" msgid="2331476809308010401">"登録"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"壁紙を設定"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"アイテムを削除しますか?"</item>
+ <item quantity="other" msgid="5874316486520635333">"アイテムを削除しますか?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"確認"</string>
<string name="cancel" msgid="3637516880917356226">"キャンセル"</string>
<string name="share" msgid="3619042788254195341">"共有"</string>
<string name="select_all" msgid="3403283025220282175">"すべて選択"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"キャッシュリクエストを処理しています"</string>
<string name="caching_label" msgid="4521059045896269095">"キャッシュ中..."</string>
<string name="crop_action" msgid="3427470284074377001">"トリミング"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"トリミングした画像は保存されていません。"</string>
+ <string name="crop_saved" msgid="1595985909779105158">"トリミングした画像を<xliff:g id="FOLDER_NAME">%s</xliff:g>に保存しました。"</string>
<string name="no_albums_alert" msgid="4111744447491690896">"使用できるアルバムはありません。"</string>
<string name="empty_album" msgid="4542880442593595494">"使用できる画像/動画: 0件"</string>
<string name="picasa_posts" msgid="1497721615718760613">"投稿"</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/photoeditor_strings.xml b/res/values-ko/photoeditor_strings.xml
index cede76b..3f89a37 100644
--- a/res/values-ko/photoeditor_strings.xml
+++ b/res/values-ko/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"사진 스튜디오"</string>
- <string name="loading_failure" msgid="5990741970574295950">"사진을 로드할 수 없습니다."</string>
- <string name="saving_failure" msgid="8596600582621364077">"사진을 저장할 수 없습니다."</string>
- <string name="photo_saved" msgid="3494363661299484657">"<xliff:g id="ALBUM_NAME">%s</xliff:g> 앨범에 사진을 저장했습니다."</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"수정된 사진"</string>
- <string name="save_photo" msgid="3125109368779997862">"수정한 사진을 저장하시겠습니까?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"사진을 로드할 수 없습니다."</string>
+ <string name="saving_failure" msgid="8229491575433743974">"수정한 사진을 저장할 수 없습니다."</string>
+ <string name="photo_saved" msgid="6163006724627682202">"수정한 사진이 <xliff:g id="FOLDER_NAME">%s</xliff:g>에 저장되었습니다."</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"저장되지 않은 변경사항을 삭제하시겠습니까?"</string>
<string name="yes" msgid="5402582493291792293">"예"</string>
- <string name="no" msgid="5595408018304861875">"아니요"</string>
<string name="save" msgid="5516670392524294967">"저장"</string>
<string name="autofix" msgid="1663414996270538748">"자동 보정"</string>
<string name="crop" msgid="7598378507763334041">"자르기"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"아이콘을 드래그하여 사진 자르기"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"사진에 그림을 그려 낙서하기"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"사진을 드래그하여 방향 바꾸기"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"터치하여 적목 현상 제거"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"적목현상을 없애려면 눈을 탭하세요."</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"드래그하여 사진 회전"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"사진을 드래그하여 반듯하게 하기"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1f8f45b..4ac2b37 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"확인"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"시작하려면 얼굴을 터치하세요."</string>
<string name="saving_image" msgid="7270334453636349407">"사진 저장 중..."</string>
<string name="save_error" msgid="6857408774183654970">"잘린 이미지를 저장하지 못했습니다."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"앨범 선택"</string>
<string name="select_group" msgid="6744208543323307114">"그룹 선택"</string>
<string name="set_image" msgid="2331476809308010401">"사진을 다음으로 설정"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"배경화면 설정"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"선택한 항목을 삭제할까요?"</item>
+ <item quantity="other" msgid="5874316486520635333">"선택한 항목을 삭제할까요?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"확인"</string>
<string name="cancel" msgid="3637516880917356226">"취소"</string>
<string name="share" msgid="3619042788254195341">"공유"</string>
<string name="select_all" msgid="3403283025220282175">"모두 선택"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"캐시 요청을 처리하는 중"</string>
<string name="caching_label" msgid="4521059045896269095">"캐시하는 중..."</string>
<string name="crop_action" msgid="3427470284074377001">"자르기"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"잘린 이미지가 저장되지 않았습니다."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"잘린 이미지가 <xliff:g id="FOLDER_NAME">%s</xliff:g>에 저장되었습니다."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"사용할 수 있는 앨범이 없습니다."</string>
<string name="empty_album" msgid="4542880442593595494">"사용할 수 있는 이미지/동영상이 없습니다."</string>
<string name="picasa_posts" msgid="1497721615718760613">"소식"</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-lt/photoeditor_strings.xml b/res/values-lt/photoeditor_strings.xml
index a2b5fed..c9f76c4 100644
--- a/res/values-lt/photoeditor_strings.xml
+++ b/res/values-lt/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Negalima įkelti nuotraukos"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Negalima išsaugoti nuotraukos"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Nuotrauka išsaugota albume „<xliff:g id="ALBUM_NAME">%s</xliff:g>“"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Redaguota"</string>
- <string name="save_photo" msgid="3125109368779997862">"Išsaugoti redaguotą nuotrauką?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Nepavyko įkelti nuotraukos"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Nepavyko išsaugoti redaguotos nuotraukos"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Redaguota nuotrauka išsaugota aplanke <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Atmesti neišsaugotus pakeitimus?"</string>
<string name="yes" msgid="5402582493291792293">"Taip"</string>
- <string name="no" msgid="5595408018304861875">"Ne"</string>
<string name="save" msgid="5516670392524294967">"IŠSAUGOT"</string>
<string name="autofix" msgid="1663414996270538748">"Automat. nustat."</string>
<string name="crop" msgid="7598378507763334041">"Apkarpyti"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Jei norite apkarpyti nuotr., vilkite žymeklius"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Pieškite ant nuotraukos, jei norite taikyti logotipų puošybą"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Vilkite nuotrauką, kad ją apverstumėte"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Palieskite, kad pašalintumėte raudonas akis"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Paliesk. raud. akis, kad pašal. jų efek."</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Vilkite nuotrauką, kad ją apsuktumėte"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Vilkite nuotrauką, kad ją ištiesintumėte"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 1a2b9fd..95f1a8f 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Pasirinkti albumą"</string>
<string name="select_group" msgid="6744208543323307114">"Pasirinkti grupę"</string>
<string name="set_image" msgid="2331476809308010401">"Nustatyti paveikslėlį kaip"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Nustat. darbal. foną"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Ištrinti pasirinktą elementą?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Ištrinti pasirink. elementus?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Patvirtinti"</string>
<string name="cancel" msgid="3637516880917356226">"Atšaukti"</string>
<string name="share" msgid="3619042788254195341">"Bendrinti"</string>
<string name="select_all" msgid="3403283025220282175">"Pasirinkti viską"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Sukti į dešinę"</string>
<string name="no_such_item" msgid="5315144556325243400">"Nepavyko surasti elemento."</string>
<string name="edit" msgid="1502273844748580847">"Redaguoti"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Nėra pasiekiamos programos veiksmui užbaigti."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Talpinimo užklausų apdorojimas"</string>
<string name="caching_label" msgid="4521059045896269095">"Talpinama…"</string>
<string name="crop_action" msgid="3427470284074377001">"Apkarpyti"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Apkarpytas vaizdas neišsaugotas."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Apkarpytas vaizdas išsaugotas aplanke <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Nėra pasiekiamų albumų."</string>
<string name="empty_album" msgid="4542880442593595494">"Nepasiekiami jokie vaizdai / vaizdo įrašai."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Įrašai"</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/photoeditor_strings.xml b/res/values-lv/photoeditor_strings.xml
index f1af66e..444f2c9 100644
--- a/res/values-lv/photoeditor_strings.xml
+++ b/res/values-lv/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Fotostudija"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Fotoattēlu nevar ielādēt."</string>
- <string name="saving_failure" msgid="8596600582621364077">"Fotoattēlu nevar saglabāt."</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotoattēls ir saglabāts albumā <xliff:g id="ALBUM_NAME">%s</xliff:g>."</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Rediģēti"</string>
- <string name="save_photo" msgid="3125109368779997862">"Vai saglabāt rediģēto fotoattēlu?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Fotoattēlu nevarēja ielādēt."</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Rediģēto fotoattēlu nevarēja saglabāt."</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Rediģētais attēls ir saglabāts mapē <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Vai atmest nesaglabātās izmaiņas?"</string>
<string name="yes" msgid="5402582493291792293">"Jā"</string>
- <string name="no" msgid="5595408018304861875">"Nē"</string>
<string name="save" msgid="5516670392524294967">"SAGLABĀT"</string>
<string name="autofix" msgid="1663414996270538748">"Automātiska labošana"</string>
<string name="crop" msgid="7598378507763334041">"Apgriešana"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Velciet marķierus, lai apgrieztu fotoattēlu."</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Velciet uz fotoattēla, lai kricelētu."</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Velciet fotoattēlu, lai to apgrieztu."</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Pieskarieties, lai noņemtu sarkano acu efektu."</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Piesk. sark. acīm, lai noņ. šo efektu."</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Velciet fotoattēlu, lai to pagrieztu."</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Velciet fotoattēlu, lai to iztaisnotu."</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 2da2540..c3e47b4 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Albuma atlase"</string>
<string name="select_group" msgid="6744208543323307114">"Atlasiet grupu"</string>
<string name="set_image" msgid="2331476809308010401">"Iestatīt attēlu kā:"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Iestatīt fona tapeti"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Vai dzēst atlasīto vienumu?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Vai dzēst atlasītos vienumus?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Apstiprināt"</string>
<string name="cancel" msgid="3637516880917356226">"Atcelt"</string>
<string name="share" msgid="3619042788254195341">"Dalies"</string>
<string name="select_all" msgid="3403283025220282175">"Atlasīt visu"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Pagriezt pa labi"</string>
<string name="no_such_item" msgid="5315144556325243400">"Nevarēja atrast vienumu."</string>
<string name="edit" msgid="1502273844748580847">"Rediģēt"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Nav nevienas lietotnes šīs darbības veikšanai."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Tiek apstrādāti pieprasījumi rakstīšanai kešatmiņā"</string>
<string name="caching_label" msgid="4521059045896269095">"Glabā kešatm..."</string>
<string name="crop_action" msgid="3427470284074377001">"Apgriezt"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Izgrieztais attēls netika saglabāts."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Apgrieztais attēls ir saglabāts mapē <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Nav pieejams neviens albums."</string>
<string name="empty_album" msgid="4542880442593595494">"Nav pieejams neviens attēls/videoklips."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Ziņas"</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/photoeditor_strings.xml b/res/values-ms/photoeditor_strings.xml
index ac4a130..c7be0f8 100644
--- a/res/values-ms/photoeditor_strings.xml
+++ b/res/values-ms/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Studio Foto"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Foto tidak boleh dimuatkan"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Foto tidak boleh disimpan"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto disimpan ke album <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Diedit"</string>
- <string name="save_photo" msgid="3125109368779997862">"Simpan foto yang diedit?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Tidak dapat memuatkan foto"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Tidak dapat menyimpan foto yang diedit"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Foto diedit disimpan ke <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Buang perubahan yang belum disimpan?"</string>
<string name="yes" msgid="5402582493291792293">"Ya"</string>
- <string name="no" msgid="5595408018304861875">"Tidak"</string>
<string name="save" msgid="5516670392524294967">"SIMPAN"</string>
<string name="autofix" msgid="1663414996270538748">"Auto-baiki"</string>
<string name="crop" msgid="7598378507763334041">"Pangkas"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Seret penanda untuk memangkas foto"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Lukis pada foto untuk mencoret"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Seret foto untuk menterbalikkannya"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Ketik untuk mengalih keluar mata merah"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Ketik pada mata merah untuk mengalih keluar"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Seret foto untuk memutarnya"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Seret foto untuk meluruskannya"</string>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index eead8b1..8349896 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Pilih album"</string>
<string name="select_group" msgid="6744208543323307114">"Pilih kumpulan"</string>
<string name="set_image" msgid="2331476809308010401">"Tetapkan gambar sebagai"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Tetapkan kertas dinding"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Padam item yang dipilih?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Padam item yang dipilih?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Sahkan"</string>
<string name="cancel" msgid="3637516880917356226">"Batal"</string>
<string name="share" msgid="3619042788254195341">"Kongsi"</string>
<string name="select_all" msgid="3403283025220282175">"Pilih semua"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Putar ke kanan"</string>
<string name="no_such_item" msgid="5315144556325243400">"Tidak dapat mencari item."</string>
<string name="edit" msgid="1502273844748580847">"Edit"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Tiada aplikasi tersedia untuk melengkapkan tindakan."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Memproses permintaan cache"</string>
<string name="caching_label" msgid="4521059045896269095">"Mengcache..."</string>
<string name="crop_action" msgid="3427470284074377001">"Pangkas"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Imej yang dipangkas tidak disimpan."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Imej yang dipangkas disimpan ke <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Tiada album tersedia."</string>
<string name="empty_album" msgid="4542880442593595494">"O imej/video yang tersedia."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Siaran"</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/photoeditor_strings.xml b/res/values-nb/photoeditor_strings.xml
index 03f7c12..425cd77 100644
--- a/res/values-nb/photoeditor_strings.xml
+++ b/res/values-nb/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Bildet kan ikke lastes inn"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Bildet kan ikke lagres"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto lagret i albumet <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Redigert"</string>
- <string name="save_photo" msgid="3125109368779997862">"Vil du lagre det redigerte bildet?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Kunne ikke laste inn bildet"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Kunne ikke lagre det redigerte bildet"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Det redigerte bildet ble lagret i <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Vil du forkaste endringer som ikke er lagret?"</string>
<string name="yes" msgid="5402582493291792293">"Ja"</string>
- <string name="no" msgid="5595408018304861875">"Nei"</string>
<string name="save" msgid="5516670392524294967">"LAGRE"</string>
<string name="autofix" msgid="1663414996270538748">"Autokorrektur"</string>
<string name="crop" msgid="7598378507763334041">"Beskjær"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Dra markører for å beskjære bildet"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Tegn på bildet for å drodle"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Dra bildet for å snu det"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Trykk for å fjerne røde øyne"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Trykk på røde øyne for å fjerne dem"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Dra bildet for å rotere det"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Dra bildet for å rette på det"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index d8a708a..37874fe 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Velg album"</string>
<string name="select_group" msgid="6744208543323307114">"Valg av gruppe"</string>
<string name="set_image" msgid="2331476809308010401">"Angi bilde som"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Angi bakgrunnsbilde"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Vil du slette det valgte medieelementet?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Vil du slette de valgte medieelementene?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Bekreft"</string>
<string name="cancel" msgid="3637516880917356226">"Avbryt"</string>
<string name="share" msgid="3619042788254195341">"Del"</string>
<string name="select_all" msgid="3403283025220282175">"Velg alle"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Roter mot høyre"</string>
<string name="no_such_item" msgid="5315144556325243400">"Finner ikke elementet."</string>
<string name="edit" msgid="1502273844748580847">"Rediger"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Ingen app er tilgjengelig for å fullføre handlingen."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Behandler forespørsler om bufring"</string>
<string name="caching_label" msgid="4521059045896269095">"Henter …"</string>
<string name="crop_action" msgid="3427470284074377001">"Beskjær"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Beskåret bilde ble ikke lagret."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Det beskårede bildet er lagret i <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Ingen album tilgjengelig."</string>
<string name="empty_album" msgid="4542880442593595494">"0 bilder/videoer tilgjengelig."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Innlegg"</string>
@@ -155,11 +159,16 @@
<string name="widget_type" msgid="1364653978966343448">"Valg av bilder"</string>
<string name="slideshow_dream_name" msgid="6915963319933437083">"Lysbildefremvisning"</string>
<string name="albums" msgid="7320787705180057947">"Album"</string>
- <string name="times" msgid="2023033894889499219">"Ganger"</string>
+ <string name="times" msgid="2023033894889499219">"Tidspunkt"</string>
<string name="locations" msgid="6649297994083130305">"Steder"</string>
<string name="people" msgid="4114003823747292747">"Folk"</string>
<string name="tags" msgid="5539648765482935955">"Etiketter"</string>
<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/photoeditor_strings.xml b/res/values-nl/photoeditor_strings.xml
index 7a0f374..e4418fc 100644
--- a/res/values-nl/photoeditor_strings.xml
+++ b/res/values-nl/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Foto kan niet worden geladen"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Foto kan niet worden opgeslagen"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto opgeslagen in album <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Bewerkt"</string>
- <string name="save_photo" msgid="3125109368779997862">"Bewerkte foto opslaan?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Kan de foto niet laden"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Kan de bewerkte foto niet opslaan"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Bewerkte foto is opgeslagen in <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Niet-opgeslagen wijzigingen annuleren?"</string>
<string name="yes" msgid="5402582493291792293">"Ja"</string>
- <string name="no" msgid="5595408018304861875">"Nee"</string>
<string name="save" msgid="5516670392524294967">"OPSLAAN"</string>
<string name="autofix" msgid="1663414996270538748">"Auto-fix"</string>
<string name="crop" msgid="7598378507763334041">"Bijsnijden"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Sleep de markeringen om de foto bij te snijden"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Teken op de foto om hierop te krabbelen"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Sleep de foto om deze te spiegelen"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Tik om rode ogen te verwijderen"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Tik op rode ogen om ze te verwijderen"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Sleep de foto om deze te draaien"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Sleep de foto om deze recht te zetten"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 9e43d85..f28c90d 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Album selecteren"</string>
<string name="select_group" msgid="6744208543323307114">"Groep selecteren"</string>
<string name="set_image" msgid="2331476809308010401">"Foto instellen als"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Achtergrond inst."</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Geselecteerd item verwijderen?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Geselect. items verwijderen?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Bevestigen"</string>
<string name="cancel" msgid="3637516880917356226">"Annuleren"</string>
<string name="share" msgid="3619042788254195341">"Delen"</string>
<string name="select_all" msgid="3403283025220282175">"Alles selecteren"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Rechtsom draaien"</string>
<string name="no_such_item" msgid="5315144556325243400">"Kan item niet vinden."</string>
<string name="edit" msgid="1502273844748580847">"Bewerken"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Geen app beschikbaar om de actie te voltooien."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Cacheverzoeken verwerken"</string>
<string name="caching_label" msgid="4521059045896269095">"In cache opslaan..."</string>
<string name="crop_action" msgid="3427470284074377001">"Bijsnijden"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Bijgesneden afbeelding is niet opgeslagen."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Bijgesneden afbeelding is opgeslagen in <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Geen albums beschikbaar."</string>
<string name="empty_album" msgid="4542880442593595494">"0 afbeeldingen/video\'s beschikbaar."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Berichten"</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/photoeditor_strings.xml b/res/values-pl/photoeditor_strings.xml
index e48c333..628cccb 100644
--- a/res/values-pl/photoeditor_strings.xml
+++ b/res/values-pl/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Studio fotograficzne"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Nie można wczytać zdjęcia"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Nie można zapisać zdjęcia"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Zdjęcie zapisano w albumie <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Edytowane"</string>
- <string name="save_photo" msgid="3125109368779997862">"Czy zapisać edytowane zdjęcie?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Nie można wczytać zdjęcia"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Nie można zapisać edytowanego zdjęcia"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Edytowane zdjęcie zapisano w <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Odrzucić niezapisane zmiany?"</string>
<string name="yes" msgid="5402582493291792293">"Tak"</string>
- <string name="no" msgid="5595408018304861875">"Nie"</string>
<string name="save" msgid="5516670392524294967">"ZAPISZ"</string>
<string name="autofix" msgid="1663414996270538748">"Autopoprawka"</string>
<string name="crop" msgid="7598378507763334041">"Przytnij"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Przeciągnij znaczniki, aby przyciąć zdjęcie"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Rysuj po zdjęciu, aby dodać bazgroły."</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Przeciągnij zdjęcie, aby je odwrócić."</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Dotknij, aby usunąć efekt czerwonych oczu."</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Dotknij czerwonych oczu, aby je usunąć"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Przeciągnij zdjęcie, aby je obrócić."</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Przeciągnij zdjęcie, aby je wyprostować."</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 82f4310..5b6ca53 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Wybierz album"</string>
<string name="select_group" msgid="6744208543323307114">"Wybierz grupę"</string>
<string name="set_image" msgid="2331476809308010401">"Ustaw zdjęcie jako"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Ustaw tapetę"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Usunąć wybrany element?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Usunąć wybrane elementy?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Potwierdź"</string>
<string name="cancel" msgid="3637516880917356226">"Anuluj"</string>
<string name="share" msgid="3619042788254195341">"Udostępnij"</string>
<string name="select_all" msgid="3403283025220282175">"Zaznacz wszystkie"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Obróć w prawo"</string>
<string name="no_such_item" msgid="5315144556325243400">"Nie można znaleźć elementu."</string>
<string name="edit" msgid="1502273844748580847">"Edytuj"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Brak dostępnej aplikacji do ukończenia operacji"</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Przetwarzanie żądań dotyczących buforowania"</string>
<string name="caching_label" msgid="4521059045896269095">"Buforowanie…"</string>
<string name="crop_action" msgid="3427470284074377001">"Przytnij"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Przycięty obraz nie został zapisany."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Przycięty obraz zapisano w <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Brak dostępnych albumów"</string>
<string name="empty_album" msgid="4542880442593595494">"O dostępnych obrazów/filmów"</string>
<string name="picasa_posts" msgid="1497721615718760613">"Posty"</string>
@@ -139,7 +143,7 @@
<string name="set_label_local_albums" msgid="6698133661656266702">"Albumy lokalne"</string>
<string name="set_label_mtp_devices" msgid="1283513183744896368">"Urządzenia MTP"</string>
<string name="set_label_picasa_albums" msgid="5356258354953935895">"Albumy Picasa"</string>
- <string name="free_space_format" msgid="8766337315709161215">"Wolne: <xliff:g id="BYTES">%s</xliff:g>"</string>
+ <string name="free_space_format" msgid="8766337315709161215">"<xliff:g id="BYTES">%s</xliff:g> wolne"</string>
<string name="size_below" msgid="2074956730721942260">"<xliff:g id="SIZE">%1$s</xliff:g> lub mniej"</string>
<string name="size_above" msgid="5324398253474104087">"<xliff:g id="SIZE">%1$s</xliff:g> lub więcej"</string>
<string name="size_between" msgid="8779660840898917208">"<xliff:g id="MIN_SIZE">%1$s</xliff:g> do <xliff:g id="MAX_SIZE">%2$s</xliff:g>"</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/photoeditor_strings.xml b/res/values-pt-rPT/photoeditor_strings.xml
index f0f6e00..13575a8 100644
--- a/res/values-pt-rPT/photoeditor_strings.xml
+++ b/res/values-pt-rPT/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Não é possível carregar a fotografia"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Não é possível guardar a fotografia"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotografia guardada no álbum <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editado"</string>
- <string name="save_photo" msgid="3125109368779997862">"Guardar a fotografia editada?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Não foi possível carregar a fotografia"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Não foi possível guardar a foto editada."</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Fotografia editada guardada em <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Rejeitar alterações não guardadas?"</string>
<string name="yes" msgid="5402582493291792293">"Sim"</string>
- <string name="no" msgid="5595408018304861875">"Não"</string>
<string name="save" msgid="5516670392524294967">"GUARDAR"</string>
<string name="autofix" msgid="1663414996270538748">"Correção Autom."</string>
<string name="crop" msgid="7598378507763334041">"Recortar"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Arraste os marcadores para recortar a fotografia"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Desenhe na fotografia para rabiscar"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Arraste a fotografia para virá-la"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Toque para remover o efeito de olhos vermelhos"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Toque nos olhos vermelhos p/ os remover"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Arraste a fotografia para rodá-la"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Arraste a fotografia para endireitá-la"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index d0b54fe..fa0288a 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Selecionar álbum"</string>
<string name="select_group" msgid="6744208543323307114">"Selecionar grupo"</string>
<string name="set_image" msgid="2331476809308010401">"Definir imagem como"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Definir imagem fundo"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Eliminar item selecionado?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Eliminar itens selecionados?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Confirmar"</string>
<string name="cancel" msgid="3637516880917356226">"Cancelar"</string>
<string name="share" msgid="3619042788254195341">"Partilhar"</string>
<string name="select_all" msgid="3403283025220282175">"Selecionar tudo"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Rodar para a direita"</string>
<string name="no_such_item" msgid="5315144556325243400">"Não foi possível encontrar item."</string>
<string name="edit" msgid="1502273844748580847">"Editar"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Nenhuma aplicação disponível para concluir a ação."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"A processar pedidos de colocação em cache"</string>
<string name="caching_label" msgid="4521059045896269095">"A col. cache..."</string>
<string name="crop_action" msgid="3427470284074377001">"Recortar"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"A imagem recortada não foi guardada."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Imagem recortada guardada em <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Nenhum álbum disponível."</string>
<string name="empty_album" msgid="4542880442593595494">"0 imagens/vídeos disponíveis."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Mensagens"</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/photoeditor_strings.xml b/res/values-pt/photoeditor_strings.xml
index 664c9a5..8c8a28b 100644
--- a/res/values-pt/photoeditor_strings.xml
+++ b/res/values-pt/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Não foi possível carregar a foto"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Não é possível salvar a foto"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Foto salva no álbum <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editado"</string>
- <string name="save_photo" msgid="3125109368779997862">"Salvar foto editada?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Não foi possível carregar a foto"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Não foi possível salvar a foto editada"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"A foto editada foi salva em <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Descartar as alterações não salvas?"</string>
<string name="yes" msgid="5402582493291792293">"Sim"</string>
- <string name="no" msgid="5595408018304861875">"Não"</string>
<string name="save" msgid="5516670392524294967">"SALVAR"</string>
<string name="autofix" msgid="1663414996270538748">"Correção autom."</string>
<string name="crop" msgid="7598378507763334041">"Cortar"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Arraste os marcadores para cortar a foto"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Desenhe na foto para rabiscar"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Arraste a foto para invertê-la"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Toque para remover olhos vermelhos"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Toque nos olhos vermelhos para removê-los"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Arraste a foto para girá-la"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Arraste a foto para endireitá-la"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index a5fce6c..b7a0140 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Selecionar álbum"</string>
<string name="select_group" msgid="6744208543323307114">"Selecionar grupo"</string>
<string name="set_image" msgid="2331476809308010401">"Definir imagem como"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Definir plano de fundo"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Excluir o item selecionado?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Excluir os itens selecionados?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Confirmar"</string>
<string name="cancel" msgid="3637516880917356226">"Cancelar"</string>
<string name="share" msgid="3619042788254195341">"Compartilhar"</string>
<string name="select_all" msgid="3403283025220282175">"Selecionar tudo"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Girar para a direita"</string>
<string name="no_such_item" msgid="5315144556325243400">"Não foi possível encontrar o item."</string>
<string name="edit" msgid="1502273844748580847">"Editar"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Não há aplicativo disponível para concluir a ação."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Processando solicitações de armazenamento em cache"</string>
<string name="caching_label" msgid="4521059045896269095">"Cache..."</string>
<string name="crop_action" msgid="3427470284074377001">"Cortar"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"A imagem cortada não foi salva."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"A imagem cortada foi salva em <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Nenhum álbum disponível."</string>
<string name="empty_album" msgid="4542880442593595494">"0 imagens/vídeos disponíveis."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Postagens"</string>
@@ -155,11 +159,16 @@
<string name="widget_type" msgid="1364653978966343448">"Escolher imagens"</string>
<string name="slideshow_dream_name" msgid="6915963319933437083">"Apresent. de slides"</string>
<string name="albums" msgid="7320787705180057947">"Álbuns"</string>
- <string name="times" msgid="2023033894889499219">"Vezes"</string>
+ <string name="times" msgid="2023033894889499219">"Data e hora"</string>
<string name="locations" msgid="6649297994083130305">"Locais"</string>
<string name="people" msgid="4114003823747292747">"Pessoas"</string>
<string name="tags" msgid="5539648765482935955">"Etiquetas"</string>
<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 4cf5c1f..ffb6ef7 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -34,10 +34,14 @@
<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>
- <!-- no translation found for crop_save_text (4972430481677741184) -->
+ <!-- no translation found for crop_save_text (152200178986698300) -->
+ <skip />
+ <!-- no translation found for ok (5296833083983263293) -->
<skip />
<!-- no translation found for multiface_crop_help (2554690102655855657) -->
<skip />
@@ -56,11 +60,15 @@
<!-- no translation found for select_group (6744208543323307114) -->
<skip />
<string name="set_image" msgid="2331476809308010401">"Definir il maletg sco"</string>
+ <!-- no translation found for set_wallpaper (8491121226190175017) -->
+ <skip />
<!-- no translation found for wallpaper (140165383777262070) -->
<skip />
<string name="camera_setas_wallpaper" msgid="797463183863414289">"Culissa"</string>
<string name="delete" msgid="2839695998251824487">"Stizzar"</string>
- <!-- no translation found for confirm_delete (7358770022173660511) -->
+ <!-- no translation found for delete_selection:one (6453379735401083732) -->
+ <!-- no translation found for delete_selection:other (5874316486520635333) -->
+ <!-- no translation found for confirm (8646870096527848520) -->
<skip />
<string name="cancel" msgid="3637516880917356226">"Interrumper"</string>
<string name="share" msgid="3619042788254195341">"Cundivider"</string>
@@ -92,8 +100,6 @@
<skip />
<!-- no translation found for edit (1502273844748580847) -->
<skip />
- <!-- no translation found for activity_not_found (5619154886080878023) -->
- <skip />
<!-- no translation found for process_caching_requests (8722939570307386071) -->
<skip />
<!-- no translation found for caching_label (4521059045896269095) -->
@@ -121,23 +127,19 @@
<skip />
<!-- no translation found for no_connectivity (7164037617297293668) -->
<skip />
- <!-- no translation found for sync_album_error (2218733298953719785) -->
- <skip />
- <!-- no translation found for sync_album_set_error (9016732535181154028) -->
+ <!-- no translation found for sync_album_error (1020688062900977530) -->
<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 />
<!-- no translation found for appwidget_empty_text (1228925628357366957) -->
<skip />
- <!-- no translation found for crop_saved (1062612625032731770) -->
- <skip />
- <!-- no translation found for crop_not_saved (3400773981839556) -->
+ <!-- no translation found for crop_saved (1595985909779105158) -->
<skip />
<!-- no translation found for no_albums_alert (4111744447491690896) -->
<skip />
@@ -256,4 +258,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/photoeditor_strings.xml b/res/values-ro/photoeditor_strings.xml
index f4c23ec..3967449 100644
--- a/res/values-ro/photoeditor_strings.xml
+++ b/res/values-ro/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Studio foto"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Fotografia nu poate fi încărcată"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Fotografia nu poate fi salvată"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotografie salvată în albumul <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Editate"</string>
- <string name="save_photo" msgid="3125109368779997862">"Salvaţi fotografia editată?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Fotografia nu a putut fi încărcată"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Fotografia editată nu a putut fi salvată"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Fotografia editată a fost salvată în <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Renunţaţi la modificările nesalvate?"</string>
<string name="yes" msgid="5402582493291792293">"Da"</string>
- <string name="no" msgid="5595408018304861875">"Nu"</string>
<string name="save" msgid="5516670392524294967">"SALVAŢI"</string>
<string name="autofix" msgid="1663414996270538748">"Remediere auto."</string>
<string name="crop" msgid="7598378507763334041">"Decupaţi"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Trageţi marcatorii pentru a decupa fotografia"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Desenaţi pe fotografie pentru a crea doodle"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Trageţi o fotografie pentru a o răsturna"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Apăsaţi pentru a elimina ochii roşii"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Apăsaţi pe ochii roşii pentru eliminare"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Trageţi fotografia pentru a o roti"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Trageţi de fotografie pentru a o îndrepta"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 3caf4bc..d10bf6f 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Selectaţi un album"</string>
<string name="select_group" msgid="6744208543323307114">"Selectaţi un grup"</string>
<string name="set_image" msgid="2331476809308010401">"Setaţi fotografia ca"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Setaţi imag. fundal"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Ştergeţi articolul selectat?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Ştergeţi articolele selectate?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Confirmaţi"</string>
<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>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Rotiţi spre dreapta"</string>
<string name="no_such_item" msgid="5315144556325243400">"Elementul nu a putut fi găsit."</string>
<string name="edit" msgid="1502273844748580847">"Editaţi"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Nu există aplic. dispon. pt. a finaliza acţiunea."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Se proces. solicit. de stocare în memoria cache"</string>
<string name="caching_label" msgid="4521059045896269095">"Mem. cache..."</string>
<string name="crop_action" msgid="3427470284074377001">"Decupaţi"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Imaginea decupată nu a fost salvată."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Imaginea decupată a fost salvată în <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Nu există albume disponibile."</string>
<string name="empty_album" msgid="4542880442593595494">"Nu există imagini/videoclipuri disponibile."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Postă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/photoeditor_strings.xml b/res/values-ru/photoeditor_strings.xml
index 312ae43..af7f91f 100644
--- a/res/values-ru/photoeditor_strings.xml
+++ b/res/values-ru/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Фотостудия"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Не удалось загрузить фото"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Не удалось сохранить фото"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Фотография сохранена в альбоме \"<xliff:g id="ALBUM_NAME">%s</xliff:g>\""</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Измененные"</string>
- <string name="save_photo" msgid="3125109368779997862">"Сохранить изменения?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Не удалось загрузить фотографию"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Не удалось сохранить изменения"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Измененная фотография сохранена в папке <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Отменить несохраненные изменения?"</string>
<string name="yes" msgid="5402582493291792293">"Да"</string>
- <string name="no" msgid="5595408018304861875">"Нет"</string>
<string name="save" msgid="5516670392524294967">"ГОТОВО"</string>
<string name="autofix" msgid="1663414996270538748">"Авто"</string>
<string name="crop" msgid="7598378507763334041">"Кадрировать"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Чтобы кадрировать фото, перетащите маркеры"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Рисуйте на фото пальцем"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Чтобы отразить фото, перетащите его край"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Нажмите для устранения эффекта красных глаз"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Нажмите на красные глаза, чтобы убрать их"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Чтобы повернуть фото, перетащите его край"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Чтобы выровнять фото, перетащите его"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 0413826..a9a7800 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"ОК"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Нажмите на лицо, чтобы начать."</string>
<string name="saving_image" msgid="7270334453636349407">"Сохранение картинки..."</string>
<string name="save_error" msgid="6857408774183654970">"Изображение не сохранено."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Выбрать альбомы"</string>
<string name="select_group" msgid="6744208543323307114">"Выбрать группы"</string>
<string name="set_image" msgid="2331476809308010401">"Установить картинку как"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Установить обои"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Удалить выбранный файл?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Удалить выбранные файлы?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Подтвердить"</string>
<string name="cancel" msgid="3637516880917356226">"Отмена"</string>
<string name="share" msgid="3619042788254195341">"Отправить"</string>
<string name="select_all" msgid="3403283025220282175">"Выбрать все"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"Обработка запросов на кэширование"</string>
<string name="caching_label" msgid="4521059045896269095">"Кэширование..."</string>
<string name="crop_action" msgid="3427470284074377001">"Кадрировать"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Изображение не сохранено."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Обрезанное изображение сохранено в папке <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Нет доступных альбомов."</string>
<string name="empty_album" msgid="4542880442593595494">"Нет фото/видео"</string>
<string name="picasa_posts" msgid="1497721615718760613">"Записи"</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/photoeditor_strings.xml b/res/values-sk/photoeditor_strings.xml
index c5c2684..2b563f4 100644
--- a/res/values-sk/photoeditor_strings.xml
+++ b/res/values-sk/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Fotografiu sa nepodarilo načítať"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Fotografiu sa nepodarilo uložiť"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotografia bola uložená do albumu <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Upravené"</string>
- <string name="save_photo" msgid="3125109368779997862">"Chcete upravenú fotografiu uložiť?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Fotografiu sa nepodarilo načítať"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Upravenú fotografiu sa nepodarilo uložiť"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Uprav. fotografia bola ulož. do prieč. <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Zahodiť neuložené zmeny?"</string>
<string name="yes" msgid="5402582493291792293">"Áno"</string>
- <string name="no" msgid="5595408018304861875">"Nie"</string>
<string name="save" msgid="5516670392524294967">"ULOŽIŤ"</string>
<string name="autofix" msgid="1663414996270538748">"Automat. oprava"</string>
<string name="crop" msgid="7598378507763334041">"Orezať"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Potiahnutím značiek orežete fotografiu"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Na fotografii môžete kresliť prstom"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Potiahnutím prstom fotografiu prevrátite"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Klepnutím odstráňte efekt červených očí"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Červené oči odstránite klepnutím na ne"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Potiahnutím prstom fotografiu otočíte"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Potiahnutím prstom fotografiu vyrovnáte"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index c25ea40..bd914b9 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Vybrať album"</string>
<string name="select_group" msgid="6744208543323307114">"Vybrať skupinu"</string>
<string name="set_image" msgid="2331476809308010401">"Fotografia bude použitá ako"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Nastaviť tapetu"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Odstrániť vybratú položku?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Odstrániť vybraté položky?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Potvrdiť"</string>
<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>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Otočiť doprava"</string>
<string name="no_such_item" msgid="5315144556325243400">"Položku sa nepodarilo nájsť."</string>
<string name="edit" msgid="1502273844748580847">"Upraviť"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Na dokončenie akcie chýba aplikácia."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Spracovávanie žiadostí o ulož. do vyrovnáv. pamäte"</string>
<string name="caching_label" msgid="4521059045896269095">"Do pamäte..."</string>
<string name="crop_action" msgid="3427470284074377001">"Orezať"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Orezaný obrázok nebol uložený."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Orezaný obrázok bol uložený do priečinka <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Nie sú k dispozícii žiadne albumy."</string>
<string name="empty_album" msgid="4542880442593595494">"Počet obrázkov / videí k dispozícii: 0"</string>
<string name="picasa_posts" msgid="1497721615718760613">"Príspevky"</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/photoeditor_strings.xml b/res/values-sl/photoeditor_strings.xml
index 802cb07..10a6e82 100644
--- a/res/values-sl/photoeditor_strings.xml
+++ b/res/values-sl/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Slike ni mogoče prenesti"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Fotografije ni mogoče shraniti"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotografija je bila shranjena v album <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Urejeno"</string>
- <string name="save_photo" msgid="3125109368779997862">"Želite shraniti urejene fotografije?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Fotografije ni mogoče naložiti"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Urejene fotografije ni mogoče shraniti"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Urejena fotografija je shranjena v mapi <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Želite zavreči neshranjene spremembe?"</string>
<string name="yes" msgid="5402582493291792293">"Da"</string>
- <string name="no" msgid="5595408018304861875">"Ne"</string>
<string name="save" msgid="5516670392524294967">"SHRANI"</string>
<string name="autofix" msgid="1663414996270538748">"Samod. poprav."</string>
<string name="crop" msgid="7598378507763334041">"Obrezovanje"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Povlecite oznake, da obrežete fotografijo"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Povlecite po fotografiji za prostoročno risanje"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Povlecite fotografijo, da jo obrnete"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Tapnite, da odstranite rdeče oči"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Rdeče oči odstranite z dotikom"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Povlecite fotografijo, da jo zavrtite"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Povlecite fotografijo, da jo poravnate"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 39c7266..fb20120 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Izberite album"</string>
<string name="select_group" msgid="6744208543323307114">"Izberite skupino"</string>
<string name="set_image" msgid="2331476809308010401">"Nastavi sliko kot"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Nastavi ozadje"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Želite izbrisati ta element?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Želite izbrisati te elemente?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Potrdi"</string>
<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>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Zasukaj desno"</string>
<string name="no_such_item" msgid="5315144556325243400">"Ni mogoče najti elementa."</string>
<string name="edit" msgid="1502273844748580847">"Urejanje"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Ni programa, ki bi dokončal dejanje."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Obdelava zahtev za predpomnjenje"</string>
<string name="caching_label" msgid="4521059045896269095">"Predpomnjenje ..."</string>
<string name="crop_action" msgid="3427470284074377001">"Obrezovanje"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Obrezana slika ni bila shranjena."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Obrezana slika je shranjena v mapi <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Ni albumov."</string>
<string name="empty_album" msgid="4542880442593595494">"Na voljo 0 slik oz. videoposnetkov."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Objave"</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/photoeditor_strings.xml b/res/values-sr/photoeditor_strings.xml
index a4a9ab5..6ac3576 100644
--- a/res/values-sr/photoeditor_strings.xml
+++ b/res/values-sr/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Фото-студио"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Фотографија не може да се учита"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Фотографија не може да се сачува"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Фотографија је сачувана у албум <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Измењено"</string>
- <string name="save_photo" msgid="3125109368779997862">"Желите ли да сачувате измењену фотографију?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Није могуће учитати слику"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Није могуће сачувати измењену слику"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Измењена слика је сачувана у <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Желите да одбаците несачуване промене?"</string>
<string name="yes" msgid="5402582493291792293">"Да"</string>
- <string name="no" msgid="5595408018304861875">"Не"</string>
<string name="save" msgid="5516670392524294967">"САЧУВАЈ"</string>
<string name="autofix" msgid="1663414996270538748">"Аутоподешавање"</string>
<string name="crop" msgid="7598378507763334041">"Опсецање"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Превуците означиваче да бисте опсекли фотографију"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Превуците слику на дудл логотип"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Превуците фотографију да бисте је окренули"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Додирните да бисте уклонили ефекат црвених очију"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Додирните црвене очи да бисте их поправили"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Превуците фотографију да бисте је ротирали"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Превуците фотографију да бисте је исправили"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 341096a..ad14c08 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"Потврди"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Додирните неко лице за почетак."</string>
<string name="saving_image" msgid="7270334453636349407">"Чување слике…"</string>
<string name="save_error" msgid="6857408774183654970">"Није могуће сачувати опсечену слику."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Избор албума"</string>
<string name="select_group" msgid="6744208543323307114">"Избор групе"</string>
<string name="set_image" msgid="2331476809308010401">"Постављање слике као"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Подешавање позадине"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Бришете изабрану ставку?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Бришете изабране ставке?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Потврди"</string>
<string name="cancel" msgid="3637516880917356226">"Откажи"</string>
<string name="share" msgid="3619042788254195341">"Дели"</string>
<string name="select_all" msgid="3403283025220282175">"Изабери све"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"Обрада захтева за кеширање"</string>
<string name="caching_label" msgid="4521059045896269095">"Кеширање..."</string>
<string name="crop_action" msgid="3427470284074377001">"Опсеци"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Опсечена слика није сачувана."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Опсечена слика је сачувана у директоријуму <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Ниједан албум није доступан."</string>
<string name="empty_album" msgid="4542880442593595494">"Доступно је 0 слика/видео снимака."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Постови"</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/photoeditor_strings.xml b/res/values-sv/photoeditor_strings.xml
index bf82cf5..e1d9a00 100644
--- a/res/values-sv/photoeditor_strings.xml
+++ b/res/values-sv/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Fotostudio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Fotot kan inte läsas in"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Fotot kan inte sparas"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotot sparades i albumet <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Redigerade"</string>
- <string name="save_photo" msgid="3125109368779997862">"Spara redigerat foto?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Det gick inte att läsa in bilden"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Det gick inte att spara redigerad bild"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Den redigerade bilden sparas i <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Vill du ignorera ändringar som inte har sparats?"</string>
<string name="yes" msgid="5402582493291792293">"Ja"</string>
- <string name="no" msgid="5595408018304861875">"Nej"</string>
<string name="save" msgid="5516670392524294967">"SPARA"</string>
<string name="autofix" msgid="1663414996270538748">"Åtgärda automatiskt"</string>
<string name="crop" msgid="7598378507763334041">"Beskär"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Dra markörerna om du vill beskära bilden"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Rita på bilden om du vill"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Tryck och dra bilden om du vill vända den"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Knacka lätt om du vill ta bort röda ögon"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Knacka lätt på röda ögon för att ta bort"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Tryck och dra bilden om du vill rotera den"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Tryck och dra bilden om du vill räta upp den"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 59f8a8f..c3a0efa 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Välj ett album"</string>
<string name="select_group" msgid="6744208543323307114">"Välj en grupp"</string>
<string name="set_image" msgid="2331476809308010401">"Använd bild som"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Välj bakgrund"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Ta bort markerat objekt?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Radera markerade objekt?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Bekräfta"</string>
<string name="cancel" msgid="3637516880917356226">"Avbryt"</string>
<string name="share" msgid="3619042788254195341">"Dela"</string>
<string name="select_all" msgid="3403283025220282175">"Markera alla"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Rotera åt höger"</string>
<string name="no_such_item" msgid="5315144556325243400">"Det gick inte att hitta objektet."</string>
<string name="edit" msgid="1502273844748580847">"Redigera"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Det finns ingen app för att slutföra åtgärden."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Begäran om cachelagring bearbetas"</string>
<string name="caching_label" msgid="4521059045896269095">"Cachelagrar ..."</string>
<string name="crop_action" msgid="3427470284074377001">"Beskär"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Den beskurna bilden sparades inte."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Den beskurna bilden har sparats i <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Det finns inga album."</string>
<string name="empty_album" msgid="4542880442593595494">"Det finns inga bilder/videor."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Inlägg"</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/photoeditor_strings.xml b/res/values-sw/photoeditor_strings.xml
index c48e0d6..d8d1b82 100644
--- a/res/values-sw/photoeditor_strings.xml
+++ b/res/values-sw/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Studio ya Picha"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Picha haiwezi kkupakizwa"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Picha haiwezi kuhifadhiwa"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Picha imehifadhiwa kwenye albamu <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Imehaririwa"</string>
- <string name="save_photo" msgid="3125109368779997862">"Hifadhi picha zilizohaririwa"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Haikuweza kupakia picha"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Haikuweza kuhifadhi picha iliyohaririwa"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Picha iliyohaririwa imehifadhiwa kwa <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Tupa mabadiliko yasiyohifadhiwa?"</string>
<string name="yes" msgid="5402582493291792293">"Ndio"</string>
- <string name="no" msgid="5595408018304861875">"Hakuna"</string>
<string name="save" msgid="5516670392524294967">"HIFADHI"</string>
<string name="autofix" msgid="1663414996270538748">"Weka kiotomatiki"</string>
<string name="crop" msgid="7598378507763334041">"Kata"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Kokota viweka alama ili kupuna picha"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Chora kwenye picha ku doodle"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Kokota picha ili kuiflipu"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Gonja ili kuondoa macho mekundu"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Gonga kwenye macho mekundu ili kuyaondoa"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"kokota picha ili kuizungusha"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Kokota picha ili kuizungusha"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 6058d5c..772ce63 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Chagua albamu"</string>
<string name="select_group" msgid="6744208543323307114">"Chagua kikundi"</string>
<string name="set_image" msgid="2331476809308010401">"Weka picha kama"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Weka mandhari"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Futa kipengee kilichoteuliwa?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Futa vipengee vilivyoteuliwa?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Thibitisha"</string>
<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 +85,9 @@
<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 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,15 +99,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Picha iliyopunguzwa haijahifadhiwa"</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Picha iliyopunguzwa imehifadhiwa kwa <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Hakuna albamu zinazopatikana."</string>
<string name="empty_album" msgid="4542880442593595494">"picha / video zilizopo 0."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Zilizowekwa"</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/photoeditor_strings.xml b/res/values-th/photoeditor_strings.xml
index d39658a..7f819e8 100644
--- a/res/values-th/photoeditor_strings.xml
+++ b/res/values-th/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"สตูดิโอถ่ายภาพ"</string>
- <string name="loading_failure" msgid="5990741970574295950">"ไม่สามารถโหลดภาพถ่าย"</string>
- <string name="saving_failure" msgid="8596600582621364077">"ไม่สามารถบันทึกภาพถ่าย"</string>
- <string name="photo_saved" msgid="3494363661299484657">"ภาพได้ัรับการบันทึกในอัลบั้ม <xliff:g id="ALBUM_NAME">%s</xliff:g> แล้ว"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"แก้ไขแล้ว"</string>
- <string name="save_photo" msgid="3125109368779997862">"บันทึกภาพที่แก้ไขหรือไม่"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"ไม่สามารถโหลดรูปภาพ"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"ไม่สามารถบันทึกรูปภาพที่แก้ไข"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"บันทึกภาพที่แก้ไขไว้ที่ <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"ยกเลิกการเปลี่ยนแปลงที่ยังไม่ได้บันทึก"</string>
<string name="yes" msgid="5402582493291792293">"ใช่"</string>
- <string name="no" msgid="5595408018304861875">"ไม่"</string>
<string name="save" msgid="5516670392524294967">"บันทึก"</string>
<string name="autofix" msgid="1663414996270538748">"แก้ไขอัตโนมัติ"</string>
<string name="crop" msgid="7598378507763334041">"ตัด"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"ลากเครื่องหมายเพื่อครอบตัดภาพ"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"วาดบนภาพเพื่อ doodle"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"ลากภาพเพื่อพลิก"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"แตะเพื่อลบตาแดง"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"แตะที่ตาแดงเพื่อนำออก"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"ลากภาพเพื่อหมุน"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"ลากภาพเพื่อยืดให้ตรง"</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 8ee83a4..2095b9b 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"ตกลง"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"แตะใบหน้าเพื่อเริ่มต้น"</string>
<string name="saving_image" msgid="7270334453636349407">"กำลังบันทึกภาพ..."</string>
<string name="save_error" msgid="6857408774183654970">"ไม่สามารถบันทึกภาพที่ตัดได้"</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"เลือกอัลบั้ม"</string>
<string name="select_group" msgid="6744208543323307114">"เลือกกลุ่ม"</string>
<string name="set_image" msgid="2331476809308010401">"ตั้งค่าภาพเป็น"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"ตั้งค่าวอลเปเปอร์"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"ลบรายการที่เลือกหรือไม่"</item>
+ <item quantity="other" msgid="5874316486520635333">"ลบรายการที่เลือกหรือไม่"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"ยืนยัน"</string>
<string name="cancel" msgid="3637516880917356226">"ยกเลิก"</string>
<string name="share" msgid="3619042788254195341">"แบ่งปัน"</string>
<string name="select_all" msgid="3403283025220282175">"เลือกทั้งหมด"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"กำลังประมวลผลคำขอให้แคช"</string>
<string name="caching_label" msgid="4521059045896269095">"กำลังแคช..."</string>
<string name="crop_action" msgid="3427470284074377001">"ตัด"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"ภาพที่ตัดไม่ได้รับการบันทึก"</string>
+ <string name="crop_saved" msgid="1595985909779105158">"บันทึกภาพที่ครอบตัดไว้ที่ <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
<string name="no_albums_alert" msgid="4111744447491690896">"ไม่มีอัลบั้ม"</string>
<string name="empty_album" msgid="4542880442593595494">"O ภาพ/วิดีโอ"</string>
<string name="picasa_posts" msgid="1497721615718760613">"โพสต์"</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/photoeditor_strings.xml b/res/values-tl/photoeditor_strings.xml
index fa547b5..4675685 100644
--- a/res/values-tl/photoeditor_strings.xml
+++ b/res/values-tl/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Hindi ma-load ang larawan"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Hindi ma-save ang larawan"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Na-save ang larawan sa album na <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Na-edit"</string>
- <string name="save_photo" msgid="3125109368779997862">"I-save ang na-edit na larawan?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Hindi mai-load ang larawan"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Hindi mai-save ang na-edit na larawan"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Nai-save ang na-edit na larawan sa <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"I-discard ang di naka-save na pagbabago?"</string>
<string name="yes" msgid="5402582493291792293">"Oo"</string>
- <string name="no" msgid="5595408018304861875">"Hindi"</string>
<string name="save" msgid="5516670392524294967">"I-SAVE"</string>
<string name="autofix" msgid="1663414996270538748">"Auto-fix"</string>
<string name="crop" msgid="7598378507763334041">"Pag-crop"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"I-drag ang mga marker upang i-crop ang larawan"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Gumuhit sa larawan upang mag-doodle"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"I-drag ang larawan upang i-flip ito"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Tapikin upang alisin ang mga pulang mata"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Pindutin ang mga red eye upang alisin ang mga ito"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"I-drag ang larawan upang i-rotate ito"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"I-drag ang larawan upang iunat ito"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index f52540b..d376a02 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Pumili ng album"</string>
<string name="select_group" msgid="6744208543323307114">"Pumili ng pangkat"</string>
<string name="set_image" msgid="2331476809308010401">"Itakda ang larawan bilang"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Itakda ang wallpaper"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Tanggalin ang napiling item?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Tanggalin mga napiling item?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Kumpirmahin"</string>
<string name="cancel" msgid="3637516880917356226">"Kanselahin"</string>
<string name="share" msgid="3619042788254195341">"Ibahagi"</string>
<string name="select_all" msgid="3403283025220282175">"Piliin lahat"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"I-rotate pakanan"</string>
<string name="no_such_item" msgid="5315144556325243400">"Hindi mahanap ang item."</string>
<string name="edit" msgid="1502273844748580847">"I-edit"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Walang app available para makumpleto ang pagkilos."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Pinoproseso ang mga kahilingan sa pag-cache"</string>
<string name="caching_label" msgid="4521059045896269095">"Nagka-cache…"</string>
<string name="crop_action" msgid="3427470284074377001">"I-crop"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Hindi nai-save ang na-crop na larawan."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Nai-save ang na-crop na larawan sa <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Walang available na mga album."</string>
<string name="empty_album" msgid="4542880442593595494">"O na larawan/video ang available."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Mga Post"</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..e3dac66 100644
--- a/res/values-tr/photoeditor_strings.xml
+++ b/res/values-tr/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Fotoğraf Stüdyosu"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Fotoğraf yüklenemiyor"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Fotoğraf kaydedilemiyor"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Fotoğraf <xliff:g id="ALBUM_NAME">%s</xliff:g> albümüne kaydedildi"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Düzenlendi"</string>
- <string name="save_photo" msgid="3125109368779997862">"Düzenlenmiş fotoğraf kaydedilsin mi?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Fotoğraf yüklenemedi"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Düzenlenen fotoğraf kaydedilemedi"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Düzenlenen fotoğraf <xliff:g id="FOLDER_NAME">%s</xliff:g> içine kaydedildi"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Kaydedilmeyen değişiklikler silinsin mi?"</string>
<string name="yes" msgid="5402582493291792293">"Evet"</string>
- <string name="no" msgid="5595408018304861875">"Hayır"</string>
<string name="save" msgid="5516670392524294967">"KAYDET"</string>
<string name="autofix" msgid="1663414996270538748">"Otomatik düzelt"</string>
<string name="crop" msgid="7598378507763334041">"Kırp"</string>
@@ -48,14 +46,14 @@
<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>
<string name="crop_tooltip" msgid="8794037869706891710">"Fotoğrafı kırpmak içni işaretçileri sürükleyin"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Doodle için fotoğrafın üzerinde çizin"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Çevirmek için fotoğrafı sürükleyin"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Kırmızı gözleri kaldırmak için hafifçe vurun"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Kırmızı gözleri hafifçe vurarak düzeltin"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Döndürmek için fotoğrafı sürükleyin"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Düzleştirmek için fotoğrafı sürükleyin"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index da4d8ff..190dc46 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Albüm seçin"</string>
<string name="select_group" msgid="6744208543323307114">"Grup seçin"</string>
<string name="set_image" msgid="2331476809308010401">"Resmi şu şekilde ayarla:"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Duvar kağıdını ayarla"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Seçilen öğe silinsin mi?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Seçilen öğeler silinsin mi?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Onayla"</string>
<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>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Sağa döndür"</string>
<string name="no_such_item" msgid="5315144556325243400">"Öğe bulunamadı."</string>
<string name="edit" msgid="1502273844748580847">"Düzenle"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"İşlemi tamamlayacak hiç uygulama yok."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Önbelleğe alma istekleri işleniyor"</string>
<string name="caching_label" msgid="4521059045896269095">"Önbelleğe alınıyor..."</string>
<string name="crop_action" msgid="3427470284074377001">"Kırp"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Kırpılmış resim kaydedilmedi."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Kırpılan resim <xliff:g id="FOLDER_NAME">%s</xliff:g> klasörüne kaydedildi."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Kullanılabilir albüm yok."</string>
<string name="empty_album" msgid="4542880442593595494">"Kullanılabilir resim/video yok."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Yayınlar"</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/photoeditor_strings.xml b/res/values-uk/photoeditor_strings.xml
index 76bb7ce..67707d5 100644
--- a/res/values-uk/photoeditor_strings.xml
+++ b/res/values-uk/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Фото неможливо завантажити"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Фото неможливо зберегти"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Фото збережено в альбом \"<xliff:g id="ALBUM_NAME">%s</xliff:g>\""</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Редаговано"</string>
- <string name="save_photo" msgid="3125109368779997862">"Зберегти відредаговану фотографію?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Не вдалося завантажити фотографію"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Не вдалося зберегти відредаговане фото"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Відредаговане фото збережено в папці <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Відхилити незбережені зміни?"</string>
<string name="yes" msgid="5402582493291792293">"Так"</string>
- <string name="no" msgid="5595408018304861875">"Ні"</string>
<string name="save" msgid="5516670392524294967">"ЗБЕРЕГТИ"</string>
<string name="autofix" msgid="1663414996270538748">"Автовиправлення"</string>
<string name="crop" msgid="7598378507763334041">"Обрізка"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Перетягніть маркери, щоб обрізати фото"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Малюйте на фото, щоб створити doodle"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Потягніть за фото, щоб перевернути його"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Натисніть, щоб усунути ефект червоних очей"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Торкн. червоних очей, щоб усунути ефект"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Потягніть за фото, щоб обертати його"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Перетягніть фото, щоб вирівняти його"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index de76648..1626d46 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"ОК"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"Торкніться обличчя, щоб почати."</string>
<string name="saving_image" msgid="7270334453636349407">"Зберіг-ня фото…"</string>
<string name="save_error" msgid="6857408774183654970">"Не вдалося зберегти обрізане зображення."</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Виберіть альбом"</string>
<string name="select_group" msgid="6744208543323307114">"Виберіть групу"</string>
<string name="set_image" msgid="2331476809308010401">"Устан. фото як"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Фонове зображення"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Видалити вибраний елемент?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Видалити вибрані елементи?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Підтвердити"</string>
<string name="cancel" msgid="3637516880917356226">"Скасувати"</string>
<string name="share" msgid="3619042788254195341">"Надісл."</string>
<string name="select_all" msgid="3403283025220282175">"Вибрати всі"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"Виконується обробка запитів кешування"</string>
<string name="caching_label" msgid="4521059045896269095">"Кешування..."</string>
<string name="crop_action" msgid="3427470284074377001">"Обрізати"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Обрізане зображення не збережено."</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Обрізане зображення збережено в папці <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Немає доступних альбомів."</string>
<string name="empty_album" msgid="4542880442593595494">"Немає доступних зображень або відео."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Публікації"</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/photoeditor_strings.xml b/res/values-vi/photoeditor_strings.xml
index c3f8281..82779c3 100644
--- a/res/values-vi/photoeditor_strings.xml
+++ b/res/values-vi/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Studio ảnh"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Không thể tải ảnh"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Không thể lưu ảnh"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Ảnh đã được lưu vào album <xliff:g id="ALBUM_NAME">%s</xliff:g>"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Đã chỉnh sửa"</string>
- <string name="save_photo" msgid="3125109368779997862">"Lưu ảnh đã chỉnh sửa?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Không thể tải ảnh"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Không thể lưu ảnh đã chỉnh sửa"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Đã lưu ảnh chỉnh sửa vào <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Hủy các thay đổi chưa lưu?"</string>
<string name="yes" msgid="5402582493291792293">"Có"</string>
- <string name="no" msgid="5595408018304861875">"Không"</string>
<string name="save" msgid="5516670392524294967">"LƯU"</string>
<string name="autofix" msgid="1663414996270538748">"T.động sửa"</string>
<string name="crop" msgid="7598378507763334041">"Cắt"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Kéo các điểm đánh dấu để cắt ảnh"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Kéo trên ảnh để vẽ ngoạch ngoạc"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Kéo ảnh để lật"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Chạm để loại bỏ mắt đỏ"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Chạm vào mắt đỏ để xóa hiệu ứng mắt đỏ"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Kéo ảnh để xoay"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Kéo ảnh để sắp thẳng"</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 1cbf0bd..72457c6 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<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="set_wallpaper" msgid="8491121226190175017">"Đặt hình nền"</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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Xóa mục đã chọn?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Xóa các mục đã chọn?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Xác nhận"</string>
<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>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Xoay phải"</string>
<string name="no_such_item" msgid="5315144556325243400">"Không thể tìm thấy mục."</string>
<string name="edit" msgid="1502273844748580847">"Chỉnh sửa"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Không có ứng dụng nào để hoàn tất tác vụ."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Đang xử lý yêu cầu lưu vào bộ nhớ cache"</string>
<string name="caching_label" msgid="4521059045896269095">"Lưu cache..."</string>
<string name="crop_action" msgid="3427470284074377001">"Cắt"</string>
@@ -90,19 +96,17 @@
<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 album 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 album. 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="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="crop_saved" msgid="1595985909779105158">"Đã lưu hình ảnh được cắt vào <xliff:g id="FOLDER_NAME">%s</xliff:g>."</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>
+ <string name="make_available_offline" msgid="5157950985488297112">"Cho phép ngoại tuyến"</string>
<string name="sync_picasa_albums" msgid="8522572542111169872">"Làm mới"</string>
<string name="done" msgid="217672440064436595">"Xong"</string>
<string name="sequence_in_set" msgid="7235465319919457488">"%1$d/%2$d mục:"</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-xlarge/styles.xml b/res/values-xlarge/styles.xml
index b71e039..0bf4d56 100644
--- a/res/values-xlarge/styles.xml
+++ b/res/values-xlarge/styles.xml
@@ -17,7 +17,7 @@
<style name="Theme.Gallery" parent="android:Theme.Holo">
<item name="android:displayOptions"></item>
<item name="android:actionBarStyle">@style/Holo.ActionBar</item>
- <item name="android:windowBackground">@null</item>
+ <item name="android:windowBackground">@android:color/black</item>
<item name="android:colorBackground">@null</item>
<item name="android:colorBackgroundCacheHint">@null</item>
</style>
diff --git a/res/values-zh-rCN/photoeditor_strings.xml b/res/values-zh-rCN/photoeditor_strings.xml
index 09ab11a..123e390 100644
--- a/res/values-zh-rCN/photoeditor_strings.xml
+++ b/res/values-zh-rCN/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"照相馆"</string>
- <string name="loading_failure" msgid="5990741970574295950">"无法加载照片"</string>
- <string name="saving_failure" msgid="8596600582621364077">"无法保存照片"</string>
- <string name="photo_saved" msgid="3494363661299484657">"照片已保存到“<xliff:g id="ALBUM_NAME">%s</xliff:g>”相册"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"已修改"</string>
- <string name="save_photo" msgid="3125109368779997862">"要保存已修改的照片吗?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"无法加载该照片"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"无法保存经过编辑的照片"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"经过编辑的照片已保存至“<xliff:g id="FOLDER_NAME">%s</xliff:g>”"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"要舍弃未保存的更改吗?"</string>
<string name="yes" msgid="5402582493291792293">"是"</string>
- <string name="no" msgid="5595408018304861875">"否"</string>
<string name="save" msgid="5516670392524294967">"保存"</string>
<string name="autofix" msgid="1663414996270538748">"自动修正"</string>
<string name="crop" msgid="7598378507763334041">"修剪"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"拖动标记以裁剪照片"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"在照片上绘制即可涂鸦"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"拖动照片可将其翻转"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"点按可消除红眼"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"点按即可消除红眼"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"拖动照片可将其旋转"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"拖动照片可将其拉直"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index a58e1fe..ba3e913 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"确定"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"触摸一张脸开始裁剪。"</string>
<string name="saving_image" msgid="7270334453636349407">"正在保存照片..."</string>
<string name="save_error" msgid="6857408774183654970">"无法保存经过裁剪的图片。"</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"选择相册"</string>
<string name="select_group" msgid="6744208543323307114">"选择群组"</string>
<string name="set_image" msgid="2331476809308010401">"将照片设置为"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"设置壁纸"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"要删除所选内容吗?"</item>
+ <item quantity="other" msgid="5874316486520635333">"要删除所选内容吗?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"确认"</string>
<string name="cancel" msgid="3637516880917356226">"取消"</string>
<string name="share" msgid="3619042788254195341">"分享"</string>
<string name="select_all" msgid="3403283025220282175">"全选"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"正在处理缓存请求"</string>
<string name="caching_label" msgid="4521059045896269095">"正在缓存..."</string>
<string name="crop_action" msgid="3427470284074377001">"修剪"</string>
@@ -90,17 +96,15 @@
<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="show_images_only" msgid="7263218480867672653">"仅限图片"</string>
<string name="show_videos_only" msgid="3850394623678871697">"仅限视频"</string>
- <string name="show_all" msgid="4780647751652596980">"图片和视频"</string>
- <string name="appwidget_title" msgid="6410561146863700411">"照片库"</string>
- <string name="appwidget_empty_text" msgid="1228925628357366957">"无照片。"</string>
- <string name="crop_saved" msgid="1062612625032731770">"经过裁剪的图片已保存至“下载内容”文件夹。"</string>
- <string name="crop_not_saved" msgid="3400773981839556">"经过裁剪的图片尚未保存。"</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="1595985909779105158">"经过裁剪的图片已保存至“<xliff:g id="FOLDER_NAME">%s</xliff:g>”。"</string>
<string name="no_albums_alert" msgid="4111744447491690896">"没有可用的相册。"</string>
- <string name="empty_album" msgid="4542880442593595494">"没有可用的图片/视频。"</string>
+ <string name="empty_album" msgid="4542880442593595494">"没有图片/视频。"</string>
<string name="picasa_posts" msgid="1497721615718760613">"帖子"</string>
<string name="make_available_offline" msgid="5157950985488297112">"允许离线状态下使用"</string>
<string name="sync_picasa_albums" msgid="8522572542111169872">"刷新"</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/photoeditor_strings.xml b/res/values-zh-rTW/photoeditor_strings.xml
index b2e8fc6..21b7246 100644
--- a/res/values-zh-rTW/photoeditor_strings.xml
+++ b/res/values-zh-rTW/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"相片工作室"</string>
- <string name="loading_failure" msgid="5990741970574295950">"無法載入相片"</string>
- <string name="saving_failure" msgid="8596600582621364077">"無法儲存相片"</string>
- <string name="photo_saved" msgid="3494363661299484657">"相片已儲存至 <xliff:g id="ALBUM_NAME">%s</xliff:g> 相簿中"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"已編輯"</string>
- <string name="save_photo" msgid="3125109368779997862">"要儲存已編輯的相片嗎?"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"無法載入相片"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"無法儲存編輯完成的相片"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"編輯完成的相片已儲存至「<xliff:g id="FOLDER_NAME">%s</xliff:g>」"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"要捨棄尚未儲存的變更?"</string>
<string name="yes" msgid="5402582493291792293">"是"</string>
- <string name="no" msgid="5595408018304861875">"否"</string>
<string name="save" msgid="5516670392524294967">"儲存"</string>
<string name="autofix" msgid="1663414996270538748">"自動修正"</string>
<string name="crop" msgid="7598378507763334041">"裁剪"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"拖曳標記即可裁剪相片"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"在相片上畫圖即可塗鴉"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"拖曳照片即可翻轉"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"輕按即可消除紅眼"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"輕按紅眼即可予以移除"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"拖曳照片即可旋轉"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"拖曳照片即可拉正"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 1a4592f..ac7de4f 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"確定"</string>
<string name="multiface_crop_help" msgid="2554690102655855657">"輕觸臉孔即可開始。"</string>
<string name="saving_image" msgid="7270334453636349407">"正在儲存相片..."</string>
<string name="save_error" msgid="6857408774183654970">"無法儲存裁剪的圖片。"</string>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"選取相簿"</string>
<string name="select_group" msgid="6744208543323307114">"選取群組"</string>
<string name="set_image" msgid="2331476809308010401">"將相片設為"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"設定桌布"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"刪除選取的項目?"</item>
+ <item quantity="other" msgid="5874316486520635333">"刪除選取的項目?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"確認"</string>
<string name="cancel" msgid="3637516880917356226">"取消"</string>
<string name="share" msgid="3619042788254195341">"分享"</string>
<string name="select_all" msgid="3403283025220282175">"全選"</string>
@@ -75,7 +82,6 @@
<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="process_caching_requests" msgid="8722939570307386071">"正在處理快取要求"</string>
<string name="caching_label" msgid="4521059045896269095">"快取中…"</string>
<string name="crop_action" msgid="3427470284074377001">"裁剪"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"未儲存裁剪的圖片。"</string>
+ <string name="crop_saved" msgid="1595985909779105158">"裁剪的圖片已儲存至「<xliff:g id="FOLDER_NAME">%s</xliff:g>」。"</string>
<string name="no_albums_alert" msgid="4111744447491690896">"沒有相簿。"</string>
<string name="empty_album" msgid="4542880442593595494">"O 個可用的圖片/影片。"</string>
<string name="picasa_posts" msgid="1497721615718760613">"訊息中的相片"</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/photoeditor_strings.xml b/res/values-zu/photoeditor_strings.xml
index 98f599f..fad43bf 100644
--- a/res/values-zu/photoeditor_strings.xml
+++ b/res/values-zu/photoeditor_strings.xml
@@ -17,13 +17,11 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="photoeditor_name" msgid="4738587346771969697">"Endawo Yezithombe"</string>
- <string name="loading_failure" msgid="5990741970574295950">"Isithombe asikwazi ukulayishwa"</string>
- <string name="saving_failure" msgid="8596600582621364077">"Isithombe asikwazi ukulondolozwa"</string>
- <string name="photo_saved" msgid="3494363661299484657">"Isithombe silondolozwe kwi <xliff:g id="ALBUM_NAME">%s</xliff:g> albhamu"</string>
- <string name="edited_photo_bucket_name" msgid="3777746536831799890">"Kuhleliwe"</string>
- <string name="save_photo" msgid="3125109368779997862">"Londoloza isithombe esihleliwe"</string>
+ <string name="loading_failure" msgid="7890360277163832130">"Ayikwazanga ukulayisha isithombe"</string>
+ <string name="saving_failure" msgid="8229491575433743974">"Ayikwazanga ukulondoloza isithombe esilungisiwe"</string>
+ <string name="photo_saved" msgid="6163006724627682202">"Isithombe esilungisiwe silondolozwe e <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
+ <string name="discard_unsaved_photo" msgid="2353274490633681946">"Lahla izinguquko ezingagciniwe?"</string>
<string name="yes" msgid="5402582493291792293">"Yebo"</string>
- <string name="no" msgid="5595408018304861875">"Cha"</string>
<string name="save" msgid="5516670392524294967">"Londoloza"</string>
<string name="autofix" msgid="1663414996270538748">"Insiza Yokulungisa Okuzenzekelayo"</string>
<string name="crop" msgid="7598378507763334041">"Nqampuna"</string>
@@ -55,7 +53,7 @@
<string name="crop_tooltip" msgid="8794037869706891710">"Hudula insiza yokubeka uphawu ukuze usike ongakudingi esithombeni"</string>
<string name="doodle_tooltip" msgid="2902117272374362915">"Dweba esithombeni ukuze kube nemidwebo eshintshayo"</string>
<string name="flip_tooltip" msgid="2700943256714731737">"Hudula isithombe ukuze usiphendule"</string>
- <string name="redeye_tooltip" msgid="9112774042113471358">"Thepha ukuze ususu iso elibomvu"</string>
+ <string name="redeye_tooltip" msgid="4183695928490874014">"Thinta emehlweni abomvu ukuze uwasuse"</string>
<string name="rotate_tooltip" msgid="7008602969130734229">"Hudula isithombe ukuze usijikeleze"</string>
<string name="straighten_tooltip" msgid="4846317027139212339">"Hudula isithombe ukuze usiqondise"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 4e3a513..7ebfa3a 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -29,9 +29,11 @@
<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="ok" msgid="5296833083983263293">"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>
@@ -42,10 +44,15 @@
<string name="select_album" msgid="1557063764849434077">"Khetha i-albhamu"</string>
<string name="select_group" msgid="6744208543323307114">"Khetha iqembu"</string>
<string name="set_image" msgid="2331476809308010401">"Hlela isithombe njenge"</string>
+ <string name="set_wallpaper" msgid="8491121226190175017">"Setha isithombe sangemuva"</string>
<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>
+ <plurals name="delete_selection">
+ <item quantity="one" msgid="6453379735401083732">"Sula into ekhethiwe?"</item>
+ <item quantity="other" msgid="5874316486520635333">"Sula izinto ezikhethiwe?"</item>
+ </plurals>
+ <string name="confirm" msgid="8646870096527848520">"Qinisekisa"</string>
<string name="cancel" msgid="3637516880917356226">"Khansela"</string>
<string name="share" msgid="3619042788254195341">"Yabelana"</string>
<string name="select_all" msgid="3403283025220282175">"Khetha konke"</string>
@@ -75,7 +82,6 @@
<string name="rotate_right" msgid="6776325835923384839">"Phendukisela kwesokudla"</string>
<string name="no_such_item" msgid="5315144556325243400">"Yehlulekile ukuthola into yakho."</string>
<string name="edit" msgid="1502273844748580847">"Hlela"</string>
- <string name="activity_not_found" msgid="5619154886080878023">"Azikho izinhlelo zokusebenza ezitholakalayo ukuze kuqedelwe okwenziwayo."</string>
<string name="process_caching_requests" msgid="8722939570307386071">"Ilungisa izicelo zokufaka kwinqolobane"</string>
<string name="caching_label" msgid="4521059045896269095">"Ukulondoloza isikhashana..."</string>
<string name="crop_action" msgid="3427470284074377001">"Khropha"</string>
@@ -90,15 +96,13 @@
<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="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>
- <string name="crop_not_saved" msgid="3400773981839556">"Umfanekiso onqampuliwe awulondoloziwe"</string>
+ <string name="crop_saved" msgid="1595985909779105158">"Umfanekiso onqampuniwe ulondolozelwe e <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
<string name="no_albums_alert" msgid="4111744447491690896">"Awekho ama-albhamu atholalakalayo."</string>
<string name="empty_album" msgid="4542880442593595494">"O izithombe/amavidiyo okutholakalayo."</string>
<string name="picasa_posts" msgid="1497721615718760613">"Okuposiwe"</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_strings.xml b/res/values/photoeditor_strings.xml
index b282a45..f53e5a6 100644
--- a/res/values/photoeditor_strings.xml
+++ b/res/values/photoeditor_strings.xml
@@ -19,26 +19,20 @@
<string name="photoeditor_name">Photo Studio</string>
<!-- Toast shown when selected photo could not be loaded. [CHAR LIMIT=40] -->
- <string name="loading_failure">Photo cannot be loaded</string>
+ <string name="loading_failure">Couldn\'t load the photo</string>
- <!-- Toast shown when edited photo could not be saved. [CHAR LIMIT=40] -->
- <string name="saving_failure">Photo cannot be saved</string>
+ <!-- Toast shown when edited photo could not be saved. [CHAR LIMIT=48] -->
+ <string name="saving_failure">Couldn\'t save edited photo</string>
- <!-- Toast shown when edited photo is successfully saved under %s album [CHAR LIMIT=40] -->
- <string name="photo_saved">Photo saved to <xliff:g id="album_name">%s</xliff:g> album</string>
+ <!-- Toast shown when edited photo is successfully saved under %s folder [CHAR LIMIT=40] -->
+ <string name="photo_saved">Edited photo saved to <xliff:g id="folder_name">%s</xliff:g></string>
- <!-- Album name where edited photo will be saved in the storage. [CHAR LIMIT=12] -->
- <string name="edited_photo_bucket_name">Edited</string>
-
- <!-- Dialog message prompted when the user is abandoning unsaved changes. [CHAR LIMIT=40] -->
- <string name="save_photo">Save edited photo?</string>
+ <!-- Dialog message prompted to confirm the user is abandoning unsaved changes. [CHAR LIMIT=40] -->
+ <string name="discard_unsaved_photo">Discard unsaved changes?</string>
<!-- Dialog yes button for the user to accept text presented in a dialog. [CHAR LIMIT=12] -->
<string name="yes">Yes</string>
- <!-- Dialog no button for the user to decline text presented in a dialog. [CHAR LIMIT=12] -->
- <string name="no">No</string>
-
<!-- Text button in the action bar for the user to save edited photo. [CHAR LIMIT=8] -->
<string name="save">SAVE</string>
@@ -133,7 +127,7 @@
<string name="flip_tooltip">Drag photo to flip it</string>
<!-- Tool-tip toast shown when the user chooses to remove red eyes. [CHAR LIMIT=40] -->
- <string name="redeye_tooltip">Tap to remove red eyes</string>
+ <string name="redeye_tooltip">Tap on red eyes to remove them</string>
<!-- Tool-tip toast shown when the user chooses to rotate photo. [CHAR LIMIT=40] -->
<string name="rotate_tooltip">Drag photo to rotate it</string>
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..529480c 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,10 @@
<!-- 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>
+ <!-- Title of a menu item to indicate performing the image crop operation
+ [CHAR LIMIT=20] -->
+ <string name="ok">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>
@@ -84,13 +91,20 @@
<!-- Displayed in the title of the dialog for things to do with a picture
that is to be "set as" (e.g. set as contact photo or set as wallpaper) -->
<string name="set_image">Set picture as</string>
+ <!-- Activity title for cropping picture and setting it as wallpaper [CHAR LIMIT=20] -->
+ <string name="set_wallpaper">Set wallpaper</string>
<!-- Toast/alert after saving wallpaper -->
<string name="wallpaper">Setting wallpaper\u2026</string>
<string name="camera_setas_wallpaper">Wallpaper</string>
<!-- Details dialog "OK" button. Dismisses dialog. -->
<string name="delete">Delete</string>
- <string name="confirm_delete">Delete</string>
+ <!-- String Delete the selected media item(s) [CHAR LIMIT=50] -->
+ <plurals name="delete_selection">
+ <item quantity="one">Delete selected item?</item>
+ <item quantity="other">Delete selected items?</item>
+ </plurals>
+ <string name="confirm">Confirm</string>
<string name="cancel">Cancel</string>
<string name="share">Share</string>
@@ -142,10 +156,6 @@
[CHAR_LIMIT=20]-->
<string name="edit">Edit</string>
- <!-- String used in a toast message indicating there is no application
- available to handle a request [CHAR LIMIT=50] -->
- <string name="activity_not_found">No app is available to complete the action.</string>
-
<!-- String used as a title of a progress dialog. The user can
choose to cache some Picasa picture albums on device, so it can
be viewed offline. This string is shown when the request is being
@@ -200,9 +210,7 @@
<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>
- <!-- 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_error">Couldn\'t download the photos in this album. 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 +224,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>
@@ -225,12 +233,9 @@
<string name="appwidget_empty_text">No photos.</string>
<!-- Toast message shown when the cropped image has been saved in the
- download folder [CHAR LIMIT=50]-->
- <string name="crop_saved">Cropped image saved to Downloads.</string>
-
- <!-- Toast message shown when the cropped image is not saved
- [CHAR LIMIT=50]-->
- <string name="crop_not_saved">Cropped image wasn\'t saved.</string>
+ %s folder (string: folder_download) [CHAR LIMIT=50]-->
+ <string name="crop_saved">
+ Cropped image saved to <xliff:g id="folder_name">%s</xliff:g>.</string>
<!-- Toast message shown when there is no albums available [CHAR LIMIT=50]-->
<string name="no_albums_alert">No albums available.</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="help_url_gallery_main" translatable="false"></string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index b26c728..3c32cef 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -17,14 +17,14 @@
<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:windowBackground">@android:color/black</item>
<item name="android:colorBackground">@null</item>
<item name="android:colorBackgroundCacheHint">@null</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 9500fff..144485d 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,26 @@
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 OrientationManager mOrientationManager;
+ private TransitionStore mTransitionStore = new TransitionStore();
+ private boolean mDisableToggleStatusBar;
private AlertDialog mAlertDialog = null;
private BroadcastReceiver mMountReceiver = new BroadcastReceiver() {
@@ -54,6 +58,14 @@
private IntentFilter mMountFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
@Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mOrientationManager = new OrientationManager(this);
+ toggleStatusBarByOrientation();
+ getWindow().setBackgroundDrawable(null);
+ }
+
+ @Override
protected void onSaveInstanceState(Bundle outState) {
mGLRootView.lockRenderThread();
try {
@@ -69,16 +81,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 +96,6 @@
return ((GalleryApp) getApplication()).getThreadPool();
}
- public GalleryApp getGalleryApplication() {
- return (GalleryApp) getApplication();
- }
-
public synchronized StateManager getStateManager() {
if (mStateManager == null) {
mStateManager = new StateManager(this);
@@ -102,8 +107,8 @@
return mGLRootView;
}
- public PositionRepository getPositionRepository() {
- return mPositionRepository;
+ public OrientationManager getOrientationManager() {
+ return mOrientationManager;
}
@Override
@@ -112,11 +117,6 @@
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();
@@ -173,11 +173,13 @@
mGLRootView.unlockRenderThread();
}
mGLRootView.onResume();
+ mOrientationManager.resume();
}
@Override
protected void onPause() {
super.onPause();
+ mOrientationManager.pause();
mGLRootView.onPause();
mGLRootView.lockRenderThread();
try {
@@ -186,6 +188,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 +216,54 @@
}
@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);
+ }
+ }
+
+ @Override
+ public TransitionStore getTransitionStore() {
+ return mTransitionStore;
}
}
diff --git a/src/com/android/gallery3d/app/ActivityState.java b/src/com/android/gallery3d/app/ActivityState.java
index 1563a09..867cda3 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,13 @@
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_WHEN_PLUGGED = 4;
+ protected static final int FLAG_SCREEN_ON_ALWAYS = 8;
private static final int SCREEN_ON_FLAGS = (
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
@@ -56,11 +56,11 @@
public int requestCode;
public int resultCode = Activity.RESULT_CANCELED;
public Intent resultData;
- ResultEntry next;
}
private boolean mDestroyed = false;
private boolean mPlugged = false;
+ boolean mIsFinishing = false;
protected ActivityState() {
}
@@ -109,25 +109,26 @@
if (plugged != mPlugged) {
mPlugged = plugged;
- final Window win = ((Activity) mActivity).getWindow();
- final WindowManager.LayoutParams params = win.getAttributes();
- setScreenOnFlags(params);
- win.setAttributes(params);
+ setScreenOnFlags();
}
}
}
};
- void setScreenOnFlags(WindowManager.LayoutParams params) {
- if (mPlugged && 0 != (mFlags & FLAG_SCREEN_ON)) {
+ void setScreenOnFlags() {
+ final Window win = ((Activity) mActivity).getWindow();
+ final WindowManager.LayoutParams params = win.getAttributes();
+ if ((0 != (mFlags & FLAG_SCREEN_ON_ALWAYS)) ||
+ (mPlugged && 0 != (mFlags & FLAG_SCREEN_ON_WHEN_PLUGGED))) {
params.flags |= SCREEN_ON_FLAGS;
} else {
params.flags &= ~SCREEN_ON_FLAGS;
}
+ win.setAttributes(params);
}
protected void onPause() {
- if (0 != (mFlags & FLAG_SCREEN_ON)) {
+ if (0 != (mFlags & FLAG_SCREEN_ON_WHEN_PLUGGED)) {
((Activity) mActivity).unregisterReceiver(mPowerIntentReceiver);
}
}
@@ -143,25 +144,17 @@
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();
- final Window win = activity.getWindow();
- final WindowManager.LayoutParams params = win.getAttributes();
+ setScreenOnFlags();
- if ((mFlags & FLAG_HIDE_STATUS_BAR) != 0) {
- params.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE;
- } else {
- params.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
- }
-
- setScreenOnFlags(params);
- win.setAttributes(params);
+ boolean lightsOut = ((mFlags & FLAG_HIDE_STATUS_BAR) != 0);
+ mActivity.getGLRoot().setLightsOutMode(lightsOut);
ResultEntry entry = mReceivedResults;
if (entry != null) {
@@ -169,13 +162,16 @@
onStateResult(entry.requestCode, entry.resultCode, entry.resultData);
}
- if (0 != (mFlags & FLAG_SCREEN_ON)) {
+ if (0 != (mFlags & FLAG_SCREEN_ON_WHEN_PLUGGED)) {
// we need to know whether the device is plugged in to do this correctly
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
activity.registerReceiver(mPowerIntentReceiver, filter);
}
onResume();
+
+ // the transition store should be cleared after resume;
+ mActivity.getTransitionStore().clear();
}
// a subclass of ActivityState should override the method to resume itself
@@ -199,4 +195,8 @@
boolean isDestroyed() {
return mDestroyed;
}
+
+ public boolean isFinishing() {
+ return mIsFinishing;
+ }
}
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..e773691 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,33 +43,35 @@
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.PhotoFallbackEffect;
+import com.android.gallery3d.ui.RelativePosition;
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;
+import com.android.gallery3d.util.MediaSetUtils;
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";
+ public static final String KEY_RESUME_ANIMATION = "resume_animation";
private static final int REQUEST_SLIDESHOW = 1;
private static final int REQUEST_PHOTO = 2;
@@ -78,16 +83,15 @@
private static final float USER_DISTANCE_METER = 0.3f;
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 +104,65 @@
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 PhotoFallbackEffect mResumeEffect;
+ private PhotoFallbackEffect.PositionProvider mPositionProvider =
+ new PhotoFallbackEffect.PositionProvider() {
+ @Override
+ public Rect getPosition(int index) {
+ Rect rect = mSlotView.getSlotRect(index);
+ Rect bounds = mSlotView.bounds();
+ rect.offset(bounds.left - mSlotView.getScrollX(),
+ bounds.top - mSlotView.getScrollY());
+ return rect;
+ }
+
+ @Override
+ public int getItemIndex(Path path) {
+ int start = mSlotView.getVisibleStart();
+ int end = mSlotView.getVisibleEnd();
+ for (int i = start; i < end; ++i) {
+ MediaItem item = mAlbumDataAdapter.get(i);
+ if (item != null && item.getPath() == path) return i;
+ }
+ return -1;
+ }
+ };
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
@@ -136,10 +170,32 @@
canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
canvas.multiplyMatrix(mMatrix, 0);
super.render(canvas);
+
+ if (mResumeEffect != null) {
+ boolean more = mResumeEffect.draw(canvas);
+ if (!more) {
+ mResumeEffect = null;
+ mAlbumView.setSlotFilter(null);
+ }
+ // We want to render one more time even when no more effect
+ // required. So that the animated thumbnails could be draw
+ // with declarations in super.render().
+ invalidate();
+ }
canvas.restore();
}
};
+ // This are the transitions we want:
+ //
+ // +--------+ +------------+ +-------+ +----------+
+ // | Camera |---------->| Fullscreen |--->| Album |--->| AlbumSet |
+ // | View | thumbnail | Photo | up | Page | up | Page |
+ // +--------+ +------------+ +-------+ +----------+
+ // ^ | | ^ |
+ // | | | | | close
+ // +----------back--------+ +----back----+ +--back-> app
+ //
@Override
protected void onBackPressed() {
if (mShowDetails) {
@@ -147,54 +203,87 @@
} else if (mSelectionManager.inSelectionMode()) {
mSelectionManager.leaveSelectionMode();
} else {
- mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
+ // TODO: fix this regression
+ // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
+ onUpPressed();
+ }
+ }
+
+ private void onUpPressed() {
+ if (mActivity.getStateManager().getStateCount() > 1) {
super.onBackPressed();
+ } else if (mParentMediaSetString != null) {
+ Bundle data = new Bundle(getData());
+ data.putString(AlbumSetPage.KEY_MEDIA_PATH, mParentMediaSetString);
+ mActivity.getStateManager().switchState(
+ this, AlbumSetPage.class, data);
}
}
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 {
+ 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());
mDetailsSource.findIndex(slotIndex);
- mAlbumView.invalidate();
+ 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());
+ 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 +307,15 @@
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());
+ mDetailsSource.findIndex(slotIndex);
+ mSlotView.invalidate();
}
+ @Override
public void doCluster(int clusterType) {
String basePath = mMediaSet.getPath().toString();
String newPath = FilterUtils.newClusterPath(basePath, clusterType);
@@ -242,25 +328,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,60 +344,53 @@
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
protected void onResume() {
super.onResume();
mIsActive = true;
+
+ mResumeEffect = mActivity.getTransitionStore().get(KEY_RESUME_ANIMATION);
+ if (mResumeEffect != null) {
+ mAlbumView.setSlotFilter(mResumeEffect);
+ mResumeEffect.setPositionProvider(mPositionProvider);
+ mResumeEffect.start();
+ }
+
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();
@@ -342,6 +407,9 @@
protected void onPause() {
super.onPause();
mIsActive = false;
+
+ mAlbumView.setSlotFilter(null);
+
mAlbumDataAdapter.pause();
mAlbumView.pause();
DetailsHelper.pause();
@@ -349,6 +417,7 @@
if (mSyncTask != null) {
mSyncTask.cancel();
mSyncTask = null;
+ clearLoadingBit(BIT_LOADING_SYNC);
}
mActionModeHandler.pause();
}
@@ -362,26 +431,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 +465,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 +483,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 +490,14 @@
}
});
}
- mAlbumView.setSelectionDrawer(mHighlightDrawer);
mDetailsHelper.show();
}
private void hideDetails() {
mShowDetails = false;
mDetailsHelper.hide();
- mAlbumView.setSelectionDrawer(mGridDrawer);
- mAlbumView.invalidate();
+ mAlbumView.setHighlightItemPath(null);
+ mSlotView.invalidate();
}
@Override
@@ -459,13 +521,20 @@
menu.findItem(R.id.action_slideshow).setVisible(true);
}
- MenuItem groupBy = menu.findItem(R.id.action_group_by);
FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
+ MenuItem groupBy = menu.findItem(R.id.action_group_by);
if (groupBy != null) {
groupBy.setVisible(mShowClusterMenu);
}
+ MenuItem switchCamera = menu.findItem(R.id.action_camera);
+ if (switchCamera != null) {
+ switchCamera.setVisible(
+ MediaSetUtils.isCameraSource(mMediaSetPath)
+ && GalleryUtils.isCameraAvailable(activity));
+ }
+
actionBar.setTitle(mMediaSet.getName());
}
actionBar.setSubtitle(null);
@@ -476,6 +545,10 @@
@Override
protected boolean onItemSelected(MenuItem item) {
switch (item.getItemId()) {
+ case android.R.id.home: {
+ onUpPressed();
+ return true;
+ }
case R.id.action_cancel:
mActivity.getStateManager().finishState(this);
return true;
@@ -504,6 +577,10 @@
}
return true;
}
+ case R.id.action_camera: {
+ GalleryUtils.startCameraActivity((Activity) mActivity);
+ return true;
+ }
default:
return false;
}
@@ -516,18 +593,17 @@
// 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();
+ mFocusIndex = data.getIntExtra(PhotoPage.KEY_RETURN_INDEX_HINT, 0);
+ mSlotView.makeSlotVisible(mFocusIndex);
break;
}
case REQUEST_DO_ANIMATION: {
- startTransition(null);
+ mSlotView.startRisingAnimation();
break;
}
}
@@ -569,31 +645,33 @@
((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
+ && (mAlbumDataAdapter.size() == 0)) {
+ // show error toast only if the album is empty
+ Toast.makeText((Context) mActivity, R.string.sync_album_error,
+ Toast.LENGTH_LONG).show();
+ }
+ } finally {
+ root.unlockRenderThread();
}
}
});
}
private void setLoadingBit(int loadTaskBit) {
- if (mLoadingBits == 0) {
- GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
- }
mLoadingBits |= loadTaskBit;
}
private void clearLoadingBit(int loadTaskBit) {
mLoadingBits &= ~loadTaskBit;
- if (mLoadingBits == 0) {
- GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
-
+ if (mLoadingBits == 0 && mIsActive) {
if (mAlbumDataAdapter.size() == 0) {
Toast.makeText((Context) mActivity,
R.string.empty_album, Toast.LENGTH_LONG).show();
@@ -610,7 +688,6 @@
@Override
public void onLoadingFinished() {
- if (!mIsActive) return;
clearLoadingBit(BIT_LOADING_RELOAD);
}
}
@@ -643,7 +720,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 83%
rename from src/com/android/gallery3d/app/AlbumSetDataAdapter.java
rename to src/com/android/gallery3d/app/AlbumSetDataLoader.java
index 5318a61..39d4a8b 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,11 +323,13 @@
@Override
public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
boolean updateComplete = false;
while (mActive) {
synchronized (this) {
if (mActive && !mDirty && updateComplete) {
- updateLoading(false);
+ if (!mSource.isLoading()) updateLoading(false);
Utils.waitWithoutInterrupt(this);
continue;
}
@@ -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..aa72eb8 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,21 +42,19 @@
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;
+import com.android.gallery3d.util.HelpUtils;
public class AlbumSetPage extends ActivityState implements
SelectionManager.SelectionListener, GalleryActionBar.ClusterRunner,
@@ -62,6 +62,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 +76,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 +98,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 +115,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 +165,105 @@
} 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());
+ mDetailsSource.findIndex(slotIndex);
+ 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,25 +276,33 @@
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) {
- GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
-
+ if (mLoadingBits == 0 && mIsActive) {
// Only show toast when there's no album and we are going to finish
// the page. Toast is redundant if we are going to stay on this page.
if ((mAlbumSetDataAdapter.size() == 0)) {
- Toast.makeText((Context) mActivity,
- R.string.empty_album, Toast.LENGTH_LONG).show();
if (mActivity.getStateManager().getStateCount() > 1) {
+ Toast.makeText((Context) mActivity,
+ R.string.empty_album, Toast.LENGTH_LONG).show();
mActivity.getStateManager().finishState(this);
}
}
@@ -308,9 +310,6 @@
}
private void setLoadingBit(int loadingBit) {
- if (mLoadingBits == 0) {
- GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
- }
mLoadingBits |= loadingBit;
}
@@ -323,8 +322,10 @@
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;
@@ -345,9 +346,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 +359,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 +368,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 +398,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 +424,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 +435,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 +443,17 @@
}
}
- 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));
}
+ final MenuItem helpMenu = menu.findItem(R.id.action_general_help);
+ HelpUtils.prepareHelpMenuItem(mActivity.getAndroidContext(),
+ helpMenu, R.string.help_url_gallery_main);
- actionBar.setTitle(mTitle);
- actionBar.setSubtitle(mSubtitle);
+ mActionBar.setTitle(mTitle);
+ mActionBar.setSubtitle(mSubtitle);
}
return true;
}
@@ -485,10 +484,7 @@
}
return true;
case R.id.action_camera: {
- Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
- .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- activity.startActivity(intent);
+ GalleryUtils.startCameraActivity(activity);
return true;
}
case R.id.action_manage_offline: {
@@ -516,31 +512,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 +527,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,19 +539,20 @@
case SelectionManager.LEAVE_SELECTION_MODE: {
mActionMode.finish();
if (mShowClusterMenu) {
- mActivity.getGalleryActionBar().showClusterMenu(mSelectedAction, this);
+ mActionBar.enableClusterMenu(mSelectedAction, this);
}
mRootPane.invalidate();
break;
}
case SelectionManager.SELECT_ALL_MODE: {
- mActionModeHandler.setTitle(getSelectedString());
+ mActionModeHandler.updateSupportedOperation();
mRootPane.invalidate();
break;
}
}
}
+ @Override
public void onSelectionChange(Path path, boolean selected) {
Utils.assertTrue(mActionMode != null);
mActionModeHandler.setTitle(getSelectedString());
@@ -582,23 +562,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 +589,51 @@
((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) {
+ Log.w(TAG, "failed to load album set");
+ }
+ } 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 +646,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/AppBridge.java b/src/com/android/gallery3d/app/AppBridge.java
new file mode 100644
index 0000000..5d6b1ec
--- /dev/null
+++ b/src/com/android/gallery3d/app/AppBridge.java
@@ -0,0 +1,67 @@
+/*
+ * 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.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.gallery3d.ui.ScreenNail;
+
+// This is the bridge to connect a PhotoPage to the external environment.
+public abstract class AppBridge implements Parcelable {
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // These are requests sent from PhotoPage to the app
+ //////////////////////////////////////////////////////////////////////////
+
+ public abstract boolean isPanorama();
+ public abstract ScreenNail attachScreenNail();
+ public abstract void detachScreenNail();
+
+ // Return true if the tap is consumed.
+ public abstract boolean onSingleTapUp(int x, int y);
+
+ // This is used to notify that the screen nail will be drawn in full screen
+ // or not in next draw() call.
+ public abstract void onFullScreenChanged(boolean full);
+
+ //////////////////////////////////////////////////////////////////////////
+ // These are requests send from app to PhotoPage
+ //////////////////////////////////////////////////////////////////////////
+
+ public interface Server {
+ // Set the camera frame relative to GLRootView.
+ public void setCameraRelativeFrame(Rect frame);
+ // 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);
+ // Enable or disable the swiping gestures (the default is enabled).
+ public void setSwipingEnabled(boolean enabled);
+ // Notify that the ScreenNail is changed.
+ public void notifyScreenNailChanged();
+ }
+
+ // If server is null, the services are not available.
+ public abstract void setServer(Server server);
+}
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..4f450d8 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;
@@ -57,6 +58,7 @@
import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.ui.TileImageViewAdapter;
+import com.android.gallery3d.util.BucketNames;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.FutureListener;
import com.android.gallery3d.util.GalleryUtils;
@@ -119,9 +121,8 @@
private static final int STATE_LOADED = 1;
private static final int STATE_SAVING = 2;
- public static final String DOWNLOAD_STRING = "download";
public static final File DOWNLOAD_BUCKET = new File(
- Environment.getExternalStorageDirectory(), DOWNLOAD_STRING);
+ Environment.getExternalStorageDirectory(), BucketNames.DOWNLOAD);
public static final String CROP_ACTION = "com.android.camera.action.CROP";
@@ -162,8 +163,12 @@
getGLRoot().setContentPane(mCropView);
ActionBar actionBar = getActionBar();
- actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP,
- ActionBar.DISPLAY_HOME_AS_UP);
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE,
+ ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE);
+ Bundle extra = getIntent().getExtras();
+ if (extra != null && extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) {
+ actionBar.setTitle(R.string.set_wallpaper);
+ }
mMainHandler = new SynchronizedHandler(getGLRoot()) {
@Override
@@ -232,6 +237,11 @@
return true;
}
+ @Override
+ public void onBackPressed() {
+ finish();
+ }
+
private class SaveOutput implements Job<Intent> {
private final RectF mCropRect;
@@ -554,8 +564,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);
@@ -603,8 +612,14 @@
int sample = BitmapUtils.computeSampleSizeLarger(
Math.max(scaleX, scaleY));
options.inSampleSize = sample;
+
+ // The decoding result is what we want if
+ // 1. The size of the decoded bitmap match the destination's size
+ // 2. The destination covers the whole output bitmap
+ // 3. No rotation
if ((rect.width() / sample) == dest.width()
&& (rect.height() / sample) == dest.height()
+ && (outputX == dest.width()) && (outputY == dest.height())
&& rotation == 0) {
// To prevent concurrent access in GLThread
synchronized (mRegionDecoder) {
@@ -708,7 +723,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 +749,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;
}
@@ -887,13 +902,9 @@
private MediaItem getMediaItemFromIntentData() {
Uri uri = getIntent().getData();
DataManager manager = getDataManager();
- if (uri == null) {
- Log.w(TAG, "no data given");
- return null;
- }
- Path path = manager.findPathByUri(uri);
+ Path path = manager.findPathByUri(uri, getIntent().getType());
if (path == null) {
- Log.w(TAG, "cannot get path for: " + uri);
+ Log.w(TAG, "cannot get path for: " + uri + ", or no data given");
return null;
}
return (MediaItem) manager.getMediaObject(path);
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..468d202 100644
--- a/src/com/android/gallery3d/app/Gallery.java
+++ b/src/com/android/gallery3d/app/Gallery.java
@@ -17,14 +17,16 @@
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;
import android.widget.Toast;
@@ -40,6 +42,7 @@
public final class Gallery extends AbstractGalleryActivity implements OnCancelListener {
public static final String EXTRA_SLIDESHOW = "slideshow";
+ public static final String EXTRA_DREAM = "dream";
public static final String EXTRA_CROP = "crop";
public static final String ACTION_REVIEW = "com.android.camera.action.REVIEW";
@@ -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
@@ -57,10 +59,8 @@
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_ACTION_BAR);
requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);
- mActionBar = new GalleryActionBar(this);
if (savedInstanceState != null) {
getStateManager().restoreFromState(savedInstanceState);
@@ -115,7 +115,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,11 +133,10 @@
private void startViewAction(Intent intent) {
Boolean slideshow = intent.getBooleanExtra(EXTRA_SLIDESHOW, false);
- getStateManager().setLaunchGalleryOnTop(true);
if (slideshow) {
getActionBar().hide();
DataManager manager = getDataManager();
- Path path = manager.findPathByUri(intent.getData());
+ Path path = manager.findPathByUri(intent.getData(), intent.getType());
if (path == null || manager.getMediaObject(path)
instanceof MediaItem) {
path = Path.fromString(
@@ -148,6 +146,9 @@
data.putString(SlideshowPage.KEY_SET_PATH, path.toString());
data.putBoolean(SlideshowPage.KEY_RANDOM_ORDER, true);
data.putBoolean(SlideshowPage.KEY_REPEAT, true);
+ if (intent.getBooleanExtra(EXTRA_DREAM, false)) {
+ data.putBoolean(SlideshowPage.KEY_DREAM, true);
+ }
getStateManager().startState(SlideshowPage.class, data);
} else {
Bundle data = new Bundle();
@@ -165,7 +166,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)) {
@@ -175,7 +175,7 @@
KEY_MEDIA_TYPES, String.valueOf(mediaType))
.build();
}
- Path setPath = dm.findPathByUri(uri);
+ Path setPath = dm.findPathByUri(uri, null);
MediaSet mediaSet = null;
if (setPath != null) {
mediaSet = (MediaSet) dm.getMediaObject(setPath);
@@ -183,6 +183,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());
@@ -192,15 +194,38 @@
startDefaultPage();
}
} else {
- Path itemPath = dm.findPathByUri(uri);
+ Path itemPath = dm.findPathByUri(uri, intent.getType());
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());
+ if (intent.getBooleanExtra(PhotoPage.KEY_TREAT_BACK_AS_UP, false)) {
+ data.putBoolean(PhotoPage.KEY_TREAT_BACK_AS_UP, true);
+ }
+
+ // 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 +238,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 +255,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..5c61648 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) {
@@ -200,11 +208,28 @@
new AlertDialog.Builder(mContext).setTitle(R.string.group_by).setItems(
mTitles, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
- clusterRunner.doCluster(actions.get(which).intValue());
+ // Need to lock rendering when operations invoked by system UI (main thread) are
+ // modifying slot data used in GL thread for rendering.
+ mActivity.getGLRoot().lockRenderThread();
+ try {
+ clusterRunner.doCluster(actions.get(which).intValue());
+ } finally {
+ mActivity.getGLRoot().unlockRenderThread();
+ }
}
}).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 +242,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) {
@@ -240,6 +275,8 @@
@Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
if (itemPosition != mCurrentIndex && mClusterRunner != null) {
+ // Need to lock rendering when operations invoked by system UI (main thread) are
+ // modifying slot data used in GL thread for rendering.
mActivity.getGLRoot().lockRenderThread();
try {
mClusterRunner.doCluster(sClusterItems[itemPosition].action);
diff --git a/src/com/android/gallery3d/app/GalleryActivity.java b/src/com/android/gallery3d/app/GalleryActivity.java
index 02f2f72..33c77fb 100644
--- a/src/com/android/gallery3d/app/GalleryActivity.java
+++ b/src/com/android/gallery3d/app/GalleryActivity.java
@@ -17,12 +17,11 @@
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();
+ public OrientationManager getOrientationManager();
+ public TransitionStore getTransitionStore();
}
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..cdbbfdc 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,36 +303,39 @@
ManageCachePage.this.onSingleTapUp(slotIndex);
}
});
- mRootPane.addComponent(mAlbumSetView);
+ mRootPane.addComponent(mSlotView);
initializeFooterViews();
}
private void initializeFooterViews() {
Activity activity = (Activity) mActivity;
- FrameLayout footer = (FrameLayout) activity.findViewById(R.id.footer);
LayoutInflater inflater = activity.getLayoutInflater();
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 +382,28 @@
}
}
+ @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) {
}
+ @Override
+ public void onConfirmDialogDismissed(boolean confirmed) {
+ }
+
+ @Override
+ public void onConfirmDialogShown() {
+ }
}
diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java
index 099e9f5..78fe1ee 100644
--- a/src/com/android/gallery3d/app/MovieActivity.java
+++ b/src/com/android/gallery3d/app/MovieActivity.java
@@ -18,14 +18,18 @@
import android.app.ActionBar;
import android.app.Activity;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
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,17 +39,25 @@
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";
+ public static final String KEY_LOGO_BITMAP = "logo-bitmap";
+ public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
private MoviePlayer mPlayer;
private boolean mFinishOnCompletion;
private Uri mUri;
+ private boolean mTreatUpAsBack;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -55,11 +67,15 @@
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);
+ rootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
Intent intent = getIntent();
initializeActionBar(intent);
mFinishOnCompletion = intent.getBooleanExtra(
MediaStore.EXTRA_FINISH_ON_COMPLETION, true);
+ mTreatUpAsBack = intent.getBooleanExtra(KEY_TREAT_UP_AS_BACK, false);
mPlayer = new MoviePlayer(rootView, this, intent.getData(), savedInstanceState,
!mFinishOnCompletion) {
@Override
@@ -82,29 +98,51 @@
winParams.buttonBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;
winParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
win.setAttributes(winParams);
+
+ // We set the background in the theme to have the launching animation.
+ // But for the performance (and battery), we remove the background here.
+ win.setBackgroundDrawable(null);
}
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
@@ -114,7 +152,10 @@
getMenuInflater().inflate(R.menu.movie, menu);
ShareActionProvider provider = GalleryActionBar.initializeShareActionProvider(menu);
- if (provider != null) {
+ // Document says EXTRA_STREAM should be a content: Uri
+ // So, we only share the video if it's "content:".
+ if (provider != null && ContentResolver.SCHEME_CONTENT
+ .equals(mUri.getScheme())) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("video/*");
intent.putExtra(Intent.EXTRA_STREAM, mUri);
@@ -127,7 +168,12 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
- finish();
+ if (mTreatUpAsBack) {
+ finish();
+ } else {
+ startActivity(new Intent(this, Gallery.class));
+ finish();
+ }
return true;
}
return false;
diff --git a/src/com/android/gallery3d/app/MovieControllerOverlay.java b/src/com/android/gallery3d/app/MovieControllerOverlay.java
index 752213e..f2c9e05 100644
--- a/src/com/android/gallery3d/app/MovieControllerOverlay.java
+++ b/src/com/android/gallery3d/app/MovieControllerOverlay.java
@@ -16,14 +16,11 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.R;
-
import android.content.Context;
+import android.graphics.Rect;
import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
@@ -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.
*/
@@ -88,8 +86,6 @@
LayoutParams matchParent =
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- LayoutInflater inflater = LayoutInflater.from(context);
-
background = new View(context);
background.setBackgroundColor(context.getResources().getColor(R.color.darker_transparent));
addView(background, matchParent);
@@ -103,6 +99,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 +113,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 +132,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 +175,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);
}
@@ -228,6 +228,7 @@
}
private void startHiding() {
+ startHideAnimation(background);
startHideAnimation(timeBar);
startHideAnimation(playPauseReplayView);
}
@@ -303,39 +304,54 @@
return true;
}
+ // The paddings of 4 sides which covered by system components. E.g.
+ // +-----------------+\
+ // | Action Bar | insets.top
+ // +-----------------+/
+ // | |
+ // | Content Area | insets.right = insets.left = 0
+ // | |
+ // +-----------------+\
+ // | Navigation Bar | insets.bottom
+ // +-----------------+/
+ // Please see View.fitSystemWindows() for more details.
+ private final Rect mWindowInsets = new Rect();
+
@Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int bw;
- int bh;
- int y;
- int h = b - t;
- int w = r - l;
+ protected boolean fitSystemWindows(Rect insets) {
+ // We don't set the paddings of this View, otherwise,
+ // the content will get cropped outside window
+ mWindowInsets.set(insets);
+ return true;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ Rect insets = mWindowInsets;
+ int pl = insets.left; // the left paddings
+ int pr = insets.right;
+ int pt = insets.top;
+ int pb = insets.bottom;
+
+ int h = bottom - top;
+ int w = right - left;
boolean error = errorView.getVisibility() == View.VISIBLE;
- bw = timeBar.getBarHeight();
- bh = bw;
- y = b - bh;
+ int y = h - pb;
+ // Put both TimeBar and Background just above the bottom system component.
+ // But extend the background to the width of the screen, since we don't
+ // care if it will be covered by a system component and it looks better.
+ background.layout(0, y - timeBar.getBarHeight(), w, y);
+ timeBar.layout(pl, y - timeBar.getPreferredHeight(), w - pr, y);
- background.layout(l, y, r, b);
-
- timeBar.layout(l, b - timeBar.getPreferredHeight(), r, b);
// Needed, otherwise the framework will not re-layout in case only the padding is changed
timeBar.requestLayout();
- // play pause / next / previous buttons
- int cx = l + w / 2; // center x
- int playbackButtonsCenterline = t + h / 2;
- bw = playPauseReplayView.getMeasuredWidth();
- bh = playPauseReplayView.getMeasuredHeight();
- playPauseReplayView.layout(
- 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);
+ // Put the play/pause/next/ previous button in the center of the screen
+ layoutCenteredView(playPauseReplayView, 0, 0, w, h);
if (mainView != null) {
- layoutCenteredView(mainView, l, t, r, b);
+ layoutCenteredView(mainView, 0, 0, w, h);
}
}
@@ -386,5 +402,4 @@
maybeStartHiding();
listener.onSeekEnd(time);
}
-
}
diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java
index 3de534b..13a77f4 100644
--- a/src/com/android/gallery3d/app/MoviePlayer.java
+++ b/src/com/android/gallery3d/app/MoviePlayer.java
@@ -16,7 +16,6 @@
package com.android.gallery3d.app;
-import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -25,6 +24,7 @@
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
@@ -60,22 +60,25 @@
private static final String CMDNAME = "command";
private static final String CMDPAUSE = "pause";
+ private static final long BLACK_TIMEOUT = 500;
+
// If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing.
// Otherwise, we pause the player.
private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins
private Context mContext;
private final VideoView mVideoView;
+ private final View mRootView;
private final Bookmarker mBookmarker;
private final Uri mUri;
private final Handler mHandler = new Handler();
private final AudioBecomingNoisyReceiver mAudioBecomingNoisyReceiver;
- private final ActionBar mActionBar;
- private final ControllerOverlay mController;
+ private final MovieControllerOverlay mController;
private long mResumeableTime = Long.MAX_VALUE;
private int mVideoPosition = 0;
private boolean mHasPaused = false;
+ private int mLastSystemUiVis = 0;
// If the time bar is being dragged.
private boolean mDragging;
@@ -83,6 +86,9 @@
// If the time bar is visible.
private boolean mShowing;
+ // Control when system UI can be shown
+ private boolean mAllowShowingSystemUI;
+
private final Runnable mPlayingChecker = new Runnable() {
@Override
public void run() {
@@ -94,6 +100,13 @@
}
};
+ private final Runnable mRemoveBackground = new Runnable() {
+ @Override
+ public void run() {
+ mRootView.setBackground(null);
+ }
+ };
+
private final Runnable mProgressChecker = new Runnable() {
@Override
public void run() {
@@ -102,12 +115,12 @@
}
};
- public MoviePlayer(View rootView, final MovieActivity movieActivity, Uri videoUri,
- Bundle savedInstance, boolean canReplay) {
+ public MoviePlayer(View rootView, final MovieActivity movieActivity,
+ Uri videoUri, Bundle savedInstance, boolean canReplay) {
mContext = movieActivity.getApplicationContext();
+ mRootView = rootView;
mVideoView = (VideoView) rootView.findViewById(R.id.surface_view);
mBookmarker = new Bookmarker(movieActivity);
- mActionBar = movieActivity.getActionBar();
mUri = videoUri;
mController = new MovieControllerOverlay(mContext);
@@ -119,24 +132,58 @@
mVideoView.setOnCompletionListener(this);
mVideoView.setVideoURI(mUri);
mVideoView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
public boolean onTouch(View v, MotionEvent event) {
mController.show();
return true;
}
});
+ // The SurfaceView is transparent before drawing the first frame.
+ // This makes the UI flashing when open a video. (black -> old screen
+ // -> video) However, we have no way to know the timing of the first
+ // frame. So, we hide the VideoView for a while to make sure the
+ // video has been drawn on it.
+ mVideoView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mVideoView.setVisibility(View.VISIBLE);
+ }
+ }, BLACK_TIMEOUT);
+
// When the user touches the screen or uses some hard key, the framework
// will change system ui visibility from invisible to visible. We show
- // the media control at this point.
+ // the media control and enable system UI (e.g. ActionBar) to be visible at this point
mVideoView.setOnSystemUiVisibilityChangeListener(
new View.OnSystemUiVisibilityChangeListener() {
+ @Override
public void onSystemUiVisibilityChange(int visibility) {
- if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
+ int diff = mLastSystemUiVis ^ visibility;
+ mLastSystemUiVis = visibility;
+ if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
+ && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
+ mAllowShowingSystemUI = true;
mController.show();
+
+ // We need to set the background to clear ghosting images
+ // when ActionBar slides in. However, if we keep the background,
+ // there will be one additional layer in HW composer, which is bad
+ // to battery. As a solution, we remove the background when we
+ // hide the action bar
+ mHandler.removeCallbacks(mRemoveBackground);
+ mRootView.setBackgroundColor(Color.BLACK);
+ } else {
+ mHandler.removeCallbacks(mRemoveBackground);
+
+ // Wait for the slide out animation, one second should be enough
+ mHandler.postDelayed(mRemoveBackground, 1000);
}
}
});
+ // Hide system UI by default
+ showSystemUi(false);
+
mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
mAudioBecomingNoisyReceiver.register();
@@ -161,8 +208,13 @@
}
private void showSystemUi(boolean visible) {
- int flag = visible ? 0 : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_LOW_PROFILE;
+ int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+ if (!visible) {
+ flag |= View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+ }
mVideoView.setSystemUiVisibility(flag);
}
@@ -251,6 +303,7 @@
mHandler.postDelayed(mPlayingChecker, 250);
} else {
mController.showPlaying();
+ mController.hide();
}
mVideoView.start();
@@ -317,15 +370,18 @@
@Override
public void onShown() {
mShowing = true;
- mActionBar.show();
- showSystemUi(true);
setProgress();
+
+ // System UI is invisible by default until the flag is set by user interaction
+ // See VideoView's onSystemUiVisibilityChange listener for details.
+ if (mAllowShowingSystemUI) {
+ showSystemUi(true);
+ }
}
@Override
public void onHidden() {
mShowing = false;
- mActionBar.hide();
showSystemUi(false);
}
diff --git a/src/com/android/gallery3d/app/OrientationManager.java b/src/com/android/gallery3d/app/OrientationManager.java
new file mode 100644
index 0000000..c3e91fb
--- /dev/null
+++ b/src/com/android/gallery3d/app/OrientationManager.java
@@ -0,0 +1,210 @@
+/*
+ * 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.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.provider.Settings;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.ViewConfiguration;
+
+import com.android.gallery3d.ui.OrientationSource;
+
+import java.util.ArrayList;
+
+public class OrientationManager implements OrientationSource {
+ private static final String TAG = "OrientationManager";
+
+ public interface Listener {
+ public void onOrientationCompensationChanged();
+ }
+
+ // Orientation hysteresis amount used in rounding, in degrees
+ private static final int ORIENTATION_HYSTERESIS = 5;
+
+ private Activity mActivity;
+ private ArrayList<Listener> mListeners;
+ private MyOrientationEventListener mOrientationListener;
+ // The degrees of the device rotated clockwise from its natural orientation.
+ private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+ // If the framework orientation is locked.
+ private boolean mOrientationLocked = false;
+ // The orientation compensation: if the framwork orientation is locked, the
+ // device orientation and the framework orientation may be different, so we
+ // need to rotate the UI. For example, if this value is 90, the UI
+ // components should be rotated 90 degrees counter-clockwise.
+ private int mOrientationCompensation = 0;
+
+ // This is true if "Settings -> Display -> Rotation Lock" is checked. We
+ // don't allow the orientation to be unlocked if the value is true.
+ private boolean mRotationLockedSetting = false;
+
+ public OrientationManager(Activity activity) {
+ mActivity = activity;
+ mListeners = new ArrayList<Listener>();
+ mOrientationListener = new MyOrientationEventListener(activity);
+ }
+
+ public void resume() {
+ ContentResolver resolver = mActivity.getContentResolver();
+ mRotationLockedSetting = Settings.System.getInt(
+ resolver, Settings.System.ACCELEROMETER_ROTATION, 0) != 1;
+ mOrientationListener.enable();
+ }
+
+ public void pause() {
+ mOrientationListener.disable();
+ }
+
+ public void addListener(Listener listener) {
+ synchronized (mListeners) {
+ mListeners.add(listener);
+ }
+ }
+
+ public void removeListener(Listener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Orientation handling
+ //
+ // We can choose to lock the framework orientation or not. If we lock the
+ // framework orientation, we calculate a a compensation value according to
+ // current device orientation and send it to listeners. If we don't lock
+ // the framework orientation, we always set the compensation value to 0.
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Lock the framework orientation to the current device orientation
+ public void lockOrientation() {
+ if (mOrientationLocked) return;
+ mOrientationLocked = true;
+ if (mActivity.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE) {
+ Log.d(TAG, "lock orientation to landscape");
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ } else {
+ Log.d(TAG, "lock orientation to portrait");
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ updateCompensation();
+ }
+
+ // Unlock the framework orientation, so it can change when the device
+ // rotates.
+ public void unlockOrientation() {
+ if (!mOrientationLocked) return;
+ if (mRotationLockedSetting) return;
+ mOrientationLocked = false;
+ Log.d(TAG, "unlock orientation");
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ disableCompensation();
+ }
+
+ // Calculate the compensation value and send it to listeners.
+ private void updateCompensation() {
+ if (mOrientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
+ return;
+ }
+
+ int orientationCompensation =
+ (mOrientation + getDisplayRotation(mActivity)) % 360;
+
+ if (mOrientationCompensation != orientationCompensation) {
+ mOrientationCompensation = orientationCompensation;
+ notifyListeners();
+ }
+ }
+
+ // Make the compensation value 0 and send it to listeners.
+ private void disableCompensation() {
+ if (mOrientationCompensation != 0) {
+ mOrientationCompensation = 0;
+ notifyListeners();
+ }
+ }
+
+ private void notifyListeners() {
+ synchronized (mListeners) {
+ for (int i = 0, n = mListeners.size(); i < n; i++) {
+ mListeners.get(i).onOrientationCompensationChanged();
+ }
+ }
+ }
+
+ // This listens to the device orientation, so we can update the compensation.
+ private class MyOrientationEventListener extends OrientationEventListener {
+ public MyOrientationEventListener(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ // We keep the last known orientation. So if the user first orient
+ // the camera then point the camera to floor or sky, we still have
+ // the correct orientation.
+ if (orientation == ORIENTATION_UNKNOWN) return;
+ mOrientation = roundOrientation(orientation, mOrientation);
+ // If the framework orientation is locked, we update the
+ // compensation value and notify the listeners.
+ if (mOrientationLocked) updateCompensation();
+ }
+ }
+
+ @Override
+ public int getDisplayRotation() {
+ return getDisplayRotation(mActivity);
+ }
+
+ @Override
+ public int getCompensation() {
+ return mOrientationCompensation;
+ }
+
+ private static int roundOrientation(int orientation, int orientationHistory) {
+ boolean changeOrientation = false;
+ if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
+ changeOrientation = true;
+ } else {
+ int dist = Math.abs(orientation - orientationHistory);
+ dist = Math.min(dist, 360 - dist);
+ changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS);
+ }
+ if (changeOrientation) {
+ return ((orientation + 45) / 90 * 90) % 360;
+ }
+ return orientationHistory;
+ }
+
+ 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;
+ }
+}
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..6a4961e 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -16,29 +16,33 @@
package com.android.gallery3d.app;
-import com.android.gallery3d.common.BitmapUtils;
-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.MediaObject;
-import com.android.gallery3d.data.MediaSet;
-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 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 com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.BitmapPool;
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.LocalMediaItem;
+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.BitmapScreenNail;
+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.MediaSetUtils;
+import com.android.gallery3d.util.ThreadPool;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -54,16 +58,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.
@@ -112,14 +116,13 @@
private int mContentStart = 0;
private int mContentEnd = 0;
- /*
- * The ImageCache is a version-to-ImageEntry map. It only holds
- * the ImageEntries in the range of [mActiveStart, mActiveEnd).
- * We also keep mActiveEnd - mActiveStart <= IMAGE_CACHE_SIZE.
- * Besides, the [mActiveStart, mActiveEnd) range must be contained
- * within the[mContentStart, mContentEnd) range.
- */
- private HashMap<Long, ImageEntry> mImageCache = new HashMap<Long, ImageEntry>();
+ // The ImageCache is a Path-to-ImageEntry map. It only holds the
+ // ImageEntries in the range of [mActiveStart, mActiveEnd). We also keep
+ // mActiveEnd - mActiveStart <= IMAGE_CACHE_SIZE. Besides, the
+ // [mActiveStart, mActiveEnd) range must be contained within
+ // the [mContentStart, mContentEnd) range.
+ private HashMap<Path, ImageEntry> mImageCache =
+ new HashMap<Path, ImageEntry>();
private int mActiveStart = 0;
private int mActiveEnd = 0;
@@ -127,10 +130,13 @@
// mCurrentIndex triggers the data loading and image loading.
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];
+ // mChanges keeps the version number (of MediaItem) about the images. If any
+ // of 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];
+ // mPaths keeps the corresponding Path (of MediaItem) for the images. This
+ // is used to determine the item movement.
+ private final Path mPaths[] = new Path[IMAGE_CACHE_SIZE];
private final Handler mMainHandler;
private final ThreadPool mThreadPool;
@@ -142,10 +148,12 @@
private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
private int mSize = 0;
private Path mItemPath;
+ private int mCameraIndex;
+ private boolean mIsPanorama;
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);
}
@@ -156,14 +164,19 @@
// The path of the current viewing item will be stored in mItemPath.
// If mItemPath is not null, mCurrentIndex is only a hint for where we
// can find the item. If mItemPath is null, then we use the mCurrentIndex to
- // find the image being viewed.
- public PhotoDataAdapter(GalleryActivity activity,
- PhotoView view, MediaSet mediaSet, Path itemPath, int indexHint) {
+ // find the image being viewed. cameraIndex is the index of the camera
+ // preview. If cameraIndex < 0, there is no camera preview.
+ public PhotoDataAdapter(GalleryActivity activity, PhotoView view,
+ MediaSet mediaSet, Path itemPath, int indexHint, int cameraIndex,
+ boolean isPanorama) {
mSource = Utils.checkNotNull(mediaSet);
mPhotoView = Utils.checkNotNull(view);
mItemPath = Utils.checkNotNull(itemPath);
mCurrentIndex = indexHint;
+ mCameraIndex = cameraIndex;
+ mIsPanorama = isPanorama;
mThreadPool = activity.getThreadPool();
+ mNeedFullImage = true;
Arrays.fill(mChanges, MediaObject.INVALID_DATA_VERSION);
@@ -176,11 +189,19 @@
((Runnable) message.obj).run();
return;
case MSG_LOAD_START: {
- if (mDataListener != null) mDataListener.onLoadingStarted();
+ if (mDataListener != null) {
+ mDataListener.onLoadingStarted();
+ }
return;
}
case MSG_LOAD_FINISH: {
- if (mDataListener != null) mDataListener.onLoadingFinished();
+ if (mDataListener != null) {
+ mDataListener.onLoadingFinished();
+ }
+ return;
+ }
+ case MSG_UPDATE_IMAGE_REQUESTS: {
+ updateImageRequests();
return;
}
default: throw new AssertionError();
@@ -191,59 +212,116 @@
updateSlidingWindow();
}
- private long getVersion(int index) {
- if (index < 0 || index >= mSize) return VERSION_OUT_OF_RANGE;
+ private MediaItem getItemInternal(int index) {
+ if (index < 0 || index >= mSize) return null;
if (index >= mContentStart && index < mContentEnd) {
- MediaItem item = mData[index % DATA_CACHE_SIZE];
- if (item != null) return item.getDataVersion();
+ return mData[index % DATA_CACHE_SIZE];
}
- return MediaObject.INVALID_DATA_VERSION;
+ return null;
}
- 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 long getVersion(int index) {
+ MediaItem item = getItemInternal(index);
+ if (item == null) return MediaObject.INVALID_DATA_VERSION;
+ return item.getDataVersion();
+ }
+
+ private Path getPath(int index) {
+ MediaItem item = getItemInternal(index);
+ if (item == null) return null;
+ return item.getPath();
+ }
+
+ private void fireDataChange() {
+ // First check if data actually changed.
+ boolean changed = false;
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
+ long newVersion = getVersion(mCurrentIndex + i);
+ if (mChanges[i + SCREEN_NAIL_MAX] != newVersion) {
+ mChanges[i + SCREEN_NAIL_MAX] = newVersion;
+ changed = true;
}
}
+
+ if (!changed) return;
+
+ // Now calculate the fromIndex array. fromIndex represents the item
+ // movement. It records the index where the picture come from. The
+ // special value Integer.MAX_VALUE means it's a new picture.
+ final int N = IMAGE_CACHE_SIZE;
+ int fromIndex[] = new int[N];
+
+ // Remember the old path array.
+ Path oldPaths[] = new Path[N];
+ System.arraycopy(mPaths, 0, oldPaths, 0, N);
+
+ // Update the mPaths array.
+ for (int i = 0; i < N; ++i) {
+ mPaths[i] = getPath(mCurrentIndex + i - SCREEN_NAIL_MAX);
+ }
+
+ // Calculate the fromIndex array.
+ for (int i = 0; i < N; i++) {
+ Path p = mPaths[i];
+ if (p == null) {
+ fromIndex[i] = Integer.MAX_VALUE;
+ continue;
+ }
+
+ // Try to find the same path in the old array
+ int j;
+ for (j = 0; j < N; j++) {
+ if (oldPaths[j] == p) {
+ break;
+ }
+ }
+ fromIndex[i] = (j < N) ? j - SCREEN_NAIL_MAX : Integer.MAX_VALUE;
+ }
+
+ mPhotoView.notifyDataChange(fromIndex, -mCurrentIndex,
+ mSize - 1 - mCurrentIndex);
}
public void setDataListener(DataListener listener) {
mDataListener = listener;
}
- private void updateScreenNail(long version, Future<Bitmap> future) {
- ImageEntry entry = mImageCache.get(version);
+ private void updateScreenNail(Path path, Future<ScreenNail> future) {
+ ImageEntry entry = mImageCache.get(path);
+ 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();
- if (entry.screenNail == null) {
+ // Combine the ScreenNails if we already have a BitmapScreenNail
+ if (entry.screenNail instanceof BitmapScreenNail) {
+ BitmapScreenNail original = (BitmapScreenNail) entry.screenNail;
+ screenNail = original.combine(screenNail);
+ }
+
+ 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);
- }
+ entry.failToLoad = false;
+ entry.screenNail = screenNail;
+ }
+
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
+ if (path == getPath(mCurrentIndex + i)) {
+ if (i == 0) updateTileProvider(entry);
+ mPhotoView.notifyImageChange(i);
+ break;
}
}
updateImageRequests();
}
- private void updateFullImage(long version, Future<BitmapRegionDecoder> future) {
- ImageEntry entry = mImageCache.get(version);
+ private void updateFullImage(Path path, Future<BitmapRegionDecoder> future) {
+ ImageEntry entry = mImageCache.get(path);
if (entry == null || entry.fullImageTask != future) {
BitmapRegionDecoder fullImage = future.get();
if (fullImage != null) fullImage.recycle();
@@ -253,12 +331,9 @@
entry.fullImageTask = null;
entry.fullImage = future.get();
if (entry.fullImage != null) {
- if (mDataListener != null) {
- mDataListener.onPhotoAvailable(version, true);
- }
- if (version == getVersion(mCurrentIndex)) {
+ if (path == getPath(mCurrentIndex)) {
updateTileProvider(entry);
- mPhotoView.notifyImageInvalidated(0);
+ mPhotoView.notifyImageChange(0);
}
}
updateImageRequests();
@@ -273,7 +348,7 @@
mReloadTask = new ReloadTask();
mReloadTask.start();
- mPhotoView.notifyModelInvalidated();
+ fireDataChange();
}
public void pause() {
@@ -287,33 +362,24 @@
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 MediaItem getItem(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);
+ 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) {
+ if (mCurrentIndex == index) return;
mCurrentIndex = index;
updateSlidingWindow();
@@ -323,29 +389,93 @@
updateImageCache();
updateImageRequests();
updateTileProvider();
- mPhotoView.notifyOnNewImage();
if (mDataListener != null) {
mDataListener.onPhotoChanged(index, mItemPath);
}
- fireModelInvalidated();
+
+ fireDataChange();
}
- public void next() {
- updateCurrentIndex(mCurrentIndex + 1);
- }
-
- public void previous() {
- updateCurrentIndex(mCurrentIndex - 1);
- }
-
- public void jumpTo(int index) {
- if (mCurrentIndex == index) return;
+ @Override
+ public void moveTo(int index) {
updateCurrentIndex(index);
}
- public Bitmap getBackupImage() {
- return mTileProvider.getBackupImage();
+ @Override
+ public ScreenNail getScreenNail(int offset) {
+ int index = mCurrentIndex + offset;
+ if (index < 0 || index >= mSize || !mIsActive) return null;
+ Utils.assertTrue(index >= mActiveStart && index < mActiveEnd);
+
+ MediaItem item = getItem(index);
+ if (item == null) return null;
+
+ ImageEntry entry = mImageCache.get(item.getPath());
+ if (entry == null) return null;
+
+ // Create a default ScreenNail if the real one is not available yet.
+ if (entry.screenNail == null) {
+ entry.screenNail = newPlaceholderScreenNail(item);
+ if (offset == 0) updateTileProvider(entry);
+ }
+
+ return entry.screenNail;
+ }
+
+ @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);
+ }
+
+ @Override
+ public boolean isCamera(int offset) {
+ return mCurrentIndex + offset == mCameraIndex;
+ }
+
+ @Override
+ public boolean isPanorama(int offset) {
+ return isCamera(offset) && mIsPanorama;
+ }
+
+ @Override
+ public boolean isVideo(int offset) {
+ MediaItem item = getItem(mCurrentIndex + offset);
+ return (item == null)
+ ? false
+ : item.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO;
+ }
+
+ @Override
+ public int getLoadingState(int offset) {
+ ImageEntry entry = mImageCache.get(getPath(mCurrentIndex + offset));
+ if (entry == null) return LOADING_INIT;
+ if (entry.failToLoad) return LOADING_FAIL;
+ if (entry.screenNail != null) return LOADING_COMPLETE;
+ return LOADING_INIT;
+ }
+
+ public ScreenNail getScreenNail() {
+ return mTileProvider.getScreenNail();
}
public int getImageHeight() {
@@ -356,22 +486,13 @@
return mTileProvider.getImageWidth();
}
- public int getImageRotation() {
- ImageEntry entry = mImageCache.get(getVersion(mCurrentIndex));
- return entry == null ? 0 : entry.rotation;
- }
-
public int getLevelCount() {
return mTileProvider.getLevelCount();
}
public Bitmap getTile(int level, int x, int y, int tileSize,
- int borderSize) {
- return mTileProvider.getTile(level, x, y, tileSize, borderSize);
- }
-
- public boolean isFailedToLoad() {
- return mTileProvider.isFailedToLoad();
+ int borderSize, BitmapPool pool) {
+ return mTileProvider.getTile(level, x, y, tileSize, borderSize, pool);
}
public boolean isEmpty() {
@@ -382,8 +503,12 @@
return mCurrentIndex;
}
- public MediaItem getCurrentMediaItem() {
- return mData[mCurrentIndex % DATA_CACHE_SIZE];
+ public MediaItem getMediaItem(int offset) {
+ int index = mCurrentIndex + offset;
+ if (index >= mContentStart && index < mContentEnd) {
+ return mData[index % DATA_CACHE_SIZE];
+ }
+ return null;
}
public void setCurrentPhoto(Path path, int indexHint) {
@@ -392,17 +517,17 @@
mCurrentIndex = indexHint;
updateSlidingWindow();
updateImageCache();
- fireModelInvalidated();
+ fireDataChange();
// We need to reload content if the path doesn't match.
- MediaItem item = getCurrentMediaItem();
+ MediaItem item = getMediaItem(0);
if (item != null && item.getPath() != path) {
if (mReloadTask != null) mReloadTask.notifyDirty();
}
}
private void updateTileProvider() {
- ImageEntry entry = mImageCache.get(getVersion(mCurrentIndex));
+ ImageEntry entry = mImageCache.get(getPath(mCurrentIndex));
if (entry == null) { // in loading
mTileProvider.clear();
} else {
@@ -411,21 +536,20 @@
}
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();
- if (entry.failToLoad) mTileProvider.setFailedToLoad();
}
}
@@ -472,6 +596,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;
}
@@ -481,17 +606,17 @@
if (entry.screenNailTask != null && entry.screenNailTask != task) {
entry.screenNailTask.cancel();
entry.screenNailTask = null;
- entry.requestedBits &= ~BIT_SCREEN_NAIL;
+ entry.requestedScreenNail = MediaObject.INVALID_DATA_VERSION;
}
if (entry.fullImageTask != null && entry.fullImageTask != task) {
entry.fullImageTask.cancel();
entry.fullImageTask = null;
- entry.requestedBits &= ~BIT_FULL_IMAGE;
+ entry.requestedFullImage = MediaObject.INVALID_DATA_VERSION;
}
}
}
- private static class ScreenNailJob implements Job<Bitmap> {
+ private class ScreenNailJob implements Job<ScreenNail> {
private MediaItem mItem;
public ScreenNailJob(MediaItem item) {
@@ -499,50 +624,109 @@
}
@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;
+
+ // If this is a temporary item, don't try to get its bitmap because
+ // it won't be available. We will get its bitmap after a data reload.
+ if (isTemporaryItem(mItem)) {
+ return newPlaceholderScreenNail(mItem);
+ }
+
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 bitmap == null ? null : new BitmapScreenNail(bitmap);
}
}
+ private class FullImageJob implements Job<BitmapRegionDecoder> {
+ private MediaItem mItem;
+
+ public FullImageJob(MediaItem item) {
+ mItem = item;
+ }
+
+ @Override
+ public BitmapRegionDecoder run(JobContext jc) {
+ if (isTemporaryItem(mItem)) {
+ return null;
+ }
+ return mItem.requestLargeImage().run(jc);
+ }
+ }
+
+ // Returns true if we think this is a temporary item created by Camera. A
+ // temporary item is an image or a video whose data is still being
+ // processed, but an incomplete entry is created first in MediaProvider, so
+ // we can display them (in grey tile) even if they are not saved to disk
+ // yet. When the image or video data is actually saved, we will get
+ // notification from MediaProvider, reload data, and show the actual image
+ // or video data.
+ private boolean isTemporaryItem(MediaItem mediaItem) {
+ // Must have camera to create a temporary item.
+ if (mCameraIndex < 0) return false;
+ // Must be an item in camera roll.
+ if (!(mediaItem instanceof LocalMediaItem)) return false;
+ LocalMediaItem item = (LocalMediaItem) mediaItem;
+ if (item.getBucketId() != MediaSetUtils.CAMERA_BUCKET_ID) return false;
+ // Must have no size, but must have width and height information
+ if (item.getSize() != 0) return false;
+ if (item.getWidth() == 0) return false;
+ if (item.getHeight() == 0) return false;
+ // Must be created in the last 10 seconds.
+ if (item.getDateInMs() - System.currentTimeMillis() > 10000) return false;
+ return true;
+ }
+
+ // Create a default ScreenNail when a ScreenNail is needed, but we don't yet
+ // have one available (because the image data is still being saved, or the
+ // Bitmap is still being loaded.
+ private ScreenNail newPlaceholderScreenNail(MediaItem item) {
+ int width = item.getWidth();
+ int height = item.getHeight();
+ return new BitmapScreenNail(width, height);
+ }
+
// Returns the task if we started the task or the task is already started.
private Future<?> startTaskIfNeeded(int index, int which) {
if (index < mActiveStart || index >= mActiveEnd) return null;
- ImageEntry entry = mImageCache.get(getVersion(index));
+ ImageEntry entry = mImageCache.get(getPath(index));
if (entry == null) return null;
+ MediaItem item = mData[index % DATA_CACHE_SIZE];
+ Utils.assertTrue(item != null);
+ long version = item.getDataVersion();
- if (which == BIT_SCREEN_NAIL && entry.screenNailTask != null) {
+ if (which == BIT_SCREEN_NAIL && entry.screenNailTask != null
+ && entry.requestedScreenNail == version) {
return entry.screenNailTask;
- } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null) {
+ } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null
+ && entry.requestedFullImage == version) {
return entry.fullImageTask;
}
- MediaItem item = mData[index % DATA_CACHE_SIZE];
- Utils.assertTrue(item != null);
-
- if (which == BIT_SCREEN_NAIL
- && (entry.requestedBits & BIT_SCREEN_NAIL) == 0) {
- entry.requestedBits |= BIT_SCREEN_NAIL;
+ if (which == BIT_SCREEN_NAIL && entry.requestedScreenNail != version) {
+ entry.requestedScreenNail = version;
entry.screenNailTask = mThreadPool.submit(
new ScreenNailJob(item),
- new ScreenNailListener(item.getDataVersion()));
+ new ScreenNailListener(item));
// request screen nail
return entry.screenNailTask;
}
- if (which == BIT_FULL_IMAGE
- && (entry.requestedBits & BIT_FULL_IMAGE) == 0
+ if (which == BIT_FULL_IMAGE && entry.requestedFullImage != version
&& (item.getSupportedOperations()
& MediaItem.SUPPORT_FULL_IMAGE) != 0) {
- entry.requestedBits |= BIT_FULL_IMAGE;
+ entry.requestedFullImage = version;
entry.fullImageTask = mThreadPool.submit(
- item.requestLargeImage(),
- new FullImageListener(item.getDataVersion()));
+ new FullImageJob(item),
+ new FullImageListener(item));
// request full image
return entry.fullImageTask;
}
@@ -550,15 +734,13 @@
}
private void updateImageCache() {
- HashSet<Long> toBeRemoved = new HashSet<Long>(mImageCache.keySet());
+ HashSet<Path> toBeRemoved = new HashSet<Path>(mImageCache.keySet());
for (int i = mActiveStart; i < mActiveEnd; ++i) {
MediaItem item = mData[i % DATA_CACHE_SIZE];
- long version = item == null
- ? MediaObject.INVALID_DATA_VERSION
- : item.getDataVersion();
- if (version == MediaObject.INVALID_DATA_VERSION) continue;
- ImageEntry entry = mImageCache.get(version);
- toBeRemoved.remove(version);
+ if (item == null) continue;
+ Path path = item.getPath();
+ ImageEntry entry = mImageCache.get(path);
+ toBeRemoved.remove(path);
if (entry != null) {
if (Math.abs(i - mCurrentIndex) > 1) {
if (entry.fullImageTask != null) {
@@ -566,30 +748,39 @@
entry.fullImageTask = null;
}
entry.fullImage = null;
- entry.requestedBits &= ~BIT_FULL_IMAGE;
+ entry.requestedFullImage = MediaObject.INVALID_DATA_VERSION;
+ }
+ if (entry.requestedScreenNail != item.getDataVersion()) {
+ // This ScreenNail is outdated, we want to update it if it's
+ // still a placeholder.
+ if (entry.screenNail instanceof BitmapScreenNail) {
+ BitmapScreenNail s = (BitmapScreenNail) entry.screenNail;
+ s.updatePlaceholderSize(
+ item.getWidth(), item.getHeight());
+ }
}
} else {
entry = new ImageEntry();
- entry.rotation = item.getFullImageRotation();
- mImageCache.put(version, entry);
+ mImageCache.put(path, entry);
}
}
// Clear the data and requests for ImageEntries outside the new window.
- for (Long version : toBeRemoved) {
- ImageEntry entry = mImageCache.remove(version);
+ for (Path path : toBeRemoved) {
+ ImageEntry entry = mImageCache.remove(path);
if (entry.fullImageTask != null) entry.fullImageTask.cancel();
if (entry.screenNailTask != null) entry.screenNailTask.cancel();
+ if (entry.screenNail != null) entry.screenNail.recycle();
}
}
private class FullImageListener
implements Runnable, FutureListener<BitmapRegionDecoder> {
- private final long mVersion;
+ private final Path mPath;
private Future<BitmapRegionDecoder> mFuture;
- public FullImageListener(long version) {
- mVersion = version;
+ public FullImageListener(MediaItem item) {
+ mPath = item.getPath();
}
@Override
@@ -601,21 +792,21 @@
@Override
public void run() {
- updateFullImage(mVersion, mFuture);
+ updateFullImage(mPath, mFuture);
}
}
private class ScreenNailListener
- implements Runnable, FutureListener<Bitmap> {
- private final long mVersion;
- private Future<Bitmap> mFuture;
+ implements Runnable, FutureListener<ScreenNail> {
+ private final Path mPath;
+ private Future<ScreenNail> mFuture;
- public ScreenNailListener(long version) {
- mVersion = version;
+ public ScreenNailListener(MediaItem item) {
+ mPath = item.getPath();
}
@Override
- public void onFutureDone(Future<Bitmap> future) {
+ public void onFutureDone(Future<ScreenNail> future) {
mFuture = future;
mMainHandler.sendMessage(
mMainHandler.obtainMessage(MSG_RUN_OBJECT, this));
@@ -623,17 +814,17 @@
@Override
public void run() {
- updateScreenNail(mVersion, mFuture);
+ updateScreenNail(mPath, mFuture);
}
}
private static class ImageEntry {
- 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 long requestedScreenNail = MediaObject.INVALID_DATA_VERSION;
+ public long requestedFullImage = MediaObject.INVALID_DATA_VERSION;
public boolean failToLoad = false;
}
@@ -738,7 +929,7 @@
updateImageCache();
updateTileProvider();
updateImageRequests();
- fireModelInvalidated();
+ fireDataChange();
return null;
}
@@ -746,11 +937,6 @@
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);
}
}
}
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index ed67b08..36ccc67 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -16,13 +16,15 @@
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.NfcAdapter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -30,12 +32,12 @@
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.View.MeasureSpec;
import android.view.WindowManager;
import android.widget.ShareActionProvider;
import android.widget.Toast;
import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaDetails;
import com.android.gallery3d.data.MediaItem;
@@ -43,46 +45,61 @@
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.MtpDevice;
import com.android.gallery3d.data.Path;
+import com.android.gallery3d.data.SnailAlbum;
+import com.android.gallery3d.data.SnailItem;
+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.GLRoot;
+import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
import com.android.gallery3d.ui.GLView;
import com.android.gallery3d.ui.ImportCompleteListener;
import com.android.gallery3d.ui.MenuExecutor;
+import com.android.gallery3d.ui.PhotoFallbackEffect;
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;
+import com.android.gallery3d.util.MediaSetUtils;
-public class PhotoPage extends ActivityState
- implements PhotoView.PhotoTapListener, FilmStripView.Listener,
- UserInteractionListener {
+public class PhotoPage extends ActivityState implements
+ PhotoView.Listener, OrientationManager.Listener, AppBridge.Server {
private static final String TAG = "PhotoPage";
private static final int MSG_HIDE_BARS = 1;
+ private static final int MSG_LOCK_ORIENTATION = 2;
+ private static final int MSG_UNLOCK_ORIENTATION = 3;
+ private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
+ private static final int MSG_UPDATE_ACTION_BAR = 5;
+ private static final int MSG_UNFREEZE_GLROOT = 6;
+ private static final int MSG_WANT_BARS = 7;
private static final int HIDE_BARS_TIMEOUT = 3500;
+ private static final int UNFREEZE_GLROOT_TIMEOUT = 250;
private static final int REQUEST_SLIDESHOW = 1;
private static final int REQUEST_CROP = 2;
private static final int REQUEST_CROP_PICASA = 3;
+ private static final int REQUEST_EDIT = 4;
+ private static final int REQUEST_PLAY_VIDEO = 5;
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_APP_BRIDGE = "app-bridge";
+ public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up";
+
+ public static final String KEY_RETURN_INDEX_HINT = "return-index-hint";
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;
@@ -92,29 +109,38 @@
private MediaSet mMediaSet;
private Menu mMenu;
- private final Intent mResultIntent = new Intent();
private int mCurrentIndex = 0;
private Handler mHandler;
private boolean mShowBars = true;
- private ActionBar mActionBar;
+ private volatile boolean mActionBarAllowed = true;
+ private GalleryActionBar mActionBar;
private MyMenuVisibilityListener mMenuVisibilityListener;
private boolean mIsMenuVisible;
- private boolean mIsInteracting;
private MediaItem mCurrentPhoto = null;
private MenuExecutor mMenuExecutor;
private boolean mIsActive;
private ShareActionProvider mShareActionProvider;
+ private String mSetPathString;
+ // This is the original mSetPathString before adding the camera preview item.
+ private String mOriginalSetPathString;
+ private AppBridge mAppBridge;
+ private SnailItem mScreenNailItem;
+ private SnailAlbum mScreenNailSet;
+ private OrientationManager mOrientationManager;
+ private boolean mHasActivityResult;
+ private boolean mTreatBackAsUp;
+
+ private NfcAdapter mNfcAdapter;
public static interface Model extends PhotoView.Model {
public void resume();
public void pause();
public boolean isEmpty();
- public MediaItem getCurrentMediaItem();
- public int getCurrentIndex();
public void setCurrentPhoto(Path path, int indexHint);
}
private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
+ @Override
public void onMenuVisibilityChanged(boolean isVisible) {
mIsMenuVisible = isVisible;
refreshHidingMessage();
@@ -132,91 +158,90 @@
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);
mPhotoView = new PhotoView(mActivity);
- mPhotoView.setPhotoTapListener(this);
+ mPhotoView.setListener(this);
mRootPane.addComponent(mPhotoView);
mApplication = (GalleryApp)((Activity) mActivity).getApplication();
+ mOrientationManager = mActivity.getOrientationManager();
+ mOrientationManager.addListener(this);
+ mActivity.getGLRoot().setOrientationSource(mOrientationManager);
- String setPathString = data.getString(KEY_MEDIA_SET_PATH);
+ mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
+ mOriginalSetPathString = mSetPathString;
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext());
Path itemPath = Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH));
+ mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false);
- if (setPathString != null) {
- mMediaSet = mActivity.getDataManager().getMediaSet(setPathString);
+ if (mSetPathString != null) {
+ mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
+ if (mAppBridge != null) {
+ mAppBridge.setServer(this);
+ mOrientationManager.lockOrientation();
+
+ // Get the ScreenNail from AppBridge and register it.
+ int id = SnailSource.newId();
+ Path screenNailSetPath = SnailSource.getSetPath(id);
+ Path screenNailItemPath = SnailSource.getItemPath(id);
+ mScreenNailSet = (SnailAlbum) mActivity.getDataManager()
+ .getMediaObject(screenNailSetPath);
+ mScreenNailItem = (SnailItem) mActivity.getDataManager()
+ .getMediaObject(screenNailItemPath);
+ mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
+
+ // Combine the original MediaSet with the one for ScreenNail
+ // from AppBridge.
+ mSetPathString = "/combo/item/{" + screenNailSetPath +
+ "," + mSetPathString + "}";
+
+ // Start from the screen nail.
+ itemPath = screenNailItemPath;
+
+ // Action bar should not be displayed when camera starts.
+ mFlags |= FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR;
+ mShowBars = false;
+ }
+
+ mMediaSet = mActivity.getDataManager().getMediaSet(mSetPathString);
+ mSelectionManager.setSourceMediaSet(mMediaSet);
mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
- mMediaSet = (MediaSet)
- mActivity.getDataManager().getMediaObject(setPathString);
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);
+ mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
+ mAppBridge == null ? -1 : 0,
+ mAppBridge == null ? false : mAppBridge.isPanorama());
mModel = pda;
mPhotoView.setModel(mModel);
- mResultIntent.putExtra(KEY_INDEX_HINT, mCurrentIndex);
- setStateResult(Activity.RESULT_OK, mResultIntent);
-
pda.setDataListener(new PhotoDataAdapter.DataListener() {
@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) {
- mResultIntent.putExtra(KEY_MEDIA_ITEM_PATH, item.toString());
- MediaItem photo = mModel.getCurrentMediaItem();
+ MediaItem photo = mModel.getMediaItem(0);
if (photo != null) updateCurrentPhoto(photo);
- } else {
- mResultIntent.removeExtra(KEY_MEDIA_ITEM_PATH);
}
- setStateResult(Activity.RESULT_OK, mResultIntent);
+ updateBars();
}
@Override
public void onLoadingFinished() {
- GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
if (!mModel.isEmpty()) {
- MediaItem photo = mModel.getCurrentMediaItem();
+ MediaItem photo = mModel.getMediaItem(0);
if (photo != null) updateCurrentPhoto(photo);
} else if (mIsActive) {
mActivity.getStateManager().finishState(PhotoPage.this);
@@ -225,12 +250,6 @@
@Override
public void onLoadingStarted() {
- GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
- }
-
- @Override
- public void onPhotoAvailable(long version, boolean fullImage) {
- if (mFilmStripView == null) initFilmStripView();
}
});
} else {
@@ -250,13 +269,39 @@
hideBars();
break;
}
+ case MSG_LOCK_ORIENTATION: {
+ mOrientationManager.lockOrientation();
+ break;
+ }
+ case MSG_UNLOCK_ORIENTATION: {
+ mOrientationManager.unlockOrientation();
+ break;
+ }
+ case MSG_ON_FULL_SCREEN_CHANGED: {
+ mAppBridge.onFullScreenChanged(message.arg1 == 1);
+ break;
+ }
+ case MSG_UPDATE_ACTION_BAR: {
+ updateBars();
+ break;
+ }
+ case MSG_WANT_BARS: {
+ wantBars();
+ break;
+ }
+ case MSG_UNFREEZE_GLROOT: {
+ mActivity.getGLRoot().unfreeze();
+ break;
+ }
default: throw new AssertionError(message.what);
}
}
};
- // 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 +312,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 +323,28 @@
}
}
- 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);
+ if ((photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
+ updateShareURI(photo.getPath());
+ }
+ }
- 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() {
@@ -327,90 +375,141 @@
return true;
}
+ //////////////////////////////////////////////////////////////////////////
+ // Action Bar show/hide management
+ //////////////////////////////////////////////////////////////////////////
+
private void showBars() {
if (mShowBars) return;
mShowBars = true;
+ mOrientationManager.unlockOrientation();
mActionBar.show();
- WindowManager.LayoutParams params = ((Activity) mActivity).getWindow().getAttributes();
- params.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
- ((Activity) mActivity).getWindow().setAttributes(params);
- if (mFilmStripView != null) {
- mFilmStripView.show();
- }
+ mActivity.getGLRoot().setLightsOutMode(false);
+ refreshHidingMessage();
}
private void hideBars() {
if (!mShowBars) return;
mShowBars = false;
mActionBar.hide();
- 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();
- }
+ mActivity.getGLRoot().setLightsOutMode(true);
+ mHandler.removeMessages(MSG_HIDE_BARS);
}
private void refreshHidingMessage() {
mHandler.removeMessages(MSG_HIDE_BARS);
- if (!mIsMenuVisible && !mIsInteracting) {
+ if (!mIsMenuVisible) {
mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
}
}
- @Override
- public void onUserInteraction() {
- showBars();
- refreshHidingMessage();
+ private boolean canShowBars() {
+ // No bars if we are showing camera preview.
+ if (mAppBridge != null && mCurrentIndex == 0) return false;
+ // No bars if it's not allowed.
+ if (!mActionBarAllowed) return false;
+
+ return true;
}
- public void onUserInteractionTap() {
+ private void wantBars() {
+ if (canShowBars()) showBars();
+ }
+
+ private void toggleBars() {
if (mShowBars) {
hideBars();
- mHandler.removeMessages(MSG_HIDE_BARS);
} else {
- showBars();
- refreshHidingMessage();
+ if (canShowBars()) showBars();
+ }
+ }
+
+ private void updateBars() {
+ if (!canShowBars()) {
+ hideBars();
}
}
@Override
- public void onUserInteractionBegin() {
- showBars();
- mIsInteracting = true;
- refreshHidingMessage();
- }
-
- @Override
- public void onUserInteractionEnd() {
- mIsInteracting = false;
-
- // This function could be called from GL thread (in SlotView.render)
- // and post to the main thread. So, it could be executed while the
- // activity is paused.
- if (mIsActive) refreshHidingMessage();
+ public void onOrientationCompensationChanged() {
+ mActivity.getGLRoot().requestLayoutContentPane();
}
@Override
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 (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
+ // We are leaving this page. Set the result now.
+ setResult();
+ if (mTreatBackAsUp) {
+ onUpPressed();
+ } else {
+ super.onBackPressed();
}
- super.onBackPressed();
}
}
+ private void onUpPressed() {
+ if (mActivity.getStateManager().getStateCount() > 1) {
+ super.onBackPressed();
+ return;
+ }
+
+ if (mOriginalSetPathString == null) return;
+
+ if (mAppBridge == null) {
+ // We're in view mode so set up the stacks on our own.
+ Bundle data = new Bundle(getData());
+ data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
+ data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
+ mActivity.getDataManager().getTopSetPath(
+ DataManager.INCLUDE_ALL));
+ mActivity.getStateManager().switchState(this, AlbumPage.class, data);
+ } else {
+ // Start the real gallery activity to view the camera roll.
+ Uri uri = Uri.parse("content://media/external/file?bucketId="
+ + MediaSetUtils.CAMERA_BUCKET_ID);
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image");
+ ((Activity) mActivity).startActivity(intent);
+ }
+ }
+
+ private void setResult() {
+ Intent result = null;
+ if (!mPhotoView.getFilmMode()) {
+ result = new Intent();
+ result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
+ }
+ setStateResult(Activity.RESULT_OK, result);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // AppBridge.Server interface
+ //////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void setCameraRelativeFrame(Rect frame) {
+ mPhotoView.setCameraRelativeFrame(frame);
+ }
+
+ @Override
+ public boolean switchWithCaptureAnimation(int offset) {
+ return mPhotoView.switchWithCaptureAnimation(offset);
+ }
+
+ @Override
+ public void setSwipingEnabled(boolean enabled) {
+ mPhotoView.setSwipingEnabled(enabled);
+ }
+
+ @Override
+ public void notifyScreenNailChanged() {
+ mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
+ mScreenNailSet.notifyChange();
+ }
+
@Override
protected boolean onCreateActionBar(Menu menu) {
MenuInflater inflater = ((Activity) mActivity).getMenuInflater();
@@ -418,14 +517,34 @@
mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu);
if (mPendingSharePath != null) updateShareURI(mPendingSharePath);
mMenu = menu;
- mShowBars = true;
updateMenuOperations();
+ updateTitle();
return true;
}
+ private MenuExecutor.ProgressListener mConfirmDialogListener =
+ new MenuExecutor.ProgressListener() {
+ @Override
+ public void onProgressUpdate(int index) {}
+
+ @Override
+ public void onProgressComplete(int result) {}
+
+ @Override
+ public void onConfirmDialogShown() {
+ mHandler.removeMessages(MSG_HIDE_BARS);
+ }
+
+ @Override
+ public void onConfirmDialogDismissed(boolean confirmed) {
+ refreshHidingMessage();
+ }
+ };
+
@Override
protected boolean onItemSelected(MenuItem item) {
- MediaItem current = mModel.getCurrentMediaItem();
+ refreshHidingMessage();
+ MediaItem current = mModel.getMediaItem(0);
if (current == null) {
// item is not ready, ignore
@@ -437,7 +556,12 @@
DataManager manager = mActivity.getDataManager();
int action = item.getItemId();
+ String confirmMsg = null;
switch (action) {
+ case android.R.id.home: {
+ onUpPressed();
+ return true;
+ }
case R.id.action_slideshow: {
Bundle data = new Bundle();
data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
@@ -458,6 +582,14 @@
: REQUEST_CROP);
return true;
}
+ case R.id.action_edit: {
+ Intent intent = new Intent(Intent.ACTION_EDIT)
+ .setData(manager.getContentUri(path))
+ .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
+ REQUEST_EDIT);
+ return true;
+ }
case R.id.action_details: {
if (mShowDetails) {
hideDetails();
@@ -466,20 +598,21 @@
}
return true;
}
+ case R.id.action_delete:
+ confirmMsg = mActivity.getResources().getQuantityString(
+ R.plurals.delete_selection, 1);
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, confirmMsg, mConfirmDialogListener);
return true;
case R.id.action_import:
mSelectionManager.deSelectAll();
mSelectionManager.toggle(path);
- mMenuExecutor.onMenuClicked(item,
+ mMenuExecutor.onMenuClicked(item, confirmMsg,
new ImportCompleteListener(mActivity));
return true;
default :
@@ -497,6 +630,7 @@
if (mDetailsHelper == null) {
mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
mDetailsHelper.setCloseListener(new CloseListener() {
+ @Override
public void onClose() {
hideDetails();
}
@@ -506,10 +640,18 @@
mDetailsHelper.show();
}
+ ////////////////////////////////////////////////////////////////////////////
+ // Callbacks from PhotoView
+ ////////////////////////////////////////////////////////////////////////////
+ @Override
public void onSingleTapUp(int x, int y) {
- MediaItem item = mModel.getCurrentMediaItem();
- if (item == null) {
- // item is not ready, ignore
+ if (mAppBridge != null) {
+ if (mAppBridge.onSingleTapUp(x, y)) return;
+ }
+
+ MediaItem item = mModel.getMediaItem(0);
+ if (item == null || item == mScreenNailItem) {
+ // item is not ready or it is camera preview, ignore
return;
}
@@ -528,47 +670,79 @@
if (playVideo) {
playVideo((Activity) mActivity, item.getPlayUri(), item.getName());
} else {
- onUserInteractionTap();
+ toggleBars();
}
}
+ @Override
+ public void lockOrientation() {
+ mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION);
+ }
+
+ @Override
+ public void unlockOrientation() {
+ mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION);
+ }
+
+ @Override
+ public void onActionBarAllowed(boolean allowed) {
+ mActionBarAllowed = allowed;
+ mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
+ }
+
+ @Override
+ public void onActionBarWanted() {
+ mHandler.sendEmptyMessage(MSG_WANT_BARS);
+ }
+
+ @Override
+ public void onFullScreenChanged(boolean full) {
+ Message m = mHandler.obtainMessage(
+ MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
+ m.sendToTarget();
+ }
+
public static void playVideo(Activity activity, Uri uri, String title) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW)
- .setDataAndType(uri, "video/*");
- intent.putExtra(Intent.EXTRA_TITLE, title);
- activity.startActivity(intent);
+ .setDataAndType(uri, "video/*")
+ .putExtra(Intent.EXTRA_TITLE, title)
+ .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
+ activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
} catch (ActivityNotFoundException e) {
Toast.makeText(activity, activity.getString(R.string.video_err),
Toast.LENGTH_SHORT).show();
}
}
- // 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);
+ private void setCurrentPhotoByIntent(Intent intent) {
+ if (intent == null) return;
+ Path path = mApplication.getDataManager()
+ .findPathByUri(intent.getData(), intent.getType());
+ if (path != null) {
+ mModel.setCurrentPhoto(path, mCurrentIndex);
+ }
}
@Override
protected void onStateResult(int requestCode, int resultCode, Intent data) {
+ mHasActivityResult = true;
switch (requestCode) {
+ case REQUEST_EDIT:
+ setCurrentPhotoByIntent(data);
+ break;
case REQUEST_CROP:
if (resultCode == Activity.RESULT_OK) {
- if (data == null) break;
- Path path = mApplication
- .getDataManager().findPathByUri(data.getData());
- if (path != null) {
- mModel.setCurrentPhoto(path, mCurrentIndex);
- }
+ setCurrentPhotoByIntent(data);
}
break;
case REQUEST_CROP_PICASA: {
- int message = resultCode == Activity.RESULT_OK
- ? R.string.crop_saved
- : R.string.crop_not_saved;
- Toast.makeText(mActivity.getAndroidContext(),
- message, Toast.LENGTH_SHORT).show();
+ if (resultCode == Activity.RESULT_OK) {
+ Context context = mActivity.getAndroidContext();
+ String message = context.getString(R.string.crop_saved,
+ context.getString(R.string.folder_download));
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+ }
break;
}
case REQUEST_SLIDESHOW: {
@@ -582,36 +756,104 @@
}
}
+ private class PreparePhotoFallback implements OnGLIdleListener {
+ private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect();
+ private boolean mResultReady = false;
+
+ public synchronized PhotoFallbackEffect get() {
+ while (!mResultReady) {
+ Utils.waitWithoutInterrupt(this);
+ }
+ return mPhotoFallback;
+ }
+
+ @Override
+ public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+ mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas);
+ synchronized (this) {
+ mResultReady = true;
+ notifyAll();
+ }
+ return false;
+ }
+ }
+
+ private void preparePhotoFallbackView() {
+ GLRoot root = mActivity.getGLRoot();
+ PreparePhotoFallback task = new PreparePhotoFallback();
+ root.unlockRenderThread();
+ PhotoFallbackEffect anim;
+ try {
+ root.addOnGLIdleListener(task);
+ anim = task.get();
+ } finally {
+ root.lockRenderThread();
+ }
+ mActivity.getTransitionStore().put(
+ AlbumPage.KEY_RESUME_ANIMATION, anim);
+ }
+
@Override
public void onPause() {
super.onPause();
mIsActive = false;
- if (mFilmStripView != null) {
- mFilmStripView.pause();
- }
+
+ mActivity.getGLRoot().unfreeze();
+ mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
+ if (isFinishing()) preparePhotoFallbackView();
+
DetailsHelper.pause();
mPhotoView.pause();
mModel.pause();
mHandler.removeMessages(MSG_HIDE_BARS);
mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
+
mMenuExecutor.pause();
}
@Override
+ public void onCurrentImageUpdated() {
+ mActivity.getGLRoot().unfreeze();
+ }
+
+ @Override
protected void onResume() {
super.onResume();
+ mActivity.getGLRoot().freeze();
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();
+
+ if (mAppBridge != null && !mHasActivityResult) {
+ mPhotoView.resetToFirstPicture();
+ }
+ mHasActivityResult = false;
+ mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mAppBridge != null) {
+ mAppBridge.setServer(null);
+ mScreenNailItem.setScreenNail(null);
+ mAppBridge.detachScreenNail();
+ mAppBridge = null;
+ mScreenNailSet = null;
+ mScreenNailItem = null;
+ }
+ mOrientationManager.removeListener(this);
+ mActivity.getGLRoot().setOrientationSource(null);
+
+ // Remove all pending messages.
+ mHandler.removeCallbacksAndMessages(null);
+ super.onDestroy();
}
private class MyDetailsSource implements DetailsSource {
@@ -619,7 +861,7 @@
@Override
public MediaDetails getDetails() {
- return mModel.getCurrentMediaItem().getDetails();
+ return mModel.getMediaItem(0).getDetails();
}
@Override
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..111333e 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 {
@@ -49,6 +49,7 @@
private PhotoView mPhotoView;
private ThreadPool mThreadPool;
+ private int mLoadingState = LOADING_INIT;
public SinglePhotoDataAdapter(
GalleryActivity activity, PhotoView view, MediaItem item) {
@@ -109,16 +110,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);
}
@@ -127,10 +124,14 @@
private void onDecodeThumbComplete(Future<Bitmap> future) {
try {
Bitmap backup = future.get();
- if (backup == null) return;
- setBackupImage(backup, backup.getWidth(), backup.getHeight());
- mPhotoView.notifyOnNewImage();
- mPhotoView.notifyImageInvalidated(0); // the current image
+ if (backup == null) {
+ mLoadingState = LOADING_FAIL;
+ return;
+ } else {
+ mLoadingState = LOADING_COMPLETE;
+ }
+ setScreenNail(backup, backup.getWidth(), backup.getHeight());
+ mPhotoView.notifyImageChange(0);
} catch (Throwable t) {
Log.w(TAG, "fail to decode thumb", t);
}
@@ -158,35 +159,69 @@
}
}
- public ImageData getNextImage() {
- return null;
- }
-
- public ImageData getPreviousImage() {
- return null;
- }
-
- public void next() {
+ @Override
+ public void moveTo(int index) {
throw new UnsupportedOperationException();
}
- public void previous() {
- 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;
+ }
}
- public void jumpTo(int index) {
- throw new UnsupportedOperationException();
+ @Override
+ public int getImageRotation(int offset) {
+ return (offset == 0) ? mItem.getFullImageRotation() : 0;
}
- public MediaItem getCurrentMediaItem() {
- return mItem;
+ @Override
+ public ScreenNail getScreenNail(int offset) {
+ return (offset == 0) ? getScreenNail() : null;
}
+ @Override
+ public void setNeedFullImage(boolean enabled) {
+ // currently not necessary.
+ }
+
+ @Override
+ public boolean isCamera(int offset) {
+ return false;
+ }
+
+ @Override
+ public boolean isPanorama(int offset) {
+ return false;
+ }
+
+ @Override
+ public boolean isVideo(int offset) {
+ return mItem.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO;
+ }
+
+ @Override
+ public MediaItem getMediaItem(int offset) {
+ return offset == 0 ? mItem : null;
+ }
+
+ @Override
public int getCurrentIndex() {
return 0;
}
+ @Override
public void setCurrentPhoto(Path path, int indexHint) {
// ignore
}
+
+ @Override
+ public int getLoadingState(int offset) {
+ return mLoadingState;
+ }
}
diff --git a/src/com/android/gallery3d/app/SlideshowDream.java b/src/com/android/gallery3d/app/SlideshowDream.java
index 80f7fe0..9963da6 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);
@@ -20,6 +14,7 @@
// Uri.fromFile(Environment.getExternalStoragePublicDirectory(
// Environment.DIRECTORY_PICTURES)))
.putExtra(Gallery.EXTRA_SLIDESHOW, true)
+ .putExtra(Gallery.EXTRA_DREAM, true)
.setFlags(getIntent().getFlags());
startActivity(i);
finish();
diff --git a/src/com/android/gallery3d/app/SlideshowPage.java b/src/com/android/gallery3d/app/SlideshowPage.java
index 8697629..a6ae527 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;
@@ -49,6 +48,7 @@
public static final String KEY_PHOTO_INDEX = "photo-index";
public static final String KEY_RANDOM_ORDER = "random-order";
public static final String KEY_REPEAT = "repeat";
+ public static final String KEY_DREAM = "dream";
private static final long SLIDESHOW_DELAY = 3000; // 3 seconds
@@ -105,7 +105,14 @@
@Override
public void onCreate(Bundle data, Bundle restoreState) {
- mFlags |= (FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR | FLAG_SCREEN_ON);
+ mFlags |= (FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR);
+ if (data.getBoolean(KEY_DREAM)) {
+ // Dream screensaver only keeps screen on for plugged devices.
+ mFlags |= FLAG_SCREEN_ON_WHEN_PLUGGED;
+ } else {
+ // User-initiated slideshow would always keep screen on.
+ mFlags |= FLAG_SCREEN_ON_ALWAYS;
+ }
mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
@Override
diff --git a/src/com/android/gallery3d/app/StateManager.java b/src/com/android/gallery3d/app/StateManager.java
index 556a06a..02e9976 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);
@@ -99,11 +93,10 @@
}
public boolean createOptionsMenu(Menu menu) {
- if (!mStack.isEmpty()) {
- ((Activity) mContext).setProgressBarIndeterminateVisibility(false);
- return getTopState().onCreateActionBar(menu);
- } else {
+ if (mStack.isEmpty()) {
return false;
+ } else {
+ return getTopState().onCreateActionBar(menu);
}
}
@@ -135,18 +128,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 +146,22 @@
}
void finishState(ActivityState state) {
- Log.v(TAG, "finishState " + state.getClass());
+ // The finish() request could be rejected (only happens under Monkey),
+ // If it is rejected, we won't close the last page.
+ if (mStack.size() == 1) {
+ Activity activity = (Activity) mContext.getAndroidContext();
+ if (mResult != null) {
+ activity.setResult(mResult.resultCode, mResult.resultData);
+ }
+ activity.finish();
+ if (!activity.isFinishing()) {
+ Log.w(TAG, "finish is rejected, keep the last state");
+ return;
+ }
+ Log.v(TAG, "no more state, finish activity");
+ }
+
+ Log.v(TAG, "finishState " + state);
if (state != mStack.peek().activityState) {
if (state.isDestroyed()) {
Log.d(TAG, "The state is already destroyed");
@@ -173,25 +175,12 @@
// Remove the top state.
mStack.pop();
+ state.mIsFinishing = true;
if (mIsResumed) state.onPause();
mContext.getGLRoot().setContentPane(null);
state.onDestroy();
- if (mStack.isEmpty()) {
- Log.v(TAG, "no more state, finish activity");
- Activity activity = (Activity) mContext.getAndroidContext();
- if (mResult != null) {
- activity.setResult(mResult.resultCode, mResult.resultData);
- }
- activity.finish();
-
- // The finish() request is rejected (only happens under Monkey),
- // so we start the default page instead.
- if (!activity.isFinishing()) {
- Log.v(TAG, "finish() failed, start default page");
- ((Gallery) mContext).startDefaultPage();
- }
- } else {
+ if (!mStack.isEmpty()) {
// Restore the immediately previous state
ActivityState top = mStack.peek().activityState;
if (mIsResumed) top.resume();
@@ -235,7 +224,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 +249,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/app/TransitionStore.java b/src/com/android/gallery3d/app/TransitionStore.java
new file mode 100644
index 0000000..9c09e7b
--- /dev/null
+++ b/src/com/android/gallery3d/app/TransitionStore.java
@@ -0,0 +1,36 @@
+/*
+ * 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 java.util.HashMap;
+
+public class TransitionStore {
+ private HashMap<Object, Object> mStorage = new HashMap<Object, Object>();
+
+ public void put(Object key, Object value) {
+ mStorage.put(key, value);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T get(Object key) {
+ return (T) mStorage.get(key);
+ }
+
+ public void clear() {
+ mStorage.clear();
+ }
+}
diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java
index 07a3d53..c08c1d7 100644
--- a/src/com/android/gallery3d/app/Wallpaper.java
+++ b/src/com/android/gallery3d/app/Wallpaper.java
@@ -18,9 +18,9 @@
import android.app.Activity;
import android.content.Intent;
+import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
-import android.view.Display;
/**
* Wallpaper picker for the gallery application. This just redirects to the
@@ -78,9 +78,10 @@
case STATE_PHOTO_PICKED: {
int width = getWallpaperDesiredMinimumWidth();
int height = getWallpaperDesiredMinimumHeight();
- Display display = getWindowManager().getDefaultDisplay();
- float spotlightX = (float) display.getWidth() / width;
- float spotlightY = (float) display.getHeight() / height;
+ Point size = new Point();
+ getWindowManager().getDefaultDisplay().getSize(size);
+ float spotlightX = (float) size.x / width;
+ float spotlightY = (float) size.y / height;
Intent request = new Intent(CropImage.ACTION_CROP)
.setDataAndType(mPickedItem, IMAGE_TYPE)
.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
@@ -91,6 +92,7 @@
.putExtra(CropImage.KEY_SPOTLIGHT_X, spotlightX)
.putExtra(CropImage.KEY_SPOTLIGHT_Y, spotlightY)
.putExtra(CropImage.KEY_SCALE, true)
+ .putExtra(CropImage.KEY_SCALE_UP_IF_NEEDED, true)
.putExtra(CropImage.KEY_NO_FACE_DETECTION, true)
.putExtra(CropImage.KEY_SET_AS_WALLPAPER, true);
startActivity(request);
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/ComboAlbumSet.java b/src/com/android/gallery3d/data/ComboAlbumSet.java
index 916b163..6d9a2a3 100644
--- a/src/com/android/gallery3d/data/ComboAlbumSet.java
+++ b/src/com/android/gallery3d/data/ComboAlbumSet.java
@@ -65,6 +65,14 @@
}
@Override
+ public boolean isLoading() {
+ for (int i = 0, n = mSets.length; i < n; ++i) {
+ if (mSets[i].isLoading()) return true;
+ }
+ return false;
+ }
+
+ @Override
public long reload() {
boolean changed = false;
for (int i = 0, n = mSets.length; i < n; ++i) {
diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java
index f7dac5e..0eb6af5 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,18 +242,10 @@
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) {
+ public Path findPathByUri(Uri uri, String type) {
if (uri == null) return null;
for (MediaSource source : mSourceMap.values()) {
- Path path = source.findPathByUri(uri);
+ Path path = source.findPathByUri(uri, type);
if (path != null) return path;
}
return null;
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..07741ef 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,16 @@
private final ChangeNotifier mNotifierImage;
private final ChangeNotifier mNotifierVideo;
private final String mName;
+ private final Handler mHandler;
+ private boolean mIsLoading;
+
+ 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 +121,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 +139,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 +167,9 @@
buffer.add(entry);
}
}
+ if (jc.isCancelled()) return null;
}
+ Log.v("DebugLoadingTime", "got " + buffer.size() + " buckets");
} finally {
cursor.close();
}
@@ -166,62 +184,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 +262,46 @@
}
@Override
- public long reload() {
+ public synchronized boolean isLoading() {
+ return mIsLoading;
+ }
+
+ @Override
+ // 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();
+ mIsLoading = true;
+ 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();
+ mIsLoading = false;
+ 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..aa27c6f 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;
@@ -57,7 +57,7 @@
private static final int INDEX_DATA = 8;
private static final int INDEX_ORIENTATION = 9;
private static final int INDEX_BUCKET_ID = 10;
- private static final int INDEX_SIZE_ID = 11;
+ private static final int INDEX_SIZE = 11;
private static final int INDEX_WIDTH = 12;
private static final int INDEX_HEIGHT = 13;
@@ -74,16 +74,13 @@
ImageColumns.ORIENTATION, // 9
ImageColumns.BUCKET_ID, // 10
ImageColumns.SIZE, // 11
- // These should be changed to proper names after they are made public.
- "width", // ImageColumns.WIDTH, // 12
- "height", // ImageColumns.HEIGHT // 13
+ ImageColumns.WIDTH, // 12
+ ImageColumns.HEIGHT // 13
};
private final GalleryApp mApplication;
public int rotation;
- public int width;
- public int height;
public LocalImage(Path path, GalleryApp application, Cursor cursor) {
super(path, nextVersionNumber());
@@ -121,7 +118,7 @@
filePath = cursor.getString(INDEX_DATA);
rotation = cursor.getInt(INDEX_ORIENTATION);
bucketId = cursor.getInt(INDEX_BUCKET_ID);
- fileSize = cursor.getLong(INDEX_SIZE_ID);
+ fileSize = cursor.getLong(INDEX_SIZE);
width = cursor.getInt(INDEX_WIDTH);
height = cursor.getInt(INDEX_HEIGHT);
}
@@ -143,7 +140,7 @@
filePath = uh.update(filePath, cursor.getString(INDEX_DATA));
rotation = uh.update(rotation, cursor.getInt(INDEX_ORIENTATION));
bucketId = uh.update(bucketId, cursor.getInt(INDEX_BUCKET_ID));
- fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE_ID));
+ fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE));
width = uh.update(width, cursor.getInt(INDEX_WIDTH));
height = uh.update(height, cursor.getInt(INDEX_HEIGHT));
return uh.isUpdated();
@@ -159,14 +156,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 +179,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 +203,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..7a54e80 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;
@@ -44,6 +44,8 @@
public long dateModifiedInSec;
public String filePath;
public int bucketId;
+ public int width;
+ public int height;
public LocalMediaItem(Path path, long version) {
super(path, version);
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..6735b16 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;
@@ -148,7 +151,7 @@
}
@Override
- public Path findPathByUri(Uri uri) {
+ public Path findPathByUri(Uri uri, String type) {
try {
switch (mUriMatcher.match(uri)) {
case LOCAL_IMAGE_ITEM: {
@@ -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..4e888a5 100644
--- a/src/com/android/gallery3d/data/LocalVideo.java
+++ b/src/com/android/gallery3d/data/LocalVideo.java
@@ -16,30 +16,24 @@
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 {
-
+ private static final String TAG = "LocalVideo";
static final Path ITEM_PATH = Path.fromString("/local/video/item");
// Must preserve order between these indices and the order of the terms in
@@ -55,7 +49,8 @@
private static final int INDEX_DATA = 8;
private static final int INDEX_DURATION = 9;
private static final int INDEX_BUCKET_ID = 10;
- private static final int INDEX_SIZE_ID = 11;
+ private static final int INDEX_SIZE = 11;
+ private static final int INDEX_RESOLUTION = 12;
static final String[] PROJECTION = new String[] {
VideoColumns._ID,
@@ -69,11 +64,11 @@
VideoColumns.DATA,
VideoColumns.DURATION,
VideoColumns.BUCKET_ID,
- VideoColumns.SIZE
+ VideoColumns.SIZE,
+ VideoColumns.RESOLUTION,
};
private final GalleryApp mApplication;
- private static Bitmap sOverlay;
public int durationInSec;
@@ -113,7 +108,22 @@
filePath = cursor.getString(INDEX_DATA);
durationInSec = cursor.getInt(INDEX_DURATION) / 1000;
bucketId = cursor.getInt(INDEX_BUCKET_ID);
- fileSize = cursor.getLong(INDEX_SIZE_ID);
+ fileSize = cursor.getLong(INDEX_SIZE);
+ parseResolution(cursor.getString(INDEX_RESOLUTION));
+ }
+
+ private void parseResolution(String resolution) {
+ if (resolution == null) return;
+ int m = resolution.indexOf('x');
+ if (m == -1) return;
+ try {
+ int w = Integer.parseInt(resolution.substring(0, m));
+ int h = Integer.parseInt(resolution.substring(m + 1));
+ width = w;
+ height = h;
+ } catch (Throwable t) {
+ Log.w(TAG, t);
+ }
}
@Override
@@ -134,7 +144,7 @@
durationInSec = uh.update(
durationInSec, cursor.getInt(INDEX_DURATION) / 1000);
bucketId = uh.update(bucketId, cursor.getInt(INDEX_BUCKET_ID));
- fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE_ID));
+ fileSize = uh.update(fileSize, cursor.getLong(INDEX_SIZE));
return uh.isUpdated();
}
@@ -148,7 +158,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;
}
@@ -213,11 +223,11 @@
@Override
public int getWidth() {
- return 0;
+ return width;
}
@Override
public int getHeight() {
- return 0;
+ return height;
}
}
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/MediaSet.java b/src/com/android/gallery3d/data/MediaSet.java
index ff9b8c3..67525b1 100644
--- a/src/com/android/gallery3d/data/MediaSet.java
+++ b/src/com/android/gallery3d/data/MediaSet.java
@@ -94,6 +94,14 @@
return false;
}
+ /**
+ * Method {@link #reload()} may process the loading task in background, this method tells
+ * its client whether the loading is still in process or not.
+ */
+ public boolean isLoading() {
+ return false;
+ }
+
public int getTotalMediaItemCount() {
int total = getMediaItemCount();
for (int i = 0, n = getSubMediaSetCount(); i < n; i++) {
diff --git a/src/com/android/gallery3d/data/MediaSource.java b/src/com/android/gallery3d/data/MediaSource.java
index ae98e0f..9f7561e 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 {
@@ -34,7 +34,7 @@
return mPrefix;
}
- public Path findPathByUri(Uri uri) {
+ public Path findPathByUri(Uri uri, String type) {
return null;
}
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..0749020 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,10 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.util.BucketNames;
+import com.android.gallery3d.util.GalleryUtils;
+
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@@ -20,8 +20,6 @@
public class MtpContext implements MtpClient.Listener {
private static final String TAG = "MtpContext";
- public static final String NAME_IMPORTED_FOLDER = "Imported";
-
private ScannerClient mScannerClient;
private Context mContext;
private MtpClient mClient;
@@ -105,7 +103,7 @@
public boolean copyFile(String deviceName, MtpObjectInfo objInfo) {
if (GalleryUtils.hasSpaceForSize(objInfo.getCompressedSize())) {
File dest = Environment.getExternalStorageDirectory();
- dest = new File(dest, NAME_IMPORTED_FOLDER);
+ dest = new File(dest, BucketNames.IMPORTED);
dest.mkdirs();
String destPath = new File(dest, objInfo.getName()).getAbsolutePath();
int objectId = objInfo.getObjectHandle();
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..1f26511 100644
--- a/src/com/android/gallery3d/data/MtpDeviceSet.java
+++ b/src/com/android/gallery3d/data/MtpDeviceSet.java
@@ -16,27 +16,38 @@
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;
+ private boolean mIsLoading;
public MtpDeviceSet(Path path, GalleryApp application, MtpContext mtpContext) {
super(path, nextVersionNumber());
@@ -44,28 +55,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 +114,40 @@
}
@Override
- public long reload() {
+ public synchronized boolean isLoading() {
+ return mIsLoading;
+ }
+
+ @Override
+ public synchronized long reload() {
if (mNotifier.isDirty()) {
+ if (mLoadTask != null) mLoadTask.cancel();
+ mIsLoading = true;
+ 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();
+ mIsLoading = false;
+ 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..cd4f803
--- /dev/null
+++ b/src/com/android/gallery3d/data/SnailAlbum.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+// This is a simple MediaSet which contains only one MediaItem -- a SnailItem.
+public class SnailAlbum extends MediaSet {
+ private static final String TAG = "SnailAlbum";
+ private SnailItem mItem;
+ private AtomicBoolean mDirty = new AtomicBoolean(false);
+
+ public SnailAlbum(Path path, MediaItem item) {
+ super(path, nextVersionNumber());
+ mItem = (SnailItem) 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() {
+ if (mDirty.compareAndSet(true, false)) {
+ mItem.updateVersion();
+ mDataVersion = nextVersionNumber();
+ }
+ return mDataVersion;
+ }
+
+ public void notifyChange() {
+ mDirty.set(true);
+ notifyContentChanged();
+ }
+}
diff --git a/src/com/android/gallery3d/data/SnailItem.java b/src/com/android/gallery3d/data/SnailItem.java
new file mode 100644
index 0000000..fdb1b0d
--- /dev/null
+++ b/src/com/android/gallery3d/data/SnailItem.java
@@ -0,0 +1,92 @@
+/*
+ * 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 static final String TAG = "SnailItem";
+ private ScreenNail mScreenNail;
+
+ public SnailItem(Path path) {
+ super(path, nextVersionNumber());
+ }
+
+ @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;
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Extra methods for SnailItem
+ //////////////////////////////////////////////////////////////////////////
+
+ public void setScreenNail(ScreenNail screenNail) {
+ mScreenNail = screenNail;
+ }
+
+ public void updateVersion() {
+ mDataVersion = nextVersionNumber();
+ }
+}
diff --git a/src/com/android/gallery3d/data/SnailSource.java b/src/com/android/gallery3d/data/SnailSource.java
new file mode 100644
index 0000000..5f663d2
--- /dev/null
+++ b/src/com/android/gallery3d/data/SnailSource.java
@@ -0,0 +1,69 @@
+/*
+ * 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.app.GalleryApp;
+
+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;
+
+ 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);
+ }
+ }
+ return null;
+ }
+
+ // Registers a new SnailAlbum containing a SnailItem and returns the id of
+ // them. You can obtain the Path of the SnailAlbum and SnailItem associated
+ // with the id by getSetPath and getItemPath().
+ public static synchronized int newId() {
+ return sNextId++;
+ }
+
+ 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);
+ }
+}
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..b5bfe96 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;
@@ -30,7 +23,13 @@
import android.graphics.BitmapRegionDecoder;
import android.net.Uri;
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;
@@ -58,22 +57,11 @@
private GalleryApp mApplication;
- public UriImage(GalleryApp application, Path path, Uri uri) {
+ public UriImage(GalleryApp application, Path path, Uri uri, String contentType) {
super(path, nextVersionNumber());
mUri = uri;
mApplication = Utils.checkNotNull(application);
- mContentType = getMimeType(uri);
- }
-
- private String getMimeType(Uri uri) {
- if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
- String extension =
- MimeTypeMap.getFileExtensionFromUrl(uri.toString());
- String type = MimeTypeMap.getSingleton()
- .getMimeTypeFromExtension(extension.toLowerCase());
- if (type != null) return type;
- }
- return mApplication.getContentResolver().getType(uri);
+ mContentType = contentType;
}
@Override
@@ -180,7 +168,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 +183,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..d37c51d 100644
--- a/src/com/android/gallery3d/data/UriSource.java
+++ b/src/com/android/gallery3d/data/UriSource.java
@@ -16,9 +16,11 @@
package com.android.gallery3d.data;
-import com.android.gallery3d.app.GalleryApp;
-
+import android.content.ContentResolver;
import android.net.Uri;
+import android.webkit.MimeTypeMap;
+
+import com.android.gallery3d.app.GalleryApp;
import java.net.URLDecoder;
import java.net.URLEncoder;
@@ -26,6 +28,8 @@
class UriSource extends MediaSource {
@SuppressWarnings("unused")
private static final String TAG = "UriSource";
+ private static final String IMAGE_TYPE_PREFIX = "image/";
+ private static final String IMAGE_TYPE_ANY = "image/*";
private GalleryApp mApplication;
@@ -37,22 +41,44 @@
@Override
public MediaObject createMediaObject(Path path) {
String segment[] = path.split();
- if (segment.length != 2) {
+ if (segment.length != 3) {
throw new RuntimeException("bad path: " + path);
}
+ String uri = URLDecoder.decode(segment[1]);
+ String type = URLDecoder.decode(segment[2]);
+ return new UriImage(mApplication, path, Uri.parse(uri), type);
+ }
- String decoded = URLDecoder.decode(segment[1]);
- return new UriImage(mApplication, path, Uri.parse(decoded));
+ private String getMimeType(Uri uri) {
+ if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+ String extension =
+ MimeTypeMap.getFileExtensionFromUrl(uri.toString());
+ String type = MimeTypeMap.getSingleton()
+ .getMimeTypeFromExtension(extension.toLowerCase());
+ if (type != null) return type;
+ }
+ // Assume the type is image if the type cannot be resolved
+ // This could happen for "http" URI.
+ String type = mApplication.getContentResolver().getType(uri);
+ if (type == null) type = "image/*";
+ return type;
}
@Override
- public Path findPathByUri(Uri uri) {
- String type = mApplication.getContentResolver().getType(uri);
- // Assume the type is image if the type cannot be resolved
- // This could happen for "http" URI.
- if (type == null || type.startsWith("image/")) {
- return Path.fromString("/uri/" + URLEncoder.encode(uri.toString()));
+ public Path findPathByUri(Uri uri, String type) {
+ String mimeType = getMimeType(uri);
+
+ // Try to find a most specific type but it has to be started with "image/"
+ if ((type == null) || (IMAGE_TYPE_ANY.equals(type)
+ && mimeType.startsWith(IMAGE_TYPE_PREFIX))) {
+ type = mimeType;
}
+
+ if (type.startsWith(IMAGE_TYPE_PREFIX)) {
+ return Path.fromString("/uri/" + URLEncoder.encode(uri.toString())
+ + "/" +URLEncoder.encode(type));
+ }
+ // We have no clues that it is an image
return null;
}
}
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..35f9bec 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,10 @@
import android.util.Log;
import android.widget.Toast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.Gallery;
+import com.android.gallery3d.app.PhotoPage;
+
public class WidgetClickHandler extends Activity {
private static final String TAG = "PhotoAppWidgetClickHandler";
@@ -46,14 +47,19 @@
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
- Intent intent = getIntent();
- if (isValidDataUri(intent.getData())) {
- startActivity(new Intent(Intent.ACTION_VIEW, intent.getData()));
+ Uri uri = getIntent().getData();
+ Intent intent;
+ if (isValidDataUri(uri)) {
+ intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.putExtra(PhotoPage.KEY_TREAT_BACK_AS_UP, true);
} else {
Toast.makeText(this,
R.string.no_such_item, Toast.LENGTH_LONG).show();
- startActivity(new Intent(this, Gallery.class));
+ intent = new Intent(this, Gallery.class);
}
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ startActivity(intent);
finish();
}
}
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/ActionBar.java b/src/com/android/gallery3d/photoeditor/ActionBar.java
index 814ee8e..94379f7 100644
--- a/src/com/android/gallery3d/photoeditor/ActionBar.java
+++ b/src/com/android/gallery3d/photoeditor/ActionBar.java
@@ -92,10 +92,6 @@
findViewById(R.id.action_bar_back).performClick();
}
- public void clickSave() {
- findViewById(R.id.save_button).performClick();
- }
-
public boolean canSave() {
return findViewById(R.id.save_button).isEnabled();
}
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/FilterStack.java b/src/com/android/gallery3d/photoeditor/FilterStack.java
index fe6fb10..273c295 100644
--- a/src/com/android/gallery3d/photoeditor/FilterStack.java
+++ b/src/com/android/gallery3d/photoeditor/FilterStack.java
@@ -118,6 +118,7 @@
reallocateBuffer(out);
}
appliedStack.get(filterIndex).process(input, buffers[out]);
+ nativeEglSetFenceAndWait();
return buffers[out];
}
return null;
@@ -275,4 +276,10 @@
photoView.onResume();
paused = false;
}
+
+ static {
+ System.loadLibrary("jni_eglfence");
+ }
+
+ private native void nativeEglSetFenceAndWait();
}
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..27aaed2 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);
}
@@ -183,22 +183,18 @@
public void run() {
// Exit effects or go back to the previous activity on pressing back button.
if (!effectsBar.exit(null)) {
- // Pop-up a dialog to save unsaved photo.
+ // Pop-up a dialog if there are unsaved changes.
if (actionBar.canSave()) {
- new YesNoCancelDialogBuilder(PhotoEditor.this, new Runnable() {
+ new YesCancelDialogBuilder(PhotoEditor.this, new Runnable() {
@Override
public void run() {
- actionBar.clickSave();
- }
- }, new Runnable() {
-
- @Override
- public void run() {
+ // Discard unsaved photo for the result.
finish();
}
- }, R.string.save_photo).show();
+ }, R.string.discard_unsaved_photo).show();
} else {
+ setResult(RESULT_OK, new Intent().setData(saveUri));
finish();
}
}
@@ -223,7 +219,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..1b59292 100644
--- a/src/com/android/gallery3d/photoeditor/SaveCopyTask.java
+++ b/src/com/android/gallery3d/photoeditor/SaveCopyTask.java
@@ -30,6 +30,7 @@
import android.widget.Toast;
import com.android.gallery3d.R;
+import com.android.gallery3d.util.BucketNames;
import java.io.File;
import java.sql.Date;
@@ -48,29 +49,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 String saveFolderName;
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()));
}
@@ -84,8 +80,19 @@
if (params[0] == null) {
return null;
}
+ // Use the default save directory if the source directory cannot be saved.
+ File saveDirectory = getSaveDirectory();
+ if ((saveDirectory == null) || !saveDirectory.canWrite()) {
+ saveDirectory = new File(Environment.getExternalStorageDirectory(),
+ BucketNames.DOWNLOAD);
+ saveFolderName = context.getString(R.string.folder_download);
+ } else {
+ saveFolderName = saveDirectory.getName();
+ }
+
Bitmap bitmap = params[0];
- File file = save(bitmap);
+ 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 +101,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, saveFolderName);
Toast toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
@@ -102,10 +109,33 @@
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 File getSaveDirectory() {
+ final File[] dir = new File[1];
+ querySource(new String[] { ImageColumns.DATA }, new ContentResolverQueryCallback () {
+
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ dir[0] = new File(cursor.getString(0)).getParentFile();
+ }
+ });
+ return dir[0];
}
/**
@@ -113,43 +143,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/YesCancelDialogBuilder.java b/src/com/android/gallery3d/photoeditor/YesCancelDialogBuilder.java
new file mode 100644
index 0000000..4df76e6
--- /dev/null
+++ b/src/com/android/gallery3d/photoeditor/YesCancelDialogBuilder.java
@@ -0,0 +1,31 @@
+package com.android.gallery3d.photoeditor;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+
+import com.android.gallery3d.R;
+
+/**
+ * Alert dialog builder that builds a simple Yes/Cancel dialog.
+ */
+public class YesCancelDialogBuilder extends AlertDialog.Builder {
+
+ public YesCancelDialogBuilder(Context context, final Runnable yes, int messageId) {
+ super(context);
+ setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ yes.run();
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // no-op
+ }
+ }).setMessage(messageId);
+ }
+}
diff --git a/src/com/android/gallery3d/photoeditor/YesNoCancelDialogBuilder.java b/src/com/android/gallery3d/photoeditor/YesNoCancelDialogBuilder.java
deleted file mode 100644
index fcc99c0..0000000
--- a/src/com/android/gallery3d/photoeditor/YesNoCancelDialogBuilder.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.android.gallery3d.photoeditor;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-
-import com.android.gallery3d.R;
-
-/**
- * Alert dialog builder that builds a simple Yes/No/Cancel dialog.
- */
-public class YesNoCancelDialogBuilder extends AlertDialog.Builder {
-
- public YesNoCancelDialogBuilder(Context context, final Runnable yes, final Runnable no,
- int messageId) {
- super(context);
- setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- yes.run();
- }
- })
- .setNeutralButton(R.string.no, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- no.run();
- }
- })
- .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // no-op
- }
- }).setMessage(messageId);
- }
-}
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..746d41d 100644
--- a/src/com/android/gallery3d/ui/ActionModeHandler.java
+++ b/src/com/android/gallery3d/ui/ActionModeHandler.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.nfc.NfcAdapter;
import android.os.Handler;
import android.view.ActionMode;
import android.view.LayoutInflater;
@@ -28,8 +29,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;
@@ -61,6 +62,7 @@
private final GalleryActivity mActivity;
private final MenuExecutor mMenuExecutor;
private final SelectionManager mSelectionManager;
+ private final NfcAdapter mNfcAdapter;
private Menu mMenu;
private DropDownMenu mSelectionMenu;
private ActionModeListener mListener;
@@ -74,6 +76,7 @@
mSelectionManager = Utils.checkNotNull(selectionManager);
mMenuExecutor = new MenuExecutor(activity, selectionManager);
mMainHandler = new Handler(activity.getMainLooper());
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext());
}
public ActionMode startActionMode() {
@@ -88,6 +91,7 @@
R.menu.selection);
updateSelectionMenu();
customMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ @Override
public boolean onMenuItemClick(MenuItem item) {
return onActionItemClicked(actionMode, item);
}
@@ -103,25 +107,41 @@
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;
+ String confirmMsg = null;
+ int action = item.getItemId();
+ if (action == R.id.action_import) {
+ listener = new ImportCompleteListener(mActivity);
+ } else if (item.getItemId() == R.id.action_delete) {
+ confirmMsg = mActivity.getResources().getQuantityString(
+ R.plurals.delete_selection, mSelectionManager.getSelectedCount());
+ }
+ mMenuExecutor.onMenuClicked(item, confirmMsg, 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 +193,53 @@
// 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) {
+ if (mNfcAdapter != null) {
+ mNfcAdapter.setBeamPushUris(null, (Activity)mActivity);
+ }
+ 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 +259,17 @@
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);
- }
- });
+ if (mNfcAdapter != null) {
+ mNfcAdapter.setBeamPushUris(uris.toArray(new Uri[uris.size()]),
+ (Activity)mActivity);
+ }
+ } else {
+ if (mNfcAdapter != null) {
+ mNfcAdapter.setBeamPushUris(null, (Activity)mActivity);
+ }
}
+
+ return intent;
}
public void updateSupportedOperation(Path path, boolean selected) {
@@ -258,21 +278,40 @@
}
public void updateSupportedOperation() {
+ // Interrupt previous unfinished task, mMenuTask is only accessed in main thread
if (mMenuTask != null) {
mMenuTask.cancel();
}
+ updateSelectionMenu();
+
// 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..93b37ce
--- /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() {
+ if (mBitmapPool != null) mBitmapPool.clear();
+ }
+}
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
index 87ff557..6561a23 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,71 @@
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 BitmapTexture labelTexture;
+ public BitmapTexture bitmapTexture;
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 +161,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 +205,139 @@
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();
+ if (entry.labelTexture != null) entry.labelTexture.recycle();
+ if (entry.bitmapTexture != null) entry.bitmapTexture.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.labelTexture = 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.bitmapTexture = 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.bitmapTexture != null) {
+ mTextureUploader.addBgTexture(entry.bitmapTexture);
}
-
- prepareSlotContent(slotIndex);
- AlbumSetItem update = data[pos];
-
- if (mListener != null && isActiveSlot(slotIndex)) {
- mListener.onWindowContentChanged(slotIndex, original, update);
+ if (entry.labelTexture != null) {
+ mTextureUploader.addBgTexture(entry.labelTexture);
}
- 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.bitmapTexture != null) {
+ mTextureUploader.addFgTexture(entry.bitmapTexture);
+ }
+ if (entry.labelTexture != null) {
+ mTextureUploader.addFgTexture(entry.labelTexture);
}
}
- }
- 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 +346,116 @@
}
}
- 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;
+ mTextureUploader.clear();
+ 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.bitmapTexture = texture;
+ 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 +476,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.labelTexture = 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.labelTexture = 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..cca8d40
--- /dev/null
+++ b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java
@@ -0,0 +1,221 @@
+/*
+ * 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(Texture texture) {
+ return ((texture instanceof UploadedTexture)
+ && ((UploadedTexture) texture).isUploading())
+ ? 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(entry.content);
+ if (content == null) {
+ content = mWaitLoadingTexture;
+ entry.isWaitLoadingDisplayed = true;
+ } else if (entry.isWaitLoadingDisplayed) {
+ entry.isWaitLoadingDisplayed = false;
+ content = new FadeInTexture(PLACEHOLDER_COLOR, entry.bitmapTexture);
+ entry.content = 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) {
+ Texture content = checkTexture(entry.labelTexture);
+ 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..deec171 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,37 @@
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 BitmapTexture bitmapTexture;
+ 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 +68,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 +146,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 +158,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.bitmapTexture != null) {
+ mTextureUploader.addBgTexture(entry.bitmapTexture);
+ }
+ }
+ }
+
+ 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.bitmapTexture != null) {
+ mTextureUploader.addFgTexture(entry.bitmapTexture);
+ }
+ }
+
+ // 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 +201,63 @@
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();
+ if (entry.bitmapTexture != null) entry.bitmapTexture.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,69 @@
}
}
- 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.bitmapTexture = new BitmapTexture(bitmap);
+ entry.content = entry.bitmapTexture;
+
+ if (isActiveSlot(mSlotIndex)) {
+ mTextureUploader.addFgTexture(entry.bitmapTexture);
--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(entry.bitmapTexture);
}
}
-
- // 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();
+ }
}
}
@@ -437,9 +342,9 @@
public void pause() {
mIsActive = false;
+ mTextureUploader.clear();
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..ab99766
--- /dev/null
+++ b/src/com/android/gallery3d/ui/AlbumSlotRenderer.java
@@ -0,0 +1,197 @@
+/*
+ * 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 {
+ @SuppressWarnings("unused")
+ private static final String TAG = "AlbumView";
+
+ public interface SlotFilter {
+ public boolean acceptSlot(int index);
+ }
+
+ private static final int PLACEHOLDER_COLOR = 0xFF222222;
+ 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;
+
+ private SlotFilter mSlotFilter;
+
+ 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(Texture texture) {
+ return (texture instanceof UploadedTexture)
+ && ((UploadedTexture) texture).isUploading()
+ ? null
+ : texture;
+ }
+
+ @Override
+ public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) {
+ if (mSlotFilter != null && !mSlotFilter.acceptSlot(index)) return 0;
+
+ AlbumSlidingWindow.AlbumEntry entry = mDataWindow.get(index);
+
+ int renderRequestFlags = 0;
+
+ Texture content = checkTexture(entry.content);
+ if (content == null) {
+ content = mWaitLoadingTexture;
+ entry.isWaitDisplayed = true;
+ } else if (entry.isWaitDisplayed) {
+ entry.isWaitDisplayed = false;
+ content = new FadeInTexture(PLACEHOLDER_COLOR, entry.bitmapTexture);
+ entry.content = 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
+ }
+
+ public void setSlotFilter(SlotFilter slotFilter) {
+ mSlotFilter = slotFilter;
+ }
+}
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..7855927 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.
@@ -34,6 +33,8 @@
protected static final int STATE_LOADED = 1;
protected static final int STATE_ERROR = -1;
+ private static final int MAX_TEXTURE_SIZE = 2048;
+
protected int mId;
protected int mState;
@@ -45,7 +46,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 +65,7 @@
}
protected void setAssociatedCanvas(GLCanvas canvas) {
- mCanvasRef = canvas == null
- ? null
- : new WeakReference<GLCanvas>(canvas);
+ mCanvasRef = canvas;
}
/**
@@ -78,6 +77,10 @@
mHeight = height;
mTextureWidth = Utils.nextPowerOf2(width);
mTextureHeight = Utils.nextPowerOf2(height);
+ if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
+ Log.w(TAG, String.format("texture is too large: %d x %d",
+ mTextureWidth, mTextureHeight), new Exception());
+ }
}
public int getId() {
@@ -133,8 +136,11 @@
// It should make sure the data is uploaded to GL memory.
abstract protected boolean onBind(GLCanvas canvas);
- public boolean isLoaded(GLCanvas canvas) {
- return mState == STATE_LOADED && mCanvasRef.get() == canvas;
+ // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
+ abstract protected int getTarget();
+
+ public boolean isLoaded() {
+ return mState == STATE_LOADED;
}
// recycle() is called when the texture will never be used again,
@@ -153,11 +159,11 @@
}
private void freeResource() {
- GLCanvas canvas = mCanvasRef == null ? null : mCanvasRef.get();
- if (canvas != null && isLoaded(canvas)) {
+ GLCanvas canvas = mCanvasRef;
+ if (canvas != null && isLoaded()) {
canvas.unloadTexture(this);
}
- mState = BasicTexture.STATE_UNLOADED;
+ mState = STATE_UNLOADED;
setAssociatedCanvas(null);
}
@@ -182,4 +188,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..5c91757
--- /dev/null
+++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java
@@ -0,0 +1,201 @@
+/*
+ * 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 com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.MediaItem;
+
+// This is a ScreenNail wraps a Bitmap. There are some extra functions:
+//
+// - If we need to draw before the bitmap is available, we draw a rectange of
+// placeholder color (gray).
+//
+// - When the the bitmap is available, and we have drawn the placeholder color
+// before, we will do a fade-in animation.
+public class BitmapScreenNail implements ScreenNail {
+ private static final String TAG = "BitmapScreenNail";
+ private static final int PLACEHOLDER_COLOR = 0xFF222222;
+ // The duration of the fading animation in milliseconds
+ private static final int DURATION = 180;
+
+ private static final int MAX_SIDE = 640;
+
+ // These are special values for mAnimationStartTime
+ private static final long ANIMATION_NOT_NEEDED = -1;
+ private static final long ANIMATION_NEEDED = -2;
+ private static final long ANIMATION_DONE = -3;
+
+ private int mWidth;
+ private int mHeight;
+ private Bitmap mBitmap;
+ private BitmapTexture mTexture;
+ private long mAnimationStartTime = ANIMATION_NOT_NEEDED;
+
+ 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.
+ }
+
+ public BitmapScreenNail(int width, int height) {
+ setSize(width, height);
+ }
+
+ private void setSize(int width, int height) {
+ if (width == 0 || height == 0) {
+ width = 640;
+ height = 480;
+ }
+ float scale = Math.min(1, (float) MAX_SIDE / Math.max(width, height));
+ mWidth = Math.round(scale * width);
+ mHeight = Math.round(scale * height);
+ }
+
+ // Combines the two ScreenNails.
+ // Returns the used one and recycle the unused one.
+ public ScreenNail combine(ScreenNail other) {
+ if (other == null) {
+ return this;
+ }
+
+ if (!(other instanceof BitmapScreenNail)) {
+ recycle();
+ return other;
+ }
+
+ // Now both are BitmapScreenNail. Move over the information about width,
+ // height, and Bitmap, then recycle the other.
+ BitmapScreenNail newer = (BitmapScreenNail) other;
+ mWidth = newer.mWidth;
+ mHeight = newer.mHeight;
+ if (newer.mBitmap != null) {
+ if (mBitmap != null) {
+ MediaItem.getThumbPool().recycle(mBitmap);
+ }
+ mBitmap = newer.mBitmap;
+ newer.mBitmap = null;
+
+ if (mTexture != null) {
+ mTexture.recycle();
+ mTexture = null;
+ }
+ }
+
+ newer.recycle();
+ return this;
+ }
+
+ public void updatePlaceholderSize(int width, int height) {
+ if (mBitmap != null) return;
+ if (width == 0 || height == 0) return;
+ setSize(width, height);
+ }
+
+ @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 (mBitmap == null) {
+ if (mAnimationStartTime == ANIMATION_NOT_NEEDED) {
+ mAnimationStartTime = ANIMATION_NEEDED;
+ }
+ canvas.fillRect(x, y, width, height, PLACEHOLDER_COLOR);
+ return;
+ }
+
+ if (mTexture == null) {
+ mTexture = new BitmapTexture(mBitmap);
+ }
+
+ if (mAnimationStartTime == ANIMATION_NEEDED) {
+ mAnimationStartTime = now();
+ }
+
+ if (isAnimating()) {
+ canvas.drawMixed(mTexture, PLACEHOLDER_COLOR, getRatio(), x, y,
+ width, height);
+ } else {
+ mTexture.draw(canvas, x, y, width, height);
+ }
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, RectF source, RectF dest) {
+ if (mBitmap == null) {
+ canvas.fillRect(dest.left, dest.top, dest.width(), dest.height(),
+ PLACEHOLDER_COLOR);
+ return;
+ }
+
+ if (mTexture == null) {
+ mTexture = new BitmapTexture(mBitmap);
+ }
+
+ canvas.drawTexture(mTexture, source, dest);
+ }
+
+ public boolean isAnimating() {
+ if (mAnimationStartTime < 0) return false;
+ if (now() - mAnimationStartTime >= DURATION) {
+ mAnimationStartTime = ANIMATION_DONE;
+ return false;
+ }
+ return true;
+ }
+
+ private static long now() {
+ return AnimationTime.get();
+ }
+
+ private float getRatio() {
+ float r = (float)(now() - mAnimationStartTime) / DURATION;
+ return Utils.clamp(1.0f - r, 0.0f, 1.0f);
+ }
+
+ public boolean isShowingPlaceholder() {
+ return (mBitmap == null) || isAnimating();
+ }
+}
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..d4c9b1d 100644
--- a/src/com/android/gallery3d/ui/BitmapTileProvider.java
+++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java
@@ -16,16 +16,17 @@
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 com.android.gallery3d.data.BitmapPool;
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,49 +45,51 @@
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;
+ @Override
+ public ScreenNail getScreenNail() {
+ return mScreenNail;
}
+ @Override
public int getImageHeight() {
return mImageHeight;
}
+ @Override
public int getImageWidth() {
return mImageWidth;
}
+ @Override
public int getLevelCount() {
return mMipmaps.length;
}
+ @Override
public Bitmap getTile(int level, int x, int y, int tileSize,
- int borderSize) {
+ int borderSize, BitmapPool pool) {
x >>= level;
y >>= level;
int size = tileSize + 2 * borderSize;
- Bitmap result = Bitmap.createBitmap(size, size, mConfig);
+
+ Bitmap result = pool == null ? null : pool.getBitmap();
+ if (result == null) {
+ result = Bitmap.createBitmap(size, size, mConfig);
+ } else {
+ result.eraseColor(0);
+ }
+
Bitmap mipmap = mMipmaps[level];
Canvas canvas = new Canvas(result);
int offsetX = -x + borderSize;
int offsetY = -y + borderSize;
canvas.drawBitmap(mipmap, offsetX, offsetY, null);
-
- // If the valid region (covered by mipmap or border) is smaller than the
- // result bitmap, subset it.
- int endX = offsetX + mipmap.getWidth() + borderSize;
- int endY = offsetY + mipmap.getHeight() + borderSize;
- if (endX < size || endY < size) {
- return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX),
- Math.min(size, endY));
- } else {
- return result;
- }
+ return result;
}
public void recycle() {
@@ -95,14 +98,8 @@
for (Bitmap bitmap : mMipmaps) {
BitmapUtils.recycleSilently(bitmap);
}
- BitmapUtils.recycleSilently(mBackup);
- }
-
- public int getRotation() {
- return 0;
- }
-
- public boolean isFailedToLoad() {
- return false;
+ if (mScreenNail != null) {
+ mScreenNail.recycle();
+ }
}
}
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..9319d8c
--- /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 = STATE_LOADED;
+ }
+
+ @Override
+ protected boolean onBind(GLCanvas canvas) {
+ if (!isLoaded()) {
+ 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..47e6acb
--- /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 {
+ @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..cbf5073
--- /dev/null
+++ b/src/com/android/gallery3d/ui/FadeTexture.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, int x, int y) {
+ draw(canvas, x, y, mWidth, mHeight);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return mIsOpaque;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ 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..c12a9f7 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,10 @@
// called in the GL thread.
public void deleteRecycledResources();
+ // Dump statistics information and clear the counters. For debug only.
+ public void dumpStatisticsAndClear();
+
+ public void beginRenderTarget(RawTexture texture);
+
+ public void endRenderTarget();
}
diff --git a/src/com/android/gallery3d/ui/GLCanvasImpl.java b/src/com/android/gallery3d/ui/GLCanvasImpl.java
index 612c7c4..ff3e9e5 100644
--- a/src/com/android/gallery3d/ui/GLCanvasImpl.java
+++ b/src/com/android/gallery3d/ui/GLCanvasImpl.java
@@ -16,21 +16,22 @@
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;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
public class GLCanvasImpl implements GLCanvas {
@SuppressWarnings("unused")
@@ -51,21 +52,19 @@
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];
private int mBoxCoords;
private final GLState mGLState;
-
- private long mAnimationTime;
+ private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
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();
@@ -73,8 +72,12 @@
private final float[] mTempMatrix = new float[32];
private final IntArray mUnboundTextures = new IntArray();
private final IntArray mDeleteBuffers = new IntArray();
- private int mHeight;
+ private int mScreenWidth;
+ private int mScreenHeight;
private boolean mBlendEnabled = true;
+ private int mFrameBuffer[] = new int[1];
+
+ private RawTexture mTargetTexture;
// Drawing statistics
int mCountDrawLine;
@@ -91,7 +94,12 @@
public void setSize(int width, int height) {
Utils.assertTrue(width >= 0 && height >= 0);
- mHeight = height;
+
+ if (mTargetTexture == null) {
+ mScreenWidth = width;
+ mScreenHeight = height;
+ }
+ mAlpha = 1.0f;
GL11 gl = mGL;
gl.glViewport(0, 0, width, height);
@@ -101,18 +109,14 @@
gl.glMatrixMode(GL11.GL_MODELVIEW);
gl.glLoadIdentity();
+
float matrix[] = mMatrixValues;
-
Matrix.setIdentityM(matrix, 0);
- 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;
+ // to match the graphic coordinate system in android, we flip it vertically.
+ if (mTargetTexture == null) {
+ Matrix.translateM(matrix, 0, 0, height, 0);
+ Matrix.scaleM(matrix, 0, 1, -1, 1);
+ }
}
public void setAlpha(float alpha) {
@@ -120,15 +124,15 @@
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 +146,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);
@@ -159,8 +163,7 @@
gl.glClientActiveTexture(GL11.GL_TEXTURE0);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
- // mMatrixValues will be initialized in setSize()
- mAlpha = 1.0f;
+ // mMatrixValues and mAlpha will be initialized in setSize()
}
public void drawRect(float x, float y, float width, float height, GLPaint paint) {
@@ -168,10 +171,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 +188,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 +205,7 @@
GL11 gl = mGL;
saveTransform();
- translate(x, y, 0);
+ translate(x, y);
scale(width, height, 1);
gl.glLoadMatrixf(mMatrixValues, 0);
@@ -218,11 +219,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 +252,7 @@
GL11 gl = mGL;
saveTransform();
- translate(x, y, 0);
+ translate(x, y);
scale(width, height, 1);
gl.glLoadMatrixf(mMatrixValues, 0);
@@ -263,7 +276,7 @@
setTextureCoords(0, 0, 1, 1);
saveTransform();
- translate(x, y, 0);
+ translate(x, y);
mGL.glLoadMatrixf(mMatrixValues, 0);
@@ -285,55 +298,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 +341,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 +357,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 +386,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 +432,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 +450,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 +468,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 +509,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 +523,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;
@@ -673,7 +542,6 @@
// Enable used features
gl.glEnable(GL11.GL_DITHER);
- gl.glEnable(GL11.GL_SCISSOR_TEST);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
@@ -705,16 +573,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 +593,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 +603,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 +630,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,11 +651,17 @@
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) {
synchronized (mUnboundTextures) {
- if (!t.isLoaded(this)) return false;
+ if (!t.isLoaded()) return false;
mUnboundTextures.add(t.mId);
return true;
}
@@ -816,23 +677,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 +702,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 +709,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);
}
@@ -880,18 +735,11 @@
private static class ConfigState {
float mAlpha;
- Rect mRect = new Rect();
float mMatrix[] = new float[16];
ConfigState mNextFree;
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);
}
@@ -918,4 +766,79 @@
private void restoreTransform() {
System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16);
}
+
+ private void setRenderTarget(RawTexture texture) {
+ GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL;
+
+ if (mTargetTexture == null && texture != null) {
+ GLId.glGenBuffers(1, mFrameBuffer, 0);
+ gl11ep.glBindFramebufferOES(
+ GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]);
+ }
+ if (mTargetTexture != null && texture == null) {
+ gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0);
+ gl11ep.glDeleteFramebuffersOES(1, mFrameBuffer, 0);
+ }
+
+ mTargetTexture = texture;
+ if (texture == null) {
+ setSize(mScreenWidth, mScreenHeight);
+ } else {
+ setSize(texture.getWidth(), texture.getHeight());
+
+ if (!texture.isLoaded()) texture.prepare(this);
+
+ gl11ep.glFramebufferTexture2DOES(
+ GL11ExtensionPack.GL_FRAMEBUFFER_OES,
+ GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES,
+ GL11.GL_TEXTURE_2D, texture.getId(), 0);
+
+ checkFramebufferStatus(gl11ep);
+ }
+ }
+
+ @Override
+ public void endRenderTarget() {
+ RawTexture texture = mTargetStack.remove(mTargetStack.size() - 1);
+ setRenderTarget(texture);
+ restore(); // restore matrix and alpha
+ }
+
+ @Override
+ public void beginRenderTarget(RawTexture texture) {
+ save(); // save matrix and alpha
+ mTargetStack.add(mTargetTexture);
+ setRenderTarget(texture);
+ }
+
+ 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/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..1651b43 100644
--- a/src/com/android/gallery3d/ui/GLRoot.java
+++ b/src/com/android/gallery3d/ui/GLRoot.java
@@ -16,22 +16,33 @@
package com.android.gallery3d.ui;
+import android.graphics.Matrix;
+
import com.android.gallery3d.anim.CanvasAnimation;
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();
public void setContentPane(GLView content);
+ public void setOrientationSource(OrientationSource source);
+ public int getDisplayRotation();
+ public int getCompensation();
+ public Matrix getCompensationMatrix();
+ public void freeze();
+ public void unfreeze();
+ public void setLightsOutMode(boolean enabled);
}
diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java
index f2140bf..99ed8cb 100644
--- a/src/com/android/gallery3d/ui/GLRootView.java
+++ b/src/com/android/gallery3d/ui/GLRootView.java
@@ -16,24 +16,28 @@
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.Matrix;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.opengl.GLSurfaceView;
import android.os.Process;
import android.os.SystemClock;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import com.android.gallery3d.R;
+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.Condition;
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,37 +63,50 @@
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 OrientationSource mOrientationSource;
+ // mCompensation is the difference between the UI orientation on GLCanvas
+ // and the framework orientation. See OrientationManager for details.
+ private int mCompensation;
+ // mCompensationMatrix maps the coordinates of touch events. It is kept sync
+ // with mCompensation.
+ private Matrix mCompensationMatrix = new Matrix();
+ private int mDisplayRotation;
+
+ // The value which will become mCompensation in next layout.
+ private int mPendingCompensation;
private int mFlags = FLAG_NEED_LAYOUT;
private volatile boolean mRenderRequested = false;
- private Rect mClipRect = new Rect();
- private int mClipRetryCount = 0;
-
private final GalleryEGLConfigChooser mEglConfigChooser =
new GalleryEGLConfigChooser();
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 final Condition mFreezeCondition =
+ mRenderLock.newCondition();
+ private boolean mFreeze;
- private static final int TARGET_FRAME_TIME = 16;
private long mLastDrawFinishTime;
private boolean mInDownState = false;
+ private boolean mFirstDraw = true;
public GLRootView(Context context) {
this(context, null);
@@ -104,16 +121,7 @@
getHolder().setFormat(PixelFormat.RGB_565);
// Uncomment this to enable gl error check.
- //setDebugFlags(DEBUG_CHECK_GL_ERROR);
- }
-
- public GalleryEGLConfigChooser getEGLConfigChooser() {
- return mEglConfigChooser;
- }
-
- @Override
- public boolean hasStencil() {
- return getEGLConfigChooser().getStencilBits() > 0;
+ // setDebugFlags(DEBUG_CHECK_GL_ERROR);
}
@Override
@@ -155,10 +163,6 @@
}
}
- public GLView getContentPane() {
- return mContentView;
- }
-
@Override
public void requestRender() {
if (DEBUG_INVALIDATE) {
@@ -190,11 +194,45 @@
private void layoutContentPane() {
mFlags &= ~FLAG_NEED_LAYOUT;
- int width = getWidth();
- int height = getHeight();
- Log.i(TAG, "layout content pane " + width + "x" + height);
- if (mContentView != null && width != 0 && height != 0) {
- mContentView.layout(0, 0, width, height);
+
+ int w = getWidth();
+ int h = getHeight();
+ int displayRotation = 0;
+ int compensation = 0;
+
+ // Get the new orientation values
+ if (mOrientationSource != null) {
+ displayRotation = mOrientationSource.getDisplayRotation();
+ compensation = mOrientationSource.getCompensation();
+ } else {
+ displayRotation = 0;
+ compensation = 0;
+ }
+
+ if (mCompensation != compensation) {
+ mCompensation = compensation;
+ if (mCompensation % 180 != 0) {
+ mCompensationMatrix.setRotate(mCompensation);
+ // move center to origin before rotation
+ mCompensationMatrix.preTranslate(-w / 2, -h / 2);
+ // align with the new origin after rotation
+ mCompensationMatrix.postTranslate(h / 2, w / 2);
+ } else {
+ mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
+ }
+ }
+ mDisplayRotation = displayRotation;
+
+ // Do the actual layout.
+ if (mCompensation % 180 != 0) {
+ int tmp = w;
+ w = h;
+ h = tmp;
+ }
+ Log.i(TAG, "layout content pane " + w + "x" + h
+ + " (compensation " + mCompensation + ")");
+ if (mContentView != null && w != 0 && h != 0) {
+ mContentView.layout(0, 0, w, h);
}
// Uncomment this to dump the view hierarchy.
//mContentView.dumpTree("");
@@ -217,12 +255,19 @@
// The GL Object has changed
Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
}
- mGL = gl;
- mCanvas = new GLCanvasImpl(gl);
- if (!DEBUG_FPS) {
- setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
- } else {
+ mRenderLock.lock();
+ try {
+ mGL = gl;
+ mCanvas = new GLCanvasImpl(gl);
+ BasicTexture.invalidateAllTextures();
+ } finally {
+ mRenderLock.unlock();
+ }
+
+ if (DEBUG_FPS || DEBUG_PROFILE) {
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ } else {
+ setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
@@ -237,13 +282,14 @@
+ ", gl10: " + gl1.toString());
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
GalleryUtils.setRenderThread();
+ if (DEBUG_PROFILE) {
+ Log.d(TAG, "Start profiling");
+ Profile.enable(20); // take a sample every 20ms
+ }
GL11 gl = (GL11) gl1;
Utils.assertTrue(mGL == gl);
mCanvas.setSize(width, height);
-
- mClipRect.set(0, 0, width, height);
- mClipRetryCount = 2;
}
private void outputFps() {
@@ -261,21 +307,52 @@
@Override
public void onDrawFrame(GL10 gl) {
+ AnimationTime.update();
+ long t0;
+ if (DEBUG_PROFILE_SLOW_ONLY) {
+ Profile.hold();
+ t0 = System.nanoTime();
+ }
mRenderLock.lock();
+
+ while (mFreeze) {
+ mFreezeCondition.awaitUninterruptibly();
+ }
+
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);
+ // We put a black cover View in front of the SurfaceView and hide it
+ // after the first draw. This prevents the SurfaceView being transparent
+ // before the first draw.
+ if (mFirstDraw) {
+ mFirstDraw = false;
+ post(new Runnable() {
+ public void run() {
+ View root = getRootView();
+ View cover = root.findViewById(R.id.gl_root_cover);
+ cover.setVisibility(GONE);
+ }
+ });
+ }
+
+ 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) {
@@ -291,22 +368,15 @@
if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
- // OpenGL seems having a bug causing us not being able to reset the
- // scissor box in "onSurfaceChanged()". We have to do it in the second
- // onDrawFrame().
- if (mClipRetryCount > 0) {
- --mClipRetryCount;
- Rect clip = mClipRect;
- gl.glScissor(clip.left, clip.top, clip.width(), clip.height());
- }
-
- mCanvas.setCurrentAnimationTimeMillis(SystemClock.uptimeMillis());
+ mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
+ rotateCanvas(-mCompensation);
if (mContentView != null) {
mContentView.render(mCanvas);
}
+ mCanvas.restore();
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 +388,7 @@
}
synchronized (mIdleListeners) {
- if (!mRenderRequested && !mIdleListeners.isEmpty()) {
- mIdleRunner.enable();
- }
+ if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
}
if (DEBUG_INVALIDATE) {
@@ -333,8 +401,25 @@
}
}
+ private void rotateCanvas(int degrees) {
+ if (degrees == 0) return;
+ int w = getWidth();
+ int h = getHeight();
+ int cx = w / 2;
+ int cy = h / 2;
+ mCanvas.translate(cx, cy);
+ mCanvas.rotate(degrees, 0, 0, 1);
+ if (degrees % 180 != 0) {
+ mCanvas.translate(-cy, -cx);
+ } else {
+ mCanvas.translate(-cx, -cy);
+ }
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
+ if (!isEnabled()) return false;
+
int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_UP) {
@@ -342,6 +427,11 @@
} else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
return false;
}
+
+ if (mCompensation != 0) {
+ event.transform(mCompensationMatrix);
+ }
+
mRenderLock.lock();
try {
// If this has been detached from root, we don't need to handle event
@@ -356,19 +446,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 +455,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 +487,98 @@
public void unlockRenderThread() {
mRenderLock.unlock();
}
+
+ @Override
+ public void onPause() {
+ unfreeze();
+ super.onPause();
+ if (DEBUG_PROFILE) {
+ Log.d(TAG, "Stop profiling");
+ Profile.disableAll();
+ Profile.dumpToFile("/sdcard/gallery.prof");
+ Profile.reset();
+ }
+ }
+
+ @Override
+ public void setOrientationSource(OrientationSource source) {
+ mOrientationSource = source;
+ }
+
+ @Override
+ public int getDisplayRotation() {
+ return mDisplayRotation;
+ }
+
+ @Override
+ public int getCompensation() {
+ return mCompensation;
+ }
+
+ @Override
+ public Matrix getCompensationMatrix() {
+ return mCompensationMatrix;
+ }
+
+ @Override
+ public void freeze() {
+ mRenderLock.lock();
+ mFreeze = true;
+ mRenderLock.unlock();
+ }
+
+ @Override
+ public void unfreeze() {
+ mRenderLock.lock();
+ mFreeze = false;
+ mFreezeCondition.signalAll();
+ mRenderLock.unlock();
+ }
+
+ @Override
+ public void setLightsOutMode(boolean enabled) {
+ int flags = enabled
+ ? SYSTEM_UI_FLAG_LOW_PROFILE
+ | SYSTEM_UI_FLAG_FULLSCREEN
+ | SYSTEM_UI_FLAG_LAYOUT_STABLE
+ : 0;
+ setSystemUiVisibility(flags);
+ }
+
+ // We need to unfreeze in the following methods and in onPause().
+ // These methods will wait on GLThread. If we have freezed the GLRootView,
+ // the GLThread will wait on main thread to call unfreeze and cause dead
+ // lock.
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ unfreeze();
+ super.surfaceChanged(holder, format, w, h);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ unfreeze();
+ super.surfaceCreated(holder);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ unfreeze();
+ super.surfaceDestroyed(holder);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ unfreeze();
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ unfreeze();
+ } finally {
+ super.finalize();
+ }
+ }
}
diff --git a/src/com/android/gallery3d/ui/GLView.java b/src/com/android/gallery3d/ui/GLView.java
index 7491a6f..bb71312 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,23 +303,14 @@
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) {
- mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
- onLayout(true, left, top, right, bottom);
- } else if ((mViewFlags & FLAG_LAYOUT_REQUESTED)!= 0) {
- mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
- onLayout(false, left, top, right, bottom);
- }
+ mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
+ // We call onLayout no matter sizeChanged is true or not because the
+ // orientation may change without changing the size of the View (for
+ // example, rotate the device by 180 degrees), and we want to handle
+ // orientation change in onLayout.
+ onLayout(sizeChanged, left, top, right, bottom);
}
private boolean setBounds(int left, int top, int right, int bottom) {
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..63d6167 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;
@@ -33,6 +31,7 @@
mActivity = galleryActivity;
}
+ @Override
public void onProgressComplete(int result) {
int message;
if (result == MenuExecutor.EXECUTION_RESULT_SUCCESS) {
@@ -44,6 +43,7 @@
Toast.makeText(mActivity.getAndroidContext(), message, Toast.LENGTH_LONG).show();
}
+ @Override
public void onProgressUpdate(int index) {
}
@@ -54,4 +54,11 @@
mActivity.getStateManager().startState(AlbumPage.class, data);
}
+ @Override
+ public void onConfirmDialogDismissed(boolean confirmed) {
+ }
+
+ @Override
+ public void onConfirmDialogShown() {
+ }
}
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..7de07e8 100644
--- a/src/com/android/gallery3d/ui/MenuExecutor.java
+++ b/src/com/android/gallery3d/ui/MenuExecutor.java
@@ -17,14 +17,17 @@
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.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
-import android.widget.Toast;
import com.android.gallery3d.R;
import com.android.gallery3d.app.CropImage;
@@ -75,6 +78,8 @@
}
public interface ProgressListener {
+ public void onConfirmDialogShown();
+ public void onConfirmDialogDismissed(boolean confirmed);
public void onProgressUpdate(int index);
public void onProgressComplete(int result);
}
@@ -172,10 +177,15 @@
return ids.get(0);
}
- public boolean onMenuClicked(MenuItem menuItem, ProgressListener listener) {
- int title;
+ private Intent getIntentBySingleSelectedPath(String action) {
DataManager manager = mActivity.getDataManager();
- int action = menuItem.getItemId();
+ Path path = getSingleSelectedPath();
+ String mimeType = getMimeType(manager.getMediaType(path));
+ return new Intent(action).setDataAndType(manager.getContentUri(path), mimeType);
+ }
+
+ private void onMenuClicked(int action, ProgressListener listener) {
+ int title;
switch (action) {
case R.id.action_select_all:
if (mSelectionManager.inSelectAllMode()) {
@@ -183,29 +193,28 @@
} else {
mSelectionManager.selectAll();
}
- return true;
+ return;
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);
+ Intent intent = getIntentBySingleSelectedPath(CropImage.ACTION_CROP);
((Activity) mActivity).startActivity(intent);
- return true;
+ return;
+ }
+ case R.id.action_edit: {
+ Intent intent = getIntentBySingleSelectedPath(Intent.ACTION_EDIT)
+ .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ ((Activity) mActivity).startActivity(Intent.createChooser(intent, null));
+ return;
}
case R.id.action_setas: {
- Path path = getSingleSelectedPath();
- int type = manager.getMediaType(path);
- Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
- String mimeType = getMimeType(type);
- intent.setDataAndType(manager.getContentUri(path), mimeType);
- intent.putExtra("mimeType", mimeType);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ Intent intent = getIntentBySingleSelectedPath(Intent.ACTION_ATTACH_DATA)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.putExtra("mimeType", intent.getType());
Activity activity = (Activity) mActivity;
activity.startActivity(Intent.createChooser(
intent, activity.getString(R.string.set_as)));
- return true;
+ return;
}
- case R.id.action_confirm_delete:
+ case R.id.action_delete:
title = R.string.delete;
break;
case R.id.action_rotate_cw:
@@ -217,17 +226,62 @@
case R.id.action_show_on_map:
title = R.string.show_on_map;
break;
- case R.id.action_edit:
- title = R.string.edit;
- break;
case R.id.action_import:
title = R.string.Import;
break;
default:
- return false;
+ return;
}
startAction(action, title, listener);
- return true;
+ }
+
+ private class ConfirmDialogListener implements OnClickListener, OnCancelListener {
+ private final int mActionId;
+ private final ProgressListener mListener;
+
+ public ConfirmDialogListener(int actionId, ProgressListener listener) {
+ mActionId = actionId;
+ mListener = listener;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ if (mListener != null) {
+ mListener.onConfirmDialogDismissed(true);
+ }
+ onMenuClicked(mActionId, mListener);
+ } else {
+ if (mListener != null) {
+ mListener.onConfirmDialogDismissed(false);
+ }
+ }
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ if (mListener != null) {
+ mListener.onConfirmDialogDismissed(false);
+ }
+ }
+ }
+
+ public void onMenuClicked(MenuItem menuItem, String confirmMsg,
+ final ProgressListener listener) {
+ final int action = menuItem.getItemId();
+
+ if (confirmMsg != null) {
+ if (listener != null) listener.onConfirmDialogShown();
+ ConfirmDialogListener cdl = new ConfirmDialogListener(action, listener);
+ new AlertDialog.Builder(mActivity.getAndroidContext())
+ .setMessage(confirmMsg)
+ .setOnCancelListener(cdl)
+ .setPositiveButton(R.string.ok, cdl)
+ .setNegativeButton(R.string.cancel, cdl)
+ .create().show();
+ } else {
+ onMenuClicked(action, listener);
+ }
}
public void startAction(int action, int title, ProgressListener listener) {
@@ -257,7 +311,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:
@@ -291,23 +345,6 @@
result = obj.Import();
break;
}
- case R.id.action_edit: {
- Activity activity = (Activity) mActivity;
- MediaItem item = (MediaItem) manager.getMediaObject(path);
- try {
- activity.startActivity(Intent.createChooser(
- new Intent(Intent.ACTION_EDIT)
- .setDataAndType(item.getContentUri(), item.getMimeType())
- .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION),
- null));
- } catch (Throwable t) {
- Log.w(TAG, "failed to start edit activity: ", t);
- Toast.makeText(activity,
- activity.getString(R.string.activity_not_found),
- Toast.LENGTH_SHORT).show();
- }
- break;
- }
default:
throw new AssertionError();
}
@@ -352,4 +389,3 @@
}
}
}
-
diff --git a/src/com/android/gallery3d/ui/NinePatchTexture.java b/src/com/android/gallery3d/ui/NinePatchTexture.java
index 15b057a..fa0e9cd 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);
}
@@ -120,7 +157,7 @@
@Override
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
- if (!isLoaded(canvas)) {
+ if (!isLoaded()) {
mInstanceCache.clear();
}
@@ -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/OrientationSource.java
similarity index 78%
rename from src/com/android/gallery3d/ui/OnSelectedListener.java
rename to src/com/android/gallery3d/ui/OrientationSource.java
index 2cc5809..e13ce1c 100644
--- a/src/com/android/gallery3d/ui/OnSelectedListener.java
+++ b/src/com/android/gallery3d/ui/OrientationSource.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.
@@ -16,6 +16,7 @@
package com.android.gallery3d.ui;
-public interface OnSelectedListener {
- public void onSelected(GLView source);
+public interface OrientationSource {
+ public int getDisplayRotation();
+ public int getCompensation();
}
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/PhotoFallbackEffect.java b/src/com/android/gallery3d/ui/PhotoFallbackEffect.java
new file mode 100644
index 0000000..3ca09ab
--- /dev/null
+++ b/src/com/android/gallery3d/ui/PhotoFallbackEffect.java
@@ -0,0 +1,177 @@
+/*
+ * 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 android.graphics.RectF;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.gallery3d.anim.Animation;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.ui.AlbumSlotRenderer.SlotFilter;
+
+import java.util.ArrayList;
+
+public class PhotoFallbackEffect extends Animation implements SlotFilter {
+
+ private static final int ANIM_DURATION = 300;
+ private static final Interpolator ANIM_INTERPOLATE = new DecelerateInterpolator(1.5f);
+
+ public static class Entry {
+ public int index;
+ public Path path;
+ public Rect source;
+ public Rect dest;
+ public RawTexture texture;
+
+ public Entry(Path path, Rect source, RawTexture texture) {
+ this.path = path;
+ this.source = source;
+ this.texture = texture;
+ }
+ }
+
+ public interface PositionProvider {
+ public Rect getPosition(int index);
+ public int getItemIndex(Path path);
+ }
+
+ private RectF mSource = new RectF();
+ private RectF mTarget = new RectF();
+ private float mProgress;
+ private PositionProvider mPositionProvider;
+
+ private ArrayList<Entry> mList = new ArrayList<Entry>();
+
+ public PhotoFallbackEffect() {
+ setDuration(ANIM_DURATION);
+ setInterpolator(ANIM_INTERPOLATE);
+ }
+
+ public void addEntry(Path path, Rect rect, RawTexture texture) {
+ mList.add(new Entry(path, rect, texture));
+ }
+
+ public Entry getEntry(Path path) {
+ for (int i = 0, n = mList.size(); i < n; ++i) {
+ Entry entry = mList.get(i);
+ if (entry.path == path) return entry;
+ }
+ return null;
+ }
+
+ public boolean draw(GLCanvas canvas) {
+ boolean more = calculate(AnimationTime.get());
+ for (int i = 0, n = mList.size(); i < n; ++i) {
+ Entry entry = mList.get(i);
+ if (entry.index < 0) continue;
+ entry.dest = mPositionProvider.getPosition(entry.index);
+ drawEntry(canvas, entry);
+ }
+ return more;
+ }
+
+ private void drawEntry(GLCanvas canvas, Entry entry) {
+ if (!entry.texture.isLoaded()) return;
+
+ int w = entry.texture.getWidth();
+ int h = entry.texture.getHeight();
+
+ Rect s = entry.source;
+ Rect d = entry.dest;
+
+ // the following calculation is based on d.width() == d.height()
+
+ float p = mProgress;
+
+ float fullScale = (float) d.height() / Math.min(s.width(), s.height());
+ float scale = fullScale * p + 1 * (1 - p);
+
+ float cx = d.centerX() * p + s.centerX() * (1 - p);
+ float cy = d.centerY() * p + s.centerY() * (1 - p);
+
+ float ch = s.height() * scale;
+ float cw = s.width() * scale;
+
+ if (w > h) {
+ // draw the center part
+ mTarget.set(cx - ch / 2, cy - ch / 2, cx + ch / 2, cy + ch / 2);
+ mSource.set((w - h) / 2, 0, (w + h) / 2, h);
+ canvas.drawTexture(entry.texture, mSource, mTarget);
+
+ canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
+ canvas.multiplyAlpha(1 - p);
+
+ // draw the left part
+ mTarget.set(cx - cw / 2, cy - ch / 2, cx - ch / 2, cy + ch / 2);
+ mSource.set(0, 0, (w - h) / 2, h);
+ canvas.drawTexture(entry.texture, mSource, mTarget);
+
+ // draw the right part
+ mTarget.set(cx + ch / 2, cy - ch / 2, cx + cw / 2, cy + ch / 2);
+ mSource.set((w + h) / 2, 0, w, h);
+ canvas.drawTexture(entry.texture, mSource, mTarget);
+
+ canvas.restore();
+ } else {
+ // draw the center part
+ mTarget.set(cx - cw / 2, cy - cw / 2, cx + cw / 2, cy + cw / 2);
+ mSource.set(0, (h - w) / 2, w, (h + w) / 2);
+ canvas.drawTexture(entry.texture, mSource, mTarget);
+
+ canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
+ canvas.multiplyAlpha(1 - p);
+
+ // draw the upper part
+ mTarget.set(cx - cw / 2, cy - ch / 2, cx + cw / 2, cy - cw / 2);
+ mSource.set(0, 0, w, (h - w) / 2);
+ canvas.drawTexture(entry.texture, mSource, mTarget);
+
+ // draw the bottom part
+ mTarget.set(cx - cw / 2, cy + cw / 2, cx + cw / 2, cy + ch / 2);
+ mSource.set(0, (w + h) / 2, w, h);
+ canvas.drawTexture(entry.texture, mSource, mTarget);
+
+ canvas.restore();
+ }
+ }
+
+ @Override
+ protected void onCalculate(float progress) {
+ mProgress = progress;
+ }
+
+ public void setPositionProvider(PositionProvider provider) {
+ mPositionProvider = provider;
+ if (mPositionProvider != null) {
+ for (int i = 0, n = mList.size(); i < n; ++i) {
+ Entry entry = mList.get(i);
+ entry.index = mPositionProvider.getItemIndex(entry.path);
+ }
+ }
+ }
+
+ @Override
+ public boolean acceptSlot(int index) {
+ for (int i = 0, n = mList.size(); i < n; ++i) {
+ Entry entry = mList.get(i);
+ if (entry.index == index) return false;
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index 217a290..a7ecd06 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -16,59 +16,112 @@
package com.android.gallery3d.ui;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Message;
+import android.view.MotionEvent;
+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.Path;
-import com.android.gallery3d.ui.PositionRepository.Position;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Point;
-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.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.util.RangeArray;
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;
+ }
- private static final long DELAY_SHOW_LOADING = 250; // 250ms;
+ public interface Model extends TileImageView.Model {
+ public int getCurrentIndex();
+ public void moveTo(int index);
- private static final int TRANS_NONE = 0;
- private static final int TRANS_SWITCH_NEXT = 3;
- private static final int TRANS_SWITCH_PREVIOUS = 4;
+ // Returns the size for the specified picture. If the size information is
+ // not avaiable, width = height = 0.
+ public void getImageSize(int offset, Size size);
- 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;
+ // Returns the media item for the specified picture.
+ public MediaItem getMediaItem(int offset);
- 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;
+ // Returns the rotation for the specified picture.
+ public int getImageRotation(int offset);
- private static final int ENTRY_PREVIOUS = 0;
- private static final int ENTRY_NEXT = 1;
+ // 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);
- private static final int IMAGE_GAP = 96;
- private static final int SWITCH_THRESHOLD = 256;
+ // Set this to true if we need the model to provide full images.
+ public void setNeedFullImage(boolean enabled);
+
+ // Returns true if the item is the Camera preview.
+ public boolean isCamera(int offset);
+
+ // Returns true if the item is the Panorama.
+ public boolean isPanorama(int offset);
+
+ // Returns true if the item is a Video.
+ public boolean isVideo(int offset);
+
+ public static final int LOADING_INIT = 0;
+ public static final int LOADING_COMPLETE = 1;
+ public static final int LOADING_FAIL = 2;
+
+ public int getLoadingState(int offset);
+ }
+
+ public interface Listener {
+ public void onSingleTapUp(int x, int y);
+ public void lockOrientation();
+ public void unlockOrientation();
+ public void onFullScreenChanged(boolean full);
+ public void onActionBarAllowed(boolean allowed);
+ public void onActionBarWanted();
+ public void onCurrentImageUpdated();
+ }
+
+ // The rules about orientation locking:
+ //
+ // (1) We need to lock the orientation if we are in page mode camera
+ // preview, so there is no (unwanted) rotation animation when the user
+ // rotates the device.
+ //
+ // (2) We need to unlock the orientation if we want to show the action bar
+ // because the action bar follows the system orientation.
+ //
+ // The rules about action bar:
+ //
+ // (1) If we are in film mode, we don't show action bar.
+ //
+ // (2) If we go from camera to gallery with capture animation, we show
+ // action bar.
+ 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 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;
+ private static final int ICON_RATIO = 6;
+
+ // 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,51 +130,57 @@
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 PhotoTapListener mPhotoTapListener;
-
+ private final MyGestureListener mGestureListener;
+ private final GestureRecognizer mGestureRecognizer;
private final PositionController mPositionController;
+ private Listener mListener;
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;
- private boolean mShowVideoPlayIcon;
- private ProgressSpinner mLoadingSpinner;
-
private SynchronizedHandler mHandler;
- 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;
+ private int mDisplayRotation = 0;
+ private int mCompensation = 0;
+ private boolean mFullScreenCamera;
+ private Rect mCameraRelativeFrame = new Rect();
+ private Rect mCameraRect = new Rect();
+
+ // [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.
+ private int mHolding;
+ private static final int HOLD_TOUCH_DOWN = 1;
+ private static final int HOLD_CAPTURE_ANIMATION = 2;
public PhotoView(GalleryActivity activity) {
- mActivity = activity;
mTileView = new TileImageView(activity);
addComponent(mTileView);
Context context = activity.getAndroidContext();
mEdgeView = new EdgeView(context);
addComponent(mEdgeView);
- mLoadingSpinner = new ProgressSpinner(context);
mLoadingText = StringTexture.newInstance(
context.getString(R.string.loading),
DEFAULT_TEXT_SIZE, Color.WHITE);
@@ -129,438 +188,609 @@
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;
+ mHandler = new MyHandler(activity.getGLRoot());
+
+ mGestureListener = new MyGestureListener();
+ mGestureRecognizer = new GestureRecognizer(context, mGestureListener);
+
+ mPositionController = new PositionController(context,
+ new PositionController.Listener() {
+ public void invalidate() {
+ PhotoView.this.invalidate();
}
- case MSG_SHOW_LOADING: {
- if (mLoadingState == LOADING_INIT) {
- // We don't need the opening animation
- mOpenedItemPath = null;
-
- mLoadingSpinner.startAnimation();
- mLoadingState = LOADING_TIMEOUT;
- invalidate();
- }
- break;
+ public boolean isHolding() {
+ return mHolding != 0;
}
- 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);
+ 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));
- }
- }
-
- 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);
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case MSG_CANCEL_EXTRA_SCALING: {
+ mGestureRecognizer.cancelScale();
+ mPositionController.setExtraScalingRange(false);
+ mCancelExtraScalingPending = false;
+ break;
}
- updateLoadingState();
- break;
+ case MSG_SWITCH_FOCUS: {
+ switchFocus();
+ break;
+ }
+ case MSG_CAPTURE_ANIMATION_DONE: {
+ // message.arg1 is the offset parameter passed to
+ // switchWithCaptureAnimation().
+ captureAnimationDone(message.arg1);
+ break;
+ }
+ default: throw new AssertionError(message.what);
}
}
+ };
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Data/Image change notifications
+ ////////////////////////////////////////////////////////////////////////////
+
+ public void notifyDataChange(int[] fromIndex, int prevBound, int nextBound) {
+ mPrevBound = prevBound;
+ mNextBound = nextBound;
+
+ // Move the boxes
+ mPositionController.moveBox(fromIndex, mPrevBound < 0, mNextBound > 0,
+ mModel.isCamera(0));
+
+ // Update the ScreenNails.
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
+ mPictures.get(i).reload();
+ }
+
+ invalidate();
}
- private void updateLoadingState() {
- // Possible transitions of mLoadingState:
- // INIT --> TIMEOUT, COMPLETE, FAIL
- // TIMEOUT --> COMPLETE, FAIL, INIT
- // COMPLETE --> INIT
- // FAIL --> INIT
- if (mModel.getLevelCount() != 0 || mModel.getBackupImage() != null) {
- mHandler.removeMessages(MSG_SHOW_LOADING);
- mLoadingState = LOADING_COMPLETE;
- } else if (mModel.isFailedToLoad()) {
- mHandler.removeMessages(MSG_SHOW_LOADING);
- mLoadingState = LOADING_FAIL;
- } else if (mLoadingState != LOADING_INIT) {
- mLoadingState = LOADING_INIT;
- mHandler.removeMessages(MSG_SHOW_LOADING);
- mHandler.sendEmptyMessageDelayed(
- MSG_SHOW_LOADING, DELAY_SHOW_LOADING);
+ public void notifyImageChange(int index) {
+ if (index == 0) {
+ mListener.onCurrentImageUpdated();
}
- }
-
- 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();
-
- if (mModel == null) {
- mTileView.notifyModelInvalidated();
- mTileView.setAlpha(1.0f);
- mImageRotation = 0;
- mPositionController.setImageSize(0, 0);
- updateLoadingState();
- } else {
- notifyImageInvalidated(0);
- }
- }
-
- @Override
- protected boolean onTouch(MotionEvent event) {
- mGestureDetector.onTouchEvent(event);
- mScaleDetector.onTouchEvent(event);
- mDownUpDetector.onTouchEvent(event);
- return true;
+ mPictures.get(index).reload();
+ invalidate();
}
@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);
+ int w = right - left;
+ int h = bottom - top;
+ mTileView.layout(0, 0, w, h);
+ mEdgeView.layout(0, 0, w, h);
+
+ GLRoot root = getGLRoot();
+ int displayRotation = root.getDisplayRotation();
+ int compensation = root.getCompensation();
+ if (mDisplayRotation != displayRotation
+ || mCompensation != compensation) {
+ mDisplayRotation = displayRotation;
+ mCompensation = compensation;
+
+ // We need to change the size and rotation of the Camera ScreenNail,
+ // but we don't want it to animate because the size doen't actually
+ // change in the eye of the user.
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
+ Picture p = mPictures.get(i);
+ if (p.isCamera()) {
+ p.forceSize();
+ }
+ }
+ }
+
+ updateCameraRect();
+ mPositionController.setConstrainedFrame(mCameraRect);
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.
+ // Update the camera rectangle due to layout change or camera relative frame
+ // change.
+ private void updateCameraRect() {
+ // Get the width and height in framework orientation because the given
+ // mCameraRelativeFrame is in that coordinates.
int w = getWidth();
int h = getHeight();
- int s = Math.min(getWidth(), getHeight()) / 6;
+ if (mCompensation % 180 != 0) {
+ int tmp = w;
+ w = h;
+ h = tmp;
+ }
+ int l = mCameraRelativeFrame.left;
+ int t = mCameraRelativeFrame.top;
+ int r = mCameraRelativeFrame.right;
+ int b = mCameraRelativeFrame.bottom;
- 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);
+ // Now convert it to the coordinates we are using.
+ switch (mCompensation) {
+ case 0: mCameraRect.set(l, t, r, b); break;
+ case 90: mCameraRect.set(h - b, l, h - t, r); break;
+ case 180: mCameraRect.set(w - r, h - b, w - l, h - t); break;
+ case 270: mCameraRect.set(t, w - r, b, w - l); break;
}
- // 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);
- }
+ Log.d(TAG, "compensation = " + mCompensation
+ + ", CameraRelativeFrame = " + mCameraRelativeFrame
+ + ", mCameraRect = " + mCameraRect);
}
- 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();
- }
+ public void setCameraRelativeFrame(Rect frame) {
+ mCameraRelativeFrame.set(frame);
+ updateCameraRect();
+ // Originally we do
+ // mPositionController.setConstrainedFrame(mCameraRect);
+ // here, but it is moved to a parameter of the setImageSize() call, so
+ // it can be updated atomically with the CameraScreenNail's size change.
}
- 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;
+ // Returns the rotation we need to do to the camera texture before drawing
+ // it to the canvas, assuming the camera texture is correct when the device
+ // is in its natural orientation.
+ private int getCameraRotation() {
+ return (mCompensation - mDisplayRotation + 360) % 360;
}
- 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 int getPanoramaRotation() {
+ return mCompensation;
}
- private boolean mIgnoreUpEvent = false;
+ ////////////////////////////////////////////////////////////////////////////
+ // Pictures
+ ////////////////////////////////////////////////////////////////////////////
- private class MyGestureListener
- extends GestureDetector.SimpleOnGestureListener {
- @Override
- public boolean onScroll(
- MotionEvent e1, MotionEvent e2, float dx, float dy) {
- if (mTransitionMode != TRANS_NONE) return true;
+ private interface Picture {
+ void reload();
+ void draw(GLCanvas canvas, Rect r);
+ void setScreenNail(ScreenNail s);
+ boolean isCamera(); // whether the picture is a camera preview
+ void forceSize(); // called when mCompensation changes
+ };
- ScreenNailEntry next = mScreenNails[ENTRY_NEXT];
- ScreenNailEntry prev = mScreenNails[ENTRY_PREVIOUS];
+ class FullPicture implements Picture {
+ private int mRotation;
+ private boolean mIsCamera;
+ private boolean mIsPanorama;
+ private boolean mIsVideo;
+ private int mLoadingState = Model.LOADING_INIT;
+ private boolean mWasCameraCenter;
+ private int mWidth, mHeight;
- mPositionController.startScroll(dx, dy, next.isEnabled(),
- prev.isEnabled());
- return true;
+ public void FullPicture(TileImageView tileView) {
+ mTileView = tileView;
}
@Override
- public boolean onSingleTapUp(MotionEvent e) {
- if (mPhotoTapListener != null) {
- mPhotoTapListener.onSingleTapUp((int) e.getX(), (int) e.getY());
- }
- return true;
+ public void reload() {
+ // mImageWidth and mImageHeight will get updated
+ mTileView.notifyModelInvalidated();
+
+ mIsCamera = mModel.isCamera(0);
+ mIsPanorama = mModel.isPanorama(0);
+ mIsVideo = mModel.isVideo(0);
+ mLoadingState = mModel.getLoadingState(0);
+ setScreenNail(mModel.getScreenNail(0));
+ setSize();
+ }
+
+ private void setSize() {
+ updateSize();
+ mPositionController.setImageSize(0, mWidth, mHeight,
+ mIsCamera ? mCameraRect : null);
}
@Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- if (swipeImages(velocityX)) {
+ public void forceSize() {
+ updateSize();
+ mPositionController.forceImageSize(0, mWidth, mHeight);
+ }
+
+ private void updateSize() {
+ if (mIsPanorama) {
+ mRotation = getPanoramaRotation();
+ } else if (mIsCamera) {
+ mRotation = getCameraRotation();
+ } else {
+ mRotation = mModel.getImageRotation(0);
+ }
+
+ int w = mTileView.mImageWidth;
+ int h = mTileView.mImageHeight;
+ mWidth = getRotated(mRotation, w, h);
+ mHeight = getRotated(mRotation, h, w);
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, Rect r) {
+ drawTileView(canvas, r);
+
+ // We want to have the 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.
+
+ // Holdings except touch-down prevent the transitions.
+ if ((mHolding & ~HOLD_TOUCH_DOWN) != 0) return;
+
+ boolean isCenter = mPositionController.isCenter();
+ boolean isCameraCenter = mIsCamera && isCenter;
+
+ if (mWasCameraCenter && mIsCamera && !isCenter && !mFilmMode) {
+ // Temporary disabled to de-emphasize filmstrip.
+ // setFilmMode(true);
+ } else if (!mWasCameraCenter && isCameraCenter && mFilmMode) {
+ setFilmMode(false);
+ }
+
+ if (isCameraCenter && !mFilmMode) {
+ // Move into camera in page mode, lock
+ mListener.lockOrientation();
+ }
+
+ mWasCameraCenter = isCameraCenter;
+ }
+
+ @Override
+ public void setScreenNail(ScreenNail s) {
+ mTileView.setScreenNail(s);
+ }
+
+ @Override
+ public boolean isCamera() {
+ return mIsCamera;
+ }
+
+ private void drawTileView(GLCanvas canvas, Rect r) {
+ float imageScale = mPositionController.getImageScale();
+ int viewW = getWidth();
+ int viewH = getHeight();
+ float cx = r.exactCenterX();
+ float cy = r.exactCenterY();
+ float scale = 1f; // the scaling factor due to card effect
+
+ canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
+ float filmRatio = mPositionController.getFilmRatio();
+ boolean wantsCardEffect = CARD_EFFECT && !mIsCamera
+ && filmRatio != 1f && !mPictures.get(-1).isCamera()
+ && !mPositionController.inOpeningAnimation();
+ 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) {
+ scale = getScrollScale(progress);
+ float alpha = getScrollAlpha(progress);
+ scale = interpolate(filmRatio, scale, 1f);
+ alpha = interpolate(filmRatio, alpha, 1f);
+
+ imageScale *= scale;
+ canvas.multiplyAlpha(alpha);
+
+ float cxPage; // the cx value in page mode
+ if (right - left <= viewW) {
+ // If the picture is narrower than the view, keep it at
+ // the center of the view.
+ cxPage = viewW / 2f;
+ } 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.
+ cxPage = (right - left) * scale / 2f;
+ }
+ cx = interpolate(filmRatio, cxPage, cx);
+ }
+ }
+
+ // Draw the tile view.
+ setTileViewPosition(cx, cy, viewW, viewH, imageScale);
+ PhotoView.super.render(canvas);
+
+ // Draw the play video icon and the message.
+ canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f));
+ int s = (int) (scale * Math.min(r.width(), r.height()) + 0.5f);
+ if (mIsVideo) drawVideoPlayIcon(canvas, s);
+ if (mLoadingState == Model.LOADING_FAIL) {
+ drawLoadingFailMessage(canvas);
+ }
+
+ // Draw a debug indicator showing which picture has focus (index ==
+ // 0).
+ //canvas.fillRect(-10, -10, 20, 20, 0x80FF00FF);
+
+ canvas.restore();
+ }
+
+ // Set the position of the tile view
+ private void setTileViewPosition(float cx, float cy,
+ int viewW, int viewH, float scale) {
+ // Find out the bitmap coordinates of the center of the view
+ int imageW = mPositionController.getImageWidth();
+ int imageH = mPositionController.getImageHeight();
+ int centerX = (int) (imageW / 2f + (viewW / 2f - cx) / scale + 0.5f);
+ int centerY = (int) (imageH / 2f + (viewH / 2f - cy) / scale + 0.5f);
+
+ int inverseX = imageW - centerX;
+ int inverseY = imageH - centerY;
+ int x, y;
+ switch (mRotation) {
+ case 0: x = centerX; y = centerY; break;
+ case 90: x = centerY; y = inverseX; break;
+ case 180: x = inverseX; y = inverseY; break;
+ case 270: x = inverseY; y = centerX; break;
+ default:
+ throw new RuntimeException(String.valueOf(mRotation));
+ }
+ mTileView.setPosition(x, y, scale, mRotation);
+ }
+ }
+
+ private class ScreenNailPicture implements Picture {
+ private int mIndex;
+ private int mRotation;
+ private ScreenNail mScreenNail;
+ private Size mSize = new Size();
+ private boolean mIsCamera;
+ private boolean mIsPanorama;
+ private boolean mIsVideo;
+ private int mLoadingState = Model.LOADING_INIT;
+ private int mWidth, mHeight;
+
+ public ScreenNailPicture(int index) {
+ mIndex = index;
+ }
+
+ @Override
+ public void reload() {
+ mIsCamera = mModel.isCamera(mIndex);
+ mIsPanorama = mModel.isPanorama(mIndex);
+ mIsVideo = mModel.isVideo(mIndex);
+ mLoadingState = mModel.getLoadingState(mIndex);
+ setScreenNail(mModel.getScreenNail(mIndex));
+ setSize();
+ }
+
+ @Override
+ public void draw(GLCanvas canvas, Rect r) {
+ if (mScreenNail == null) {
+ // Draw a placeholder rectange if there should be a picture in
+ // this position (but somehow there isn't).
+ if (mIndex >= mPrevBound && mIndex <= mNextBound) {
+ drawPlaceHolder(canvas, r);
+ }
+ return;
+ }
+ if (r.left >= getWidth() || r.right <= 0 ||
+ r.top >= getHeight() || r.bottom <= 0) {
+ mScreenNail.noDraw();
+ return;
+ }
+
+ float filmRatio = mPositionController.getFilmRatio();
+ boolean wantsCardEffect = CARD_EFFECT && mIndex > 0
+ && filmRatio != 1f && !mPictures.get(0).isCamera();
+ int w = getWidth();
+ int cx = wantsCardEffect
+ ? (int) (interpolate(filmRatio, w / 2, r.centerX()) + 0.5f)
+ : r.centerX();
+ int cy = r.centerY();
+ canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA);
+ 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);
+ alpha = interpolate(filmRatio, alpha, 1f);
+ scale = interpolate(filmRatio, scale, 1f);
+ canvas.multiplyAlpha(alpha);
+ canvas.scale(scale, scale, 1);
+ }
+ if (mRotation != 0) {
+ canvas.rotate(mRotation, 0, 0, 1);
+ }
+ int drawW = getRotated(mRotation, r.width(), r.height());
+ int drawH = getRotated(mRotation, r.height(), r.width());
+ mScreenNail.draw(canvas, -drawW / 2, -drawH / 2, drawW, drawH);
+ if (isScreenNailAnimating()) {
+ invalidate();
+ }
+ int s = Math.min(drawW, drawH);
+ if (mIsVideo) drawVideoPlayIcon(canvas, s);
+ if (mLoadingState == Model.LOADING_FAIL) {
+ drawLoadingFailMessage(canvas);
+ }
+ canvas.restore();
+ }
+
+ private boolean isScreenNailAnimating() {
+ return (mScreenNail instanceof BitmapScreenNail)
+ && ((BitmapScreenNail) mScreenNail).isAnimating();
+ }
+
+ @Override
+ public void setScreenNail(ScreenNail s) {
+ mScreenNail = s;
+ }
+
+ private void setSize() {
+ updateSize();
+ mPositionController.setImageSize(mIndex, mWidth, mHeight, null);
+ }
+
+ @Override
+ public void forceSize() {
+ updateSize();
+ mPositionController.forceImageSize(mIndex, mWidth, mHeight);
+ }
+
+ private void updateSize() {
+ if (mIsPanorama) {
+ mRotation = getPanoramaRotation();
+ } else if (mIsCamera) {
+ mRotation = getCameraRotation();
+ } else {
+ mRotation = mModel.getImageRotation(mIndex);
+ }
+
+ int w = 0, h = 0;
+ if (mScreenNail != null) {
+ w = mScreenNail.getWidth();
+ h = mScreenNail.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;
+ }
+
+ mWidth = getRotated(mRotation, w, h);
+ mHeight = getRotated(mRotation, h, w);
+ }
+
+ @Override
+ public boolean isCamera() {
+ return mIsCamera;
+ }
+ }
+
+ // Draw a gray placeholder in the specified rectangle.
+ private void drawPlaceHolder(GLCanvas canvas, Rect r) {
+ canvas.fillRect(r.left, r.top, r.width(), r.height(), PLACEHOLDER_COLOR);
+ }
+
+ // Draw the video play icon (in the place where the spinner was)
+ private void drawVideoPlayIcon(GLCanvas canvas, int side) {
+ int s = side / ICON_RATIO;
+ // Draw the video play icon at the center
+ mVideoPlayIcon.draw(canvas, -s / 2, -s / 2, s, s);
+ }
+
+ // Draw the "no thumbnail" message
+ private void drawLoadingFailMessage(GLCanvas canvas) {
+ StringTexture m = mNoThumbnailText;
+ m.draw(canvas, -m.getWidth() / 2, -m.getHeight() / 2);
+ }
+
+ 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 film mode in this scaling gesture.
+ private boolean mModeChanged;
+ // If this scaling gesture should be ignored.
+ private boolean mIgnoreScalingGesture;
+ // whether the down action happened while the view is scrolling.
+ private boolean mDownInScrolling;
+ // If we should ignore all gestures other than onSingleTapUp.
+ private boolean mIgnoreSwipingGesture;
+
+ @Override
+ public boolean onSingleTapUp(float x, float y) {
+ // We do this in addition to onUp() because we want the snapback of
+ // setFilmMode to happen.
+ mHolding &= ~HOLD_TOUCH_DOWN;
+
+ if (mFilmMode && !mDownInScrolling) {
+ switchToHitPicture((int) (x + 0.5f), (int) (y + 0.5f));
+ setFilmMode(false);
mIgnoreUpEvent = true;
- } else if (mTransitionMode != TRANS_NONE) {
- // do nothing
+ return true;
+ }
+
+ if (mListener != null) {
+ // Do the inverse transform of the touch coordinates.
+ Matrix m = getGLRoot().getCompensationMatrix();
+ Matrix inv = new Matrix();
+ m.invert(inv);
+ float[] pts = new float[] {x, y};
+ inv.mapPoints(pts);
+ mListener.onSingleTapUp((int) (pts[0] + 0.5f), (int) (pts[1] + 0.5f));
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTap(float x, float y) {
+ if (mIgnoreSwipingGesture) return true;
+ if (mPictures.get(0).isCamera()) return false;
+ 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) {
+ if (mIgnoreSwipingGesture) return true;
+ mPositionController.startScroll(-dx, -dy);
+ return true;
+ }
+
+ @Override
+ public boolean onFling(float velocityX, float velocityY) {
+ if (mIgnoreSwipingGesture) return true;
+ if (swipeImages(velocityX, velocityY)) {
+ mIgnoreUpEvent = true;
} else if (mPositionController.fling(velocityX, velocityY)) {
mIgnoreUpEvent = true;
}
@@ -568,279 +798,439 @@
}
@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();
+ public boolean onScaleBegin(float focusX, float focusY) {
+ if (mIgnoreSwipingGesture) return true;
+ // We ignore the scaling gesture if it is a camera preview.
+ mIgnoreScalingGesture = mPictures.get(0).isCamera();
+ if (mIgnoreScalingGesture) {
+ return true;
}
+ 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;
}
- }
-
- 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;
+ public boolean onScale(float focusX, float focusY, float scale) {
+ if (mIgnoreSwipingGesture) return true;
+ if (mIgnoreScalingGesture) return true;
+ if (mModeChanged) return true;
+ if (Float.isNaN(scale) || Float.isInfinite(scale)) return false;
+
+ // We wait for the scale change accumulated to a large enough change
+ // before reacting to it. Otherwise we may mistakenly treat a
+ // zoom-in gesture as zoom-out or vice versa.
+ if (scale > 0.99f && scale < 1.01f) return false;
+
+ int outOfRange = mPositionController.scaleBy(scale, focusX, focusY);
+
+ // If mode changes, we treat this scaling gesture has ended.
+ if (mCanChangeMode) {
+ if ((outOfRange < 0 && !mFilmMode) ||
+ (outOfRange > 0 && mFilmMode)) {
+ stopExtraScalingIfNeeded();
+
+ // Removing the touch down flag allows snapback to happen
+ // for film mode change.
+ mHolding &= ~HOLD_TOUCH_DOWN;
+ setFilmMode(!mFilmMode);
+
+ // We need to call onScaleEnd() before setting mModeChanged
+ // to true.
+ onScaleEnd();
+ mModeChanged = true;
+ return true;
}
+ }
+
+ if (outOfRange != 0) {
+ startExtraScalingIfNeeded();
} else {
- if (mCancelExtraScalingPending) {
- mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING);
- mPositionController.setExtraScalingRange(false);
- mCancelExtraScalingPending = false;
- }
+ stopExtraScalingIfNeeded();
}
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) {
+ public void onScaleEnd() {
+ if (mIgnoreSwipingGesture) return;
+ if (mIgnoreScalingGesture) return;
+ if (mModeChanged) return;
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);
- break;
+ private void startExtraScalingIfNeeded() {
+ if (!mCancelExtraScalingPending) {
+ mHandler.sendEmptyMessageDelayed(
+ MSG_CANCEL_EXTRA_SCALING, 700);
+ mPositionController.setExtraScalingRange(true);
+ mCancelExtraScalingPending = true;
}
- default: throw new IllegalArgumentException(String.valueOf(direction));
- }
- }
-
- private class MyDownUpListener implements DownUpDetector.DownUpListener {
- public void onDown(MotionEvent e) {
}
- public void onUp(MotionEvent e) {
+ private void stopExtraScalingIfNeeded() {
+ if (mCancelExtraScalingPending) {
+ mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING);
+ mPositionController.setExtraScalingRange(false);
+ mCancelExtraScalingPending = false;
+ }
+ }
+
+ @Override
+ public void onDown() {
+ if (mIgnoreSwipingGesture) return;
+
+ mHolding |= HOLD_TOUCH_DOWN;
+
+ if (mFilmMode && mPositionController.isScrolling()) {
+ mDownInScrolling = true;
+ mPositionController.stopScrolling();
+ } else {
+ mDownInScrolling = false;
+ }
+ }
+
+ @Override
+ public void onUp() {
+ if (mIgnoreSwipingGesture) return;
+
+ mHolding &= ~HOLD_TOUCH_DOWN;
mEdgeView.onRelease();
if (mIgnoreUpEvent) {
mIgnoreUpEvent = false;
return;
}
- if (!snapToNeighborImage() && mTransitionMode == TRANS_NONE) {
- mPositionController.up();
+
+ snapback();
+ }
+
+ public void setSwipingEnabled(boolean enabled) {
+ mIgnoreSwipingGesture = !enabled;
+ }
+ }
+
+ public void setSwipingEnabled(boolean enabled) {
+ mGestureListener.setSwipingEnabled(enabled);
+ }
+
+ private void setFilmMode(boolean enabled) {
+ if (mFilmMode == enabled) return;
+ mFilmMode = enabled;
+ mPositionController.setFilmMode(mFilmMode);
+ mModel.setNeedFullImage(!enabled);
+ mListener.onActionBarAllowed(!enabled);
+
+ // Move into camera in page mode, lock
+ if (!enabled && mPictures.get(0).isCamera()) {
+ mListener.lockOrientation();
+ }
+ }
+
+ public boolean getFilmMode() {
+ return mFilmMode;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Framework events
+ ////////////////////////////////////////////////////////////////////////////
+
+ 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();
+ }
+
+ // move to the camera preview and show controls after resume
+ public void resetToFirstPicture() {
+ mModel.moveTo(0);
+ setFilmMode(false);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Rendering
+ ////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ protected void render(GLCanvas canvas) {
+ // Check if the camera preview occupies the full screen.
+ boolean full = !mFilmMode && mPictures.get(0).isCamera()
+ && mPositionController.isCenter()
+ && mPositionController.isAtMinimalScale();
+ if (full != mFullScreenCamera) {
+ mFullScreenCamera = full;
+ mListener.onFullScreenChanged(full);
+ }
+
+ // Determine how many photos we need to draw in addition to the center
+ // one.
+ int neighbors;
+ if (mFullScreenCamera) {
+ neighbors = 0;
+ } else {
+ // In page mode, we draw only one previous/next photo. But if we are
+ // doing capture animation, we want to draw all photos.
+ boolean inPageMode = (mPositionController.getFilmRatio() == 0f);
+ boolean inCaptureAnimation =
+ ((mHolding & HOLD_CAPTURE_ANIMATION) != 0);
+ if (inPageMode && !inCaptureAnimation) {
+ neighbors = 1;
+ } else {
+ neighbors = SCREEN_NAIL_MAX;
+ }
+ }
+
+ // Draw photos from back to front
+ for (int i = neighbors; i >= -neighbors; i--) {
+ Rect r = mPositionController.getPosition(i);
+ mPictures.get(i).draw(canvas, r);
+ }
+
+ 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;
+ }
+
+ // Switch to the previous or next picture if the hit position is inside
+ // one of their boxes. This runs in main thread.
+ private void switchToHitPicture(int x, int y) {
+ if (mPrevBound < 0) {
+ Rect r = mPositionController.getPosition(-1);
+ if (r.right >= x) {
+ slideToPrevPicture();
+ return;
+ }
+ }
+
+ if (mNextBound > 0) {
+ Rect r = mPositionController.getPosition(1);
+ if (r.left <= x) {
+ slideToNextPicture();
+ return;
}
}
}
+ ////////////////////////////////////////////////////////////////////////////
+ // 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() {
- // 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();
+ mModel.moveTo(mModel.getCurrentIndex() + 1);
}
- 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();
+ private void switchToPrevImage() {
+ mModel.moveTo(mModel.getCurrentIndex() - 1);
}
- public void notifyTransitionComplete() {
- mHandler.sendEmptyMessage(MSG_TRANSITION_COMPLETE);
+ private void switchToFirstImage() {
+ mModel.moveTo(0);
}
- private void onTransitionComplete() {
- int mode = mTransitionMode;
- mTransitionMode = TRANS_NONE;
+ ////////////////////////////////////////////////////////////////////////////
+ // Opening Animation
+ ////////////////////////////////////////////////////////////////////////////
- if (mModel == null) return;
- if (mode == TRANS_SWITCH_NEXT) {
+ 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 (mHolding != 0) return true;
+ if (offset == 1) {
+ if (mNextBound <= 0) return false;
+ // Temporary disable action bar until the capture animation is done.
+ if (!mFilmMode) mListener.onActionBarAllowed(false);
switchToNextImage();
- } else if (mode == TRANS_SWITCH_PREVIOUS) {
- switchToPreviousImage();
- }
- }
+ mPositionController.startCaptureAnimationSlide(-1);
+ } else if (offset == -1) {
+ if (mPrevBound >= 0) return false;
+ if (mFilmMode) setFilmMode(false);
- 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();
+ // If we are too far away from the first image (so that we don't
+ // have all the ScreenNails in-between), we go directly without
+ // animation.
+ if (mModel.getCurrentIndex() > SCREEN_NAIL_MAX) {
+ switchToFirstImage();
+ mPositionController.skipToFinalPosition();
+ return true;
}
+
+ switchToFirstImage();
+ mPositionController.startCaptureAnimationSlide(1);
+ } else {
+ return false;
}
-
- 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;
-
- int w = getWidth();
- int x = applyFadingAnimation ? w / 2 : mOffsetX;
- int y = getHeight() / 2;
- int flags = GLCanvas.SAVE_FLAG_MATRIX;
-
- if (applyFadingAnimation) flags |= GLCanvas.SAVE_FLAG_ALPHA;
- canvas.save(flags);
- canvas.translate(x, y, 0);
- if (applyFadingAnimation) {
- float progress = (float) (x - mOffsetX) / w;
- float alpha = getScrollAlpha(progress);
- float scale = getScrollScale(progress);
- canvas.multiplyAlpha(alpha);
- canvas.scale(scale, scale, 1);
- }
- 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);
- canvas.restore();
- }
+ mHolding |= HOLD_CAPTURE_ANIMATION;
+ Message m = mHandler.obtainMessage(MSG_CAPTURE_ANIMATION_DONE, offset, 0);
+ mHandler.sendMessageDelayed(m, PositionController.CAPTURE_ANIMATION_TIME);
+ return true;
}
+ private void captureAnimationDone(int offset) {
+ mHolding &= ~HOLD_CAPTURE_ANIMATION;
+ if (offset == 1 && !mFilmMode) {
+ // Now the capture animation is done, enable the action bar.
+ mListener.onActionBarAllowed(true);
+ mListener.onActionBarWanted();
+ }
+ 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
@@ -859,10 +1249,15 @@
// If the object width is smaller than the view width,
// |....view....|
// |<-->| progress = -1 when left = viewWidth
+ // |<-->| progress = 0 when left = viewWidth / 2 - w / 2
// |<-->| progress = 1 when left = -w
- // So progress = 1 - 2 * (left + w) / (viewWidth + w)
if (w < viewWidth) {
- return 1f - 2f * (left + w) / (viewWidth + w);
+ int zx = viewWidth / 2 - w / 2;
+ if (left > zx) {
+ return -(left - zx) / (float) (viewWidth - zx); // progress = (0, -1]
+ } else {
+ return (left - zx) / (float) (-w - zx); // progress = [0, 1]
+ }
}
// If the object width is larger than the view width,
@@ -917,44 +1312,49 @@
}
}
- public void pause() {
- mPositionController.skipAnimation();
- mTransitionMode = TRANS_NONE;
- mTileView.freeTextures();
- for (ScreenNailEntry entry : mScreenNails) {
- entry.set(false, null, 0);
+ // Returns an interpolated value for the page/film transition.
+ // When ratio = 0, the result is from.
+ // When ratio = 1, the result is to.
+ private static float interpolate(float ratio, float from, float to) {
+ return from + (to - from) * ratio * ratio;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Simple public utilities
+ ////////////////////////////////////////////////////////////////////////////
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public Rect getPhotoRect(int index) {
+ return mPositionController.getPosition(index);
+ }
+
+ public PhotoFallbackEffect buildFallbackEffect(GLView root, GLCanvas canvas) {
+ Rect location = new Rect();
+ Utils.assertTrue(root.getBoundsOf(this, location));
+
+ Rect fullRect = bounds();
+ PhotoFallbackEffect effect = new PhotoFallbackEffect();
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; ++i) {
+ MediaItem item = mModel.getMediaItem(i);
+ if (item == null) continue;
+ ScreenNail sc = mModel.getScreenNail(i);
+ if (!(sc instanceof BitmapScreenNail)
+ || ((BitmapScreenNail) sc).isShowingPlaceholder()) continue;
+
+ // Now, sc is BitmapScreenNail and is not showing placeholder
+ Rect rect = new Rect(getPhotoRect(i));
+ if (!Rect.intersects(fullRect, rect)) continue;
+ rect.offset(location.left, location.top);
+
+ RawTexture texture = new RawTexture(sc.getWidth(), sc.getHeight(), true);
+ canvas.beginRenderTarget(texture);
+ sc.draw(canvas, 0, 0, sc.getWidth(), sc.getHeight());
+ canvas.endRenderTarget();
+ effect.addEntry(item.getPath(), rect, texture);
}
- }
-
- public void resume() {
- mTileView.prepareTextures();
- }
-
- public void setOpenedItem(Path itemPath) {
- mOpenedItemPath = itemPath;
- }
-
- 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;
+ return effect;
}
}
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index 2068446..65334d5 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -16,353 +16,555 @@
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;
+
+ public static final int CAPTURE_ANIMATION_TIME = 700;
+
+ // 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
+ 400, // ANIM_KIND_OPENING
0, // ANIM_KIND_FLING (the duration is calculated dynamically)
+ CAPTURE_ANIMATION_TIME, // 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;
+
+ // Use a large enough value, so we won't see the gray shadown in the beginning.
+ private int mViewW = 1200;
+ private int mViewH = 1200;
+
+ // 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;
+ // Constrained frame is a rectangle that the focused box should fit into if
+ // it is constrained. It has two effects:
+ //
+ // (1) In page mode, if the focused box is constrained, scaling for the
+ // focused box is adjusted to fit into the constrained frame, instead of the
+ // whole view.
+ //
+ // (2) In page mode, if the focused box is constrained, the mPlatform's
+ // default center (mDefaultX/Y) is moved to the center of the constrained
+ // frame, instead of the view center.
+ //
+ private Rect mConstrainedFrame = new Rect();
- private RectF mTempRect = new RectF();
- private float[] mTempPoints = new float[8];
+ // Whether the focused box is constrained.
+ //
+ // Our current program's first call to moveBox() sets constrained = true, so
+ // we set the initial value of this variable to true, and we will not see
+ // see unwanted transition animation.
+ private boolean mConstrained = true;
- public PositionController(PhotoView viewer, Context context,
- EdgeView edgeView) {
- mViewer = viewer;
- mEdgeView = edgeView;
- mScroller = new FlingScroller();
+ //
+ // ___________________________________________________________
+ // | _____ _____ _____ _____ _____ |
+ // | | | | | | | | | | | |
+ // | | Box | | Box | | Box*| | Box | | Box | |
+ // | |_____|.....|_____|.....|_____|.....|_____|.....|_____| |
+ // | Gap Gap Gap Gap |
+ // |___________________________________________________________|
+ //
+ // <-- Platform -->
+ //
+ // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
+
+ 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);
+ private FilmRatio mFilmRatio = new FilmRatio();
+
+ // 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,
+ null /* default interpolator */, false /* no flywheel */);
- // If no image available, use view size.
- 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);
- return;
+ // 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());
}
-
- 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);
+ for (int i = -BOX_MAX; i < BOX_MAX; i++) {
+ mGaps.put(i, new Gap());
+ initGap(i);
}
- mViewer.setPosition(mCurrentX, mCurrentY, mCurrentScale);
}
+ public void setOpenAnimationRect(Rect r) {
+ mOpenAnimationRect = r;
+ }
+
+ public void setViewSize(int viewW, int viewH) {
+ if (viewW == mViewW && viewH == mViewH) return;
+
+ boolean wasMinimal = isAtMinimalScale();
+
+ mViewW = viewW;
+ mViewH = viewH;
+ initPlatform();
+
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ setBoxSize(i, viewW, viewH, true);
+ }
+
+ updateScaleAndGapLimit();
+
+ // If the focused box was at minimal scale, we try to make it the
+ // minimal scale under the new view size.
+ if (wasMinimal) {
+ Box b = mBoxes.get(0);
+ b.mCurrentScale = b.mScaleMin;
+ }
+
+ // If we have the opening animation, do it. Otherwise go directly to the
+ // right position.
+ if (!startOpeningAnimationIfNeeded()) {
+ skipToFinalPosition();
+ }
+ }
+
+ public void setConstrainedFrame(Rect cFrame) {
+ if (mConstrainedFrame.equals(cFrame)) return;
+ mConstrainedFrame.set(cFrame);
+ mPlatform.updateDefaultXY();
+ updateScaleAndGapLimit();
+ snapAndRedraw();
+ }
+
+ public void forceImageSize(int index, int width, int height) {
+ if (width == 0 || height == 0) return;
+ Box b = mBoxes.get(index);
+ b.mImageW = width;
+ b.mImageH = height;
+ return;
+ }
+
+ public void setImageSize(int index, int width, int height, Rect cFrame) {
+ if (width == 0 || height == 0) return;
+
+ boolean needUpdate = false;
+ if (cFrame != null && !mConstrainedFrame.equals(cFrame)) {
+ mConstrainedFrame.set(cFrame);
+ mPlatform.updateDefaultXY();
+ needUpdate = true;
+ }
+ needUpdate |= setBoxSize(index, width, height, false);
+
+ if (!needUpdate) return;
+ updateScaleAndGapLimit();
+ snapAndRedraw();
+ }
+
+ // 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;
+
+ // 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.
+ //
+ // If the aspect ratio changes, we don't know if it is because one side
+ // grows or the other side shrinks. Currently we just assume the view
+ // angle of the longer side doesn't change (so the aspect ratio change
+ // is because the view angle of the shorter side changes). This matches
+ // what camera preview does.
+ float ratio = (width > height)
+ ? (float) b.mImageW / width
+ : (float) b.mImageH / height;
+
+ b.mImageW = width;
+ 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(b);
+ b.mAnimationStartTime = NO_ANIMATION;
+ } else {
+ b.mCurrentScale *= ratio;
+ b.mFromScale *= ratio;
+ b.mToScale *= ratio;
+ }
+
+ if (i == 0) {
+ mFocusX /= ratio;
+ mFocusY /= ratio;
+ }
+
+ return true;
+ }
+
+ private boolean startOpeningAnimationIfNeeded() {
+ if (mOpenAnimationRect == null) return false;
+ Box b = mBoxes.get(0);
+ if (b.mUseViewSize) return false;
+
+ // Start animation from the saved rectangle if we have one.
+ Rect r = mOpenAnimationRect;
+ mOpenAnimationRect = null;
+
+ mPlatform.mCurrentX = r.centerX() - mViewW / 2;
+ b.mCurrentY = r.centerY() - mViewH / 2;
+ b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
+ r.height() / (float) b.mImageH);
+ startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
+ ANIM_KIND_OPENING);
+
+ // Animate from large gaps for neighbor boxes to avoid them
+ // shown on the screen during opening animation.
+ for (int i = -1; i < 1; i++) {
+ Gap g = mGaps.get(i);
+ g.mCurrentGap = mViewW;
+ g.doAnimation(g.mDefaultSize, ANIM_KIND_OPENING);
+ }
+
+ return true;
+ }
+
+ public void setFilmMode(boolean enabled) {
+ if (enabled == mFilmMode) return;
+ mFilmMode = enabled;
+
+ mPlatform.updateDefaultXY();
+ 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, mFilmMode, mConstrained, and mConstrainedFrame.
+ private void updateScaleAndGapLimit() {
+ for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
+ Box b = mBoxes.get(i);
+ b.mScaleMin = getMinimalScale(b);
+ b.mScaleMax = getMaximalScale(b);
+ }
+
+ 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.mCurrentY = mPlatform.mToY;
+ 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();
+ }
+
+ public void skipToFinalPosition() {
+ stopAnimation();
+ snapAndRedraw();
+ skipAnimation();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Start an animations for the focused box
+ ////////////////////////////////////////////////////////////////////////////
+
public void zoomIn(float tapX, float tapY, float targetScale) {
- if (targetScale > mScaleMax) targetScale = mScaleMax;
+ tapX -= mViewW / 2;
+ tapY -= mViewH / 2;
+ Box b = mBoxes.get(0);
- // 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);
+ // 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) (-tempX * targetScale + 0.5f);
+ int y = (int) (-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(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
}
public void beginScale(float focusX, float focusY) {
+ focusX -= mViewW / 2;
+ focusY -= mViewH / 2;
+ 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) {
+ focusX -= mViewW / 2;
+ focusY -= mViewH / 2;
+ 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 = b.clampScale(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(mPlatform.mDefaultX, 0, 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(mPlatform.mDefaultX, mPlatform.mDefaultY,
+ ANIM_KIND_CAPTURE);
+ b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
+ n.doAnimation(0, 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;
+
+ // Only allow scrolling when we are not currently in an animation or we
+ // are in some animation with can be interrupted.
+ if (b.mAnimationStartTime != NO_ANIMATION) {
+ switch (b.mAnimationKind) {
+ case ANIM_KIND_SCROLL:
+ case ANIM_KIND_FLING:
+ break;
+ default:
+ return;
+ }
+ }
+
+ int x = p.mCurrentX + (int) (dx + 0.5f);
+ int y = b.mCurrentY + (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,216 +572,587 @@
// 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.
+ x -= mPlatform.mDefaultX;
+ if (!mHasPrev && x > 0) {
+ mListener.onPull(x, EdgeView.LEFT);
+ x = 0;
+ } else if (!mHasNext && x < 0) {
+ mListener.onPull(-x, EdgeView.RIGHT);
+ x = 0;
+ }
+ x += mPlatform.mDefaultX;
+ 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 defaultX = p.mDefaultX;
+ if ((!mHasPrev && p.mCurrentX >= defaultX)
+ || (!mHasNext && p.mCurrentX <= defaultX)) {
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();
+ }
+ mFilmRatio.startSnapback();
+ redraw();
+ }
+
+ private void startAnimation(int targetX, int targetY, float targetScale,
+ int kind) {
+ boolean changed = false;
+ changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, 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();
+ }
+ changed |= mFilmRatio.advanceAnimation();
+ if (changed) redraw();
+ }
+
+ public boolean inOpeningAnimation() {
+ return (mPlatform.mAnimationKind == ANIM_KIND_OPENING &&
+ mPlatform.mAnimationStartTime != NO_ANIMATION) ||
+ (mBoxes.get(0).mAnimationKind == ANIM_KIND_OPENING &&
+ mBoxes.get(0).mAnimationStartTime != NO_ANIMATION);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // 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 + mPlatform.mCurrentY + mViewH / 2;
+ int w = widthOf(b);
+ int h = heightOf(b);
+ if (i == 0) {
+ int x = mPlatform.mCurrentX + mViewW / 2;
+ 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;
-
- float scaleMin = mExtraScalingRange ?
- mScaleMin * SCALE_MIN_EXTRA : mScaleMin;
- float scaleMax = mExtraScalingRange ?
- mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
-
- if (mCurrentScale < scaleMin || mCurrentScale > scaleMax) {
- needAnimation = true;
- scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
- }
-
- 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;
- }
-
- if (needAnimation) {
- startAnimation(x, y, scale, ANIM_KIND_SNAPBACK);
- }
-
- return needAnimation;
+ // Returns the position of a box.
+ public Rect getPosition(int index) {
+ return mRects.get(index);
}
- // Calculates the stable region of mCurrent{X/Y}, where "stable" means
+ ////////////////////////////////////////////////////////////////////////////
+ // Box management
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Initialize the platform to be at the view center.
+ private void initPlatform() {
+ mPlatform.updateDefaultXY();
+ mPlatform.mCurrentX = mPlatform.mDefaultX;
+ mPlatform.mCurrentY = mPlatform.mDefaultY;
+ 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);
+ b.mScaleMax = getMaximalScale(b);
+ b.mCurrentY = 0;
+ 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.
+ //
+ // hasPrev/hasNext indicates if there are previous/next boxes for the
+ // focused box. constrained indicates whether the focused box should be put
+ // into the constrained frame.
+ public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
+ boolean constrained) {
+ //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() - mViewW / 2;
+ }
+
+ // 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);
+ }
+
+ // 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);
+ }
+
+ // 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. calculate the new absolute X coordinates for those box before
+ // first or after last.
+ for (int i = first - 1; i >= -BOX_MAX; i--) {
+ Box a = mBoxes.get(i + 1);
+ Box b = mBoxes.get(i);
+ int wa = widthOf(a);
+ int wb = widthOf(b);
+ Gap g = mGaps.get(i);
+ b.mAbsoluteX = a.mAbsoluteX - wa / 2 - (wb - wb / 2) - g.mCurrentGap;
+ }
+
+ for (int i = last + 1; i <= BOX_MAX; i++) {
+ Box a = mBoxes.get(i - 1);
+ Box b = mBoxes.get(i);
+ int wa = widthOf(a);
+ int wb = widthOf(b);
+ Gap g = mGaps.get(i - 1);
+ b.mAbsoluteX = a.mAbsoluteX + (wa - wa / 2) + wb / 2 + g.mCurrentGap;
+ }
+
+ // 9. offset the Platform position
+ int dx = mBoxes.get(0).mAbsoluteX - mPlatform.mCurrentX;
+ mPlatform.mCurrentX += dx;
+ mPlatform.mFromX += dx;
+ mPlatform.mToX += dx;
+ mPlatform.mFlingOffset += dx;
+
+ if (mConstrained != constrained) {
+ mConstrained = constrained;
+ mPlatform.updateDefaultXY();
+ updateScaleAndGapLimit();
+ }
+
+ snapAndRedraw();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Public utilities
+ ////////////////////////////////////////////////////////////////////////////
+
+ public boolean isAtMinimalScale() {
+ Box b = mBoxes.get(0);
+ return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
+ }
+
+ public boolean isCenter() {
+ Box b = mBoxes.get(0);
+ return mPlatform.mCurrentX == mPlatform.mDefaultX
+ && b.mCurrentY == 0;
+ }
+
+ 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;
+ }
+
+ public boolean isScrolling() {
+ return mPlatform.mAnimationStartTime != NO_ANIMATION
+ && mPlatform.mCurrentX != mPlatform.mToX;
+ }
+
+ public void stopScrolling() {
+ if (mPlatform.mAnimationStartTime == NO_ANIMATION) return;
+ if (mFilmMode) mFilmScroller.forceFinished(true);
+ mPlatform.mFromX = mPlatform.mToX = mPlatform.mCurrentX;
+ }
+
+ public float getFilmRatio() {
+ return mFilmRatio.mCurrentRatio;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Private utilities
+ ////////////////////////////////////////////////////////////////////////////
+
+ private float getMinimalScale(Box b) {
+ float wFactor = 1.0f;
+ float hFactor = 1.0f;
+ int viewW, viewH;
+
+ if (!mFilmMode && mConstrained && !mConstrainedFrame.isEmpty()
+ && b == mBoxes.get(0)) {
+ viewW = mConstrainedFrame.width();
+ viewH = mConstrainedFrame.height();
+ } else {
+ viewW = mViewW;
+ viewH = mViewH;
+ }
+
+ 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 * viewW / b.mImageW,
+ hFactor * viewH / b.mImageH);
+ return Math.min(SCALE_LIMIT, s);
+ }
+
+ private float getMaximalScale(Box b) {
+ if (mFilmMode) return getMinimalScale(b);
+ if (mConstrained && !mConstrainedFrame.isEmpty()) return getMinimalScale(b);
+ return SCALE_LIMIT;
+ }
+
+ private static boolean isAlmostEqual(float a, float b) {
+ float diff = a - b;
+ return (diff < 0 ? -diff : diff) < 0.02f;
+ }
+
+ // Calculates the stable region of mPlatform.mCurrentX and
+ // mBoxes.get(0).mCurrentY, where "stable" means
//
// (1) If the dimension of scaled image >= view dimension, we will not
// see black region outside the image (at that dimension).
@@ -594,95 +1167,511 @@
// 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 + 1) / 2 - (w + 1) / 2 - horizontalSlack;
+ mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
+ mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
+ mBoundBottom = h / 2 - mViewH / 2;
// 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 = 0;
}
// Same for width
- if (Math.floor(mImageW * scale) <= mViewW) {
- mBoundLeft = mBoundRight = mImageW / 2;
+ if (viewWiderThanScaledImage(scale)) {
+ mBoundLeft = mBoundRight = mPlatform.mDefaultX;
}
}
- 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 b.mAnimationStartTime == NO_ANIMATION
+ ? b.mCurrentScale : b.mToScale;
}
- public RectF getImageBounds() {
- float points[] = mTempPoints;
+ ////////////////////////////////////////////////////////////////////////////
+ // Animatable: an thing which can do animation.
+ ////////////////////////////////////////////////////////////////////////////
+ private abstract static class Animatable {
+ public long mAnimationStartTime;
+ public int mAnimationKind;
+ public int mAnimationDuration;
- /*
- * (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;
+ // 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();
- RectF rect = mTempRect;
- rect.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
- Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
+ // 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 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;
+ 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_OPENING:
+ progress = 1 - f * f * f; // x^3
+ break;
+ case ANIM_KIND_SNAPBACK:
+ case ANIM_KIND_ZOOM:
+ case ANIM_KIND_SLIDE:
+ progress = 1 - f * f * f * f * f; // x^5
+ break;
+ }
+ return progress;
+ }
}
- public int getImageWidth() {
- return mImageW;
+ ////////////////////////////////////////////////////////////////////////////
+ // Platform: captures the global X/Y movement.
+ ////////////////////////////////////////////////////////////////////////////
+ private class Platform extends Animatable {
+ public int mCurrentX, mFromX, mToX, mDefaultX;
+ public int mCurrentY, mFromY, mToY, mDefaultY;
+ public int mFlingOffset;
+
+ @Override
+ public boolean startSnapback() {
+ if (mAnimationStartTime != NO_ANIMATION) return false;
+ if (mAnimationKind == ANIM_KIND_SCROLL
+ && mListener.isHolding()) return false;
+ if (mInScale) 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;
+ int y = mDefaultY;
+ if (mFilmMode) {
+ int defaultX = mDefaultX;
+ if (!mHasNext) x = Math.max(x, defaultX);
+ if (!mHasPrev) x = Math.min(x, defaultX);
+ } else {
+ calculateStableBound(scale, HORIZONTAL_SLACK);
+ // If the picture is zoomed-in, we want to keep the focus point
+ // stay in the same position on screen, so we need to adjust
+ // target mCurrentX (which is the center of the focused
+ // box). The position of the focus point on screen (relative the
+ // the center of the view) is:
+ //
+ // mCurrentX + scale * mFocusX = mCurrentX' + scale' * mFocusX
+ // => mCurrentX' = mCurrentX + (scale - scale') * mFocusX
+ //
+ if (!viewWiderThanScaledImage(scale)) {
+ float scaleDiff = b.mCurrentScale - scale;
+ x += (int) (mFocusX * scaleDiff + 0.5f);
+ }
+ x = Utils.clamp(x, mBoundLeft, mBoundRight);
+ }
+ if (mCurrentX != x || mCurrentY != y) {
+ return doAnimation(x, y, ANIM_KIND_SNAPBACK);
+ }
+ return false;
+ }
+
+ // The updateDefaultXY() should be called whenever these variables
+ // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
+ // mFilmMode
+ public void updateDefaultXY() {
+ // We don't check mFilmMode and return 0 for mDefaultX. Because
+ // otherwise if we decide to leave film mode because we are
+ // centered, we will immediately back into film mode because we find
+ // we are not centered.
+ if (mConstrained && !mConstrainedFrame.isEmpty()) {
+ mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
+ mDefaultY = mFilmMode ? 0 :
+ mConstrainedFrame.centerY() - mViewH / 2;
+ } else {
+ mDefaultX = 0;
+ mDefaultY = 0;
+ }
+ }
+
+ // Starts an animation for the platform.
+ private boolean doAnimation(int targetX, int targetY, int kind) {
+ if (mCurrentX == targetX && mCurrentY == targetY) return false;
+ mAnimationKind = kind;
+ mFromX = mCurrentX;
+ mFromY = mCurrentY;
+ mToX = targetX;
+ mToY = targetY;
+ 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 < mDefaultX) {
+ if (!mHasNext) {
+ dir = EdgeView.RIGHT;
+ }
+ } else if (mCurrentX > mDefaultX) {
+ 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 = mDefaultX;
+ }
+ 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;
+ mCurrentY = mToY;
+ return true;
+ } else {
+ if (mAnimationKind == ANIM_KIND_CAPTURE) {
+ progress = CaptureAnimation.calculateSlide(progress);
+ }
+ mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
+ mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
+ if (mAnimationKind == ANIM_KIND_CAPTURE) {
+ return false;
+ } else {
+ return (mCurrentX == mToX && mCurrentY == mToY);
+ }
+ }
+ }
}
- 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 = mCurrentY;
+ 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 = 0;
+ } else {
+ calculateStableBound(scale, HORIZONTAL_SLACK);
+ // If the picture is zoomed-in, we want to keep the focus
+ // point stay in the same position on screen. See the
+ // comment in Platform.startSnapback for details.
+ if (!viewTallerThanScaledImage(scale)) {
+ float scaleDiff = mCurrentScale - scale;
+ y += (int) (mFocusY * scaleDiff + 0.5f);
+ }
+ y = Utils.clamp(y, mBoundTop, mBoundBottom);
+ }
+ } else {
+ y = 0;
+ 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 = clampScale(targetScale);
+
+ // 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 = 0;
+ }
+
+ 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;
+ }
+
+ // Clamps the input scale to the range that doAnimation() can reach.
+ public float clampScale(float s) {
+ return Utils.clamp(s,
+ SCALE_MIN_EXTRA * mScaleMin,
+ SCALE_MAX_EXTRA * mScaleMax);
+ }
+
+ @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;
+
+ // 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);
+ }
+ }
+ }
}
- public boolean isAtRightEdge() {
- calculateStableBound(mCurrentScale);
- return mCurrentX >= mBoundRight;
+ ////////////////////////////////////////////////////////////////////////////
+ // FilmRatio: represents the progress of film mode change.
+ ////////////////////////////////////////////////////////////////////////////
+ private class FilmRatio extends Animatable {
+ // The film ratio: 1 means switching to film mode is complete, 0 means
+ // switching to page mode is complete.
+ public float mCurrentRatio, mFromRatio, mToRatio;
+
+ @Override
+ public boolean startSnapback() {
+ float target = mFilmMode ? 1f : 0f;
+ if (target == mToRatio) return false;
+ return doAnimation(target, ANIM_KIND_SNAPBACK);
+ }
+
+ // Starts an animation for the film ratio.
+ private boolean doAnimation(float targetRatio, int kind) {
+ mAnimationKind = kind;
+ mFromRatio = mCurrentRatio;
+ mToRatio = targetRatio;
+ mAnimationStartTime = AnimationTime.startTime();
+ mAnimationDuration = ANIM_TIME[mAnimationKind];
+ advanceAnimation();
+ return true;
+ }
+
+ @Override
+ protected boolean interpolate(float progress) {
+ if (progress >= 1) {
+ mCurrentRatio = mToRatio;
+ return true;
+ } else {
+ mCurrentRatio = mFromRatio + progress * (mToRatio - mFromRatio);
+ return (mCurrentRatio == mToRatio);
+ }
+ }
}
}
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/RawTexture.java b/src/com/android/gallery3d/ui/RawTexture.java
index c1be435..4c0d9d3 100644
--- a/src/com/android/gallery3d/ui/RawTexture.java
+++ b/src/com/android/gallery3d/ui/RawTexture.java
@@ -17,38 +17,75 @@
package com.android.gallery3d.ui;
import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
-// 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 {
+public class RawTexture extends BasicTexture {
+ private static final String TAG = "RawTexture";
- private RawTexture(GLCanvas canvas, int id) {
- super(canvas, id, STATE_LOADED);
+ private final static int[] sTextureId = new int[1];
+ private final static float[] sCropRect = new float[4];
+
+ private final boolean mOpaque;
+
+ public RawTexture(int width, int height, boolean opaque) {
+ mOpaque = opaque;
+ setSize(width, height);
}
- public static RawTexture newInstance(GLCanvas canvas) {
- int[] textureId = new int[1];
+ @Override
+ public boolean isOpaque() {
+ return mOpaque;
+ }
+
+ protected void prepare(GLCanvas canvas) {
GL11 gl = canvas.getGLInstance();
- gl.glGenTextures(1, textureId, 0);
- return new RawTexture(canvas, textureId[0]);
+
+ // 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] = mHeight;
+ sCropRect[2] = mWidth;
+ sCropRect[3] = -mHeight;
+
+ // Upload the bitmap to a new texture.
+ 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);
+ 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.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA,
+ getTextureWidth(), getTextureHeight(),
+ 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, null);
+
+ mId = sTextureId[0];
+ mState = STATE_LOADED;
+ setAssociatedCanvas(canvas);
}
@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;
+ if (isLoaded()) return true;
+ Log.w(TAG, "lost the content due to context change");
+ return false;
}
@Override
- public void yield() {
- // we cannot free the texture because we have no backup.
+ public void yield() {
+ // we cannot free the texture because we have no backup.
+ }
+
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
}
}
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/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..86e92da 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);
@@ -108,15 +107,21 @@
return mInverseSelection ^ mClickedSet.contains(itemId);
}
+ private int getTotalCount() {
+ if (mSourceMediaSet == null) return -1;
+
+ if (mTotal < 0) {
+ mTotal = mIsAlbumSet
+ ? mSourceMediaSet.getSubMediaSetCount()
+ : mSourceMediaSet.getMediaItemCount();
+ }
+ return mTotal;
+ }
+
public int getSelectedCount() {
int count = mClickedSet.size();
if (mInverseSelection) {
- if (mTotal < 0) {
- mTotal = mIsAlbumSet
- ? mSourceMediaSet.getSubMediaSetCount()
- : mSourceMediaSet.getMediaItemCount();
- }
- count = mTotal - count;
+ count = getTotalCount() - count;
}
return count;
}
@@ -129,20 +134,18 @@
mClickedSet.add(path);
}
+ // Convert to inverse selection mode if everything is selected.
+ int count = getSelectedCount();
+ if (count == getTotalCount()) {
+ selectAll();
+ }
+
if (mListener != null) mListener.onSelectionChange(path, isItemSelected(path));
- if (getSelectedCount() == 0 && mAutoLeave) {
+ if (count == 0 && mAutoLeave) {
leaveSelectionMode();
}
}
- 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++) {
@@ -168,8 +171,8 @@
ArrayList<Path> selected = new ArrayList<Path>();
if (mIsAlbumSet) {
if (mInverseSelection) {
- int max = mSourceMediaSet.getSubMediaSetCount();
- for (int i = 0; i < max; i++) {
+ int total = getTotalCount();
+ for (int i = 0; i < total; i++) {
MediaSet set = mSourceMediaSet.getSubMediaSet(i);
Path id = set.getPath();
if (!mClickedSet.contains(id)) {
@@ -191,8 +194,7 @@
}
} else {
if (mInverseSelection) {
-
- int total = mSourceMediaSet.getMediaItemCount();
+ int total = getTotalCount();
int index = 0;
while (index < total) {
int count = Math.min(total - index, MediaSet.MEDIAITEM_BATCH_FETCH_COUNT);
@@ -216,8 +218,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..88a2e0d 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,21 @@
(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();
}
private void updateScrollPosition(int position, boolean force) {
@@ -205,20 +195,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 +229,23 @@
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);
+ more |= mLayout.advanceAnimation(animTime);
int oldX = mScrollX;
updateScrollPosition(mScroller.getPosition(), false);
@@ -279,51 +268,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 +311,64 @@
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);
}
}
@@ -413,24 +390,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;
@@ -449,22 +411,28 @@
private int mContentLength;
private int mScrollPosition;
- private int mVerticalPadding;
- private int mHorizontalPadding;
+ private IntegerAnimation mVerticalPadding = new IntegerAnimation();
+ private IntegerAnimation mHorizontalPadding = new IntegerAnimation();
public void setSlotSpec(Spec spec) {
mSpec = spec;
}
public boolean setSlotCount(int slotCount) {
+ if (slotCount == mSlotCount) return false;
+ if (mSlotCount != 0) {
+ mHorizontalPadding.setEnabled(true);
+ mVerticalPadding.setEnabled(true);
+ }
mSlotCount = slotCount;
- int hPadding = mHorizontalPadding;
- int vPadding = mVerticalPadding;
+ int hPadding = mHorizontalPadding.getTarget();
+ int vPadding = mVerticalPadding.getTarget();
initLayoutParameters();
- return vPadding != mVerticalPadding || hPadding != mHorizontalPadding;
+ return vPadding != mVerticalPadding.getTarget()
+ || hPadding != mHorizontalPadding.getTarget();
}
- public Rect getSlotRect(int index) {
+ public Rect getSlotRect(int index, Rect rect) {
int col, row;
if (WIDE) {
col = index / mUnitCount;
@@ -474,9 +442,10 @@
col = index - row * mUnitCount;
}
- int x = mHorizontalPadding + col * (mSlotWidth + mSlotGap);
- int y = mVerticalPadding + row * (mSlotHeight + mSlotGap);
- return new Rect(x, y, x + mSlotWidth, y + mSlotHeight);
+ int x = mHorizontalPadding.get() + col * (mSlotWidth + mSlotGap);
+ int y = mVerticalPadding.get() + row * (mSlotHeight + mSlotGap);
+ rect.set(x, y, x + mSlotWidth, y + mSlotHeight);
+ return rect;
}
public int getSlotWidth() {
@@ -487,10 +456,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,15 +504,19 @@
mSlotWidth = mSlotHeight;
}
+ if (mRenderer != null) {
+ mRenderer.onSlotSizeChanged(mSlotWidth, mSlotHeight);
+ }
+
int[] padding = new int[2];
if (WIDE) {
initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
- mVerticalPadding = padding[0];
- mHorizontalPadding = padding[1];
+ mVerticalPadding.startAnimateTo(padding[0]);
+ mHorizontalPadding.startAnimateTo(padding[1]);
} else {
initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding);
- mVerticalPadding = padding[1];
- mHorizontalPadding = padding[0];
+ mVerticalPadding.startAnimateTo(padding[1]);
+ mHorizontalPadding.startAnimateTo(padding[0]);
}
updateVisibleSlotRange();
}
@@ -592,6 +561,9 @@
} else {
mVisibleStart = mVisibleEnd = 0;
}
+ if (mRenderer != null) {
+ mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd);
+ }
}
public int getVisibleStart() {
@@ -606,8 +578,8 @@
int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0);
int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition);
- absoluteX -= mHorizontalPadding;
- absoluteY -= mVerticalPadding;
+ absoluteX -= mHorizontalPadding.get();
+ absoluteY -= mVerticalPadding.get();
if (absoluteX < 0 || absoluteY < 0) {
return INDEX_NONE;
@@ -643,28 +615,38 @@
int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight;
return limit <= 0 ? 0 : limit;
}
+
+ public boolean advanceAnimation(long animTime) {
+ // use '|' to make sure both sides will be executed
+ return mVerticalPadding.calculate(animTime) | mHorizontalPadding.calculate(animTime);
+ }
}
- 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 +657,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 +670,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 +683,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 +692,7 @@
@Override
public void onLongPress(MotionEvent e) {
- cancelDown();
+ cancelDown(true);
if (mDownInScrolling) return;
lockRendering();
try {
@@ -735,7 +717,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;
}
@@ -754,4 +737,42 @@
public int getScrollY() {
return mScrollY;
}
+
+ private static class IntegerAnimation extends Animation {
+ private int mTarget;
+ private int mCurrent = 0;
+ private int mFrom = 0;
+ private boolean mEnabled = false;
+
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ public void startAnimateTo(int target) {
+ if (!mEnabled) {
+ mTarget = mCurrent = target;
+ return;
+ }
+ if (target == mTarget) return;
+
+ mFrom = mCurrent;
+ mTarget = target;
+ setDuration(180);
+ start();
+ }
+
+ public int get() {
+ return mCurrent;
+ }
+
+ public int getTarget() {
+ return mTarget;
+ }
+
+ @Override
+ protected void onCalculate(float progress) {
+ mCurrent = Math.round(mFrom + progress * (mTarget - mFrom));
+ if (progress == 1f) mEnabled = false;
+ }
+ }
}
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..5f45f16
--- /dev/null
+++ b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
@@ -0,0 +1,107 @@
+/*
+ * 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.setDefaultBufferSize(mWidth, mHeight);
+ 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..714b097
--- /dev/null
+++ b/src/com/android/gallery3d/ui/TextureUploader.java
@@ -0,0 +1,104 @@
+/*
+ * 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() {
+ while (!mFgTextures.isEmpty()) {
+ mFgTextures.pop().setIsUploading(false);
+ }
+ while (!mBgTextures.isEmpty()) {
+ mBgTextures.pop().setIsUploading(false);
+ }
+ }
+
+ // caller should hold synchronized on "this"
+ private void queueSelfIfNeed() {
+ if (mIsQueued) return;
+ mIsQueued = true;
+ mGLRoot.addOnGLIdleListener(this);
+ }
+
+ public synchronized void addBgTexture(UploadedTexture t) {
+ if (t.isContentValid()) return;
+ mBgTextures.addLast(t);
+ t.setIsUploading(true);
+ queueSelfIfNeed();
+ }
+
+ public synchronized void addFgTexture(UploadedTexture t) {
+ if (t.isContentValid()) return;
+ mFgTextures.addLast(t);
+ t.setIsUploading(true);
+ 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();
+ t.setIsUploading(false);
+ if (t.isContentValid()) continue;
+
+ // this has to be protected by the synchronized block
+ // to prevent the inner bitmap get recycled
+ 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..0c6086d 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -20,18 +20,18 @@
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;
+import com.android.gallery3d.data.BitmapPool;
import com.android.gallery3d.data.DecodeUtils;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.ThreadPool;
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 {
@@ -44,8 +44,12 @@
// texture to avoid seams between tiles.
private static final int TILE_SIZE = 254;
private static final int TILE_BORDER = 1;
+ private static final int BITMAP_SIZE = TILE_SIZE + TILE_BORDER * 2;
private static final int UPLOAD_LIMIT = 1;
+ private static final BitmapPool sTilePool =
+ new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128);
+
/*
* This is the tile state in the CPU side.
* Life of a Tile:
@@ -72,7 +76,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 +84,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,12 +98,12 @@
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();
- private TileQueue mUploadQueue = new TileQueue();
- private TileQueue mDecodeQueue = new TileQueue();
+ private final TileQueue mRecycledQueue = new TileQueue();
+ private final TileQueue mUploadQueue = new TileQueue();
+ private final TileQueue mDecodeQueue = new TileQueue();
// The width and height of the full-sized bitmap
protected int mImageWidth = SIZE_UNKNOWN;
@@ -109,7 +113,6 @@
protected int mCenterY;
protected float mScale;
protected int mRotation;
- protected float mAlpha = 1.0f;
// Temp variables to avoid memory allocation
private final Rect mTileRange = new Rect();
@@ -118,12 +121,12 @@
private final TileUploader mTileUploader = new TileUploader();
private boolean mIsTextureFreed;
private Future<Void> mTileDecoder;
- private ThreadPool mThreadPool;
+ private final ThreadPool mThreadPool;
private boolean mBackgroundTileUploaded;
public static interface Model {
public int getLevelCount();
- public Bitmap getBackupImage();
+ public ScreenNail getScreenNail();
public int getImageWidth();
public int getImageHeight();
@@ -140,8 +143,7 @@
//
// The method would be called in another thread.
public Bitmap getTile(int level, int x, int y, int tileSize,
- int borderSize);
- public boolean isFailedToLoad();
+ int borderSize, BitmapPool pool);
}
public TileImageView(GalleryContext context) {
@@ -154,31 +156,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();
@@ -245,19 +235,20 @@
mDecodeQueue.clean();
mUploadQueue.clean();
mBackgroundTileUploaded = false;
- }
- // Recycle unused tiles: if the level of the active tile is outside the
- // range [fromLevel, endLevel) or not in the visible range.
- Iterator<Map.Entry<Long, Tile>>
- iter = mActiveTiles.entrySet().iterator();
- while (iter.hasNext()) {
- Tile tile = iter.next().getValue();
- int level = tile.mTileLevel;
- if (level < fromLevel || level >= endLevel
- || !range[level - fromLevel].contains(tile.mX, tile.mY)) {
- iter.remove();
- recycleTile(tile);
+ // Recycle unused tiles: if the level of the active tile is outside the
+ // range [fromLevel, endLevel) or not in the visible range.
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
+ int level = tile.mTileLevel;
+ if (level < fromLevel || level >= endLevel
+ || !range[level - fromLevel].contains(tile.mX, tile.mY)) {
+ mActiveTiles.removeAt(i);
+ i--;
+ n--;
+ recycleTile(tile);
+ }
}
}
@@ -277,7 +268,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 +300,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;
@@ -347,8 +340,8 @@
}
public boolean setPosition(int centerX, int centerY, float scale, int rotation) {
- if (mCenterX == centerX
- && mCenterY == centerY && mScale == scale) return false;
+ if (mCenterX == centerX && mCenterY == centerY
+ && mScale == scale && mRotation == rotation) return false;
mCenterX = centerX;
mCenterY = centerY;
mScale = scale;
@@ -358,13 +351,6 @@
return true;
}
- public boolean setAlpha(float alpha) {
- if (mAlpha == alpha) return false;
- mAlpha = alpha;
- invalidate();
- return true;
- }
-
public void freeTextures() {
mIsTextureFreed = true;
@@ -374,11 +360,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 +377,8 @@
tile = mRecycledQueue.pop();
}
}
- updateBackupTexture(null);
+ setScreenNail(null);
+ sTilePool.clear();
}
public void prepareTextures() {
@@ -399,7 +388,7 @@
if (mIsTextureFreed) {
layoutTiles(mCenterX, mCenterY, mScale, mRotation);
mIsTextureFreed = false;
- updateBackupTexture(mModel != null ? mModel.getBackupImage() : null);
+ setScreenNail(mModel == null ? null : mModel.getScreenNail());
}
}
@@ -412,20 +401,22 @@
int rotation = mRotation;
int flags = 0;
if (rotation != 0) flags |= GLCanvas.SAVE_FLAG_MATRIX;
- if (mAlpha != 1.0f) flags |= GLCanvas.SAVE_FLAG_ALPHA;
if (flags != 0) {
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 (level != mLevelCount && !isScreenNailAnimating()) {
+ if (mScreenNail != null) {
+ mScreenNail.noDraw();
+ }
+
int size = (TILE_SIZE << level);
float length = size * mScale;
Rect r = mTileRange;
@@ -437,10 +428,13 @@
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));
+ if (isScreenNailAnimating()) {
+ invalidate();
+ }
}
} finally {
if (flags != 0) canvas.restore();
@@ -453,10 +447,17 @@
}
}
+ private boolean isScreenNailAnimating() {
+ return (mScreenNail instanceof BitmapScreenNail)
+ && ((BitmapScreenNail) mScreenNail).isAnimating();
+ }
+
private void uploadBackgroundTiles(GLCanvas canvas) {
mBackgroundTileUploaded = true;
- for (Tile tile : mActiveTiles.values()) {
- if (!tile.isContentValid(canvas)) queueForDecode(tile);
+ int n = mActiveTiles.size();
+ for (int i = 0; i < n; i++) {
+ Tile tile = mActiveTiles.valueAt(i);
+ if (!tile.isContentValid()) queueForDecode(tile);
}
}
@@ -485,7 +486,10 @@
synchronized (this) {
if (tile.mTileState == STATE_RECYCLING) {
tile.mTileState = STATE_RECYCLED;
- tile.mDecodedTile = null;
+ if (tile.mDecodedTile != null) {
+ sTilePool.recycle(tile.mDecodedTile);
+ tile.mDecodedTile = null;
+ }
mRecycledQueue.push(tile);
return false;
}
@@ -510,12 +514,15 @@
return;
}
tile.mTileState = STATE_RECYCLED;
- tile.mDecodedTile = null;
+ if (tile.mDecodedTile != null) {
+ sTilePool.recycle(tile.mDecodedTile);
+ tile.mDecodedTile = null;
+ }
mRecycledQueue.push(tile);
}
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 +538,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) {
@@ -550,7 +558,7 @@
tile = mUploadQueue.pop();
}
if (tile == null || quota <= 0) break;
- if (!tile.isContentValid(canvas)) {
+ if (!tile.isContentValid()) {
Utils.assertTrue(tile.mTileState == STATE_DECODED);
tile.updateContent(canvas);
--quota;
@@ -572,7 +580,7 @@
Tile tile = getTile(tx, ty, level);
if (tile != null) {
- if (!tile.isContentValid(canvas)) {
+ if (!tile.isContentValid()) {
if (tile.mTileState == STATE_DECODED) {
if (mUploadQuota > 0) {
--mUploadQuota;
@@ -587,14 +595,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);
}
}
@@ -602,7 +609,7 @@
static boolean drawTile(
Tile tile, GLCanvas canvas, RectF source, RectF target) {
while (true) {
- if (tile.isContentValid(canvas)) {
+ if (tile.isContentValid()) {
// offset source rectangle for the texture border.
source.offset(TILE_BORDER, TILE_BORDER);
canvas.drawTexture(tile, source, target);
@@ -631,12 +638,12 @@
}
private class Tile extends UploadedTexture {
- int mX;
- int mY;
- int mTileLevel;
- Tile mNext;
- Bitmap mDecodedTile;
- volatile int mTileState = STATE_ACTIVATED;
+ public int mX;
+ public int mY;
+ public int mTileLevel;
+ public Tile mNext;
+ public Bitmap mDecodedTile;
+ public volatile int mTileState = STATE_ACTIVATED;
public Tile(int x, int y, int level) {
mX = x;
@@ -646,7 +653,7 @@
@Override
protected void onFreeBitmap(Bitmap bitmap) {
- bitmap.recycle();
+ sTilePool.recycle(bitmap);
}
boolean decode() {
@@ -654,7 +661,7 @@
// by (1 << mTilelevel) from a region in the original image.
try {
mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile(
- mTileLevel, mX, mY, TILE_SIZE, TILE_BORDER));
+ mTileLevel, mX, mY, TILE_SIZE, TILE_BORDER, sTilePool));
} catch (Throwable t) {
Log.w(TAG, "fail to decode tile", t);
}
@@ -664,6 +671,13 @@
@Override
protected Bitmap onGetBitmap() {
Utils.assertTrue(mTileState == STATE_DECODED);
+
+ // We need to override the width and height, so that we won't
+ // draw beyond the boundaries.
+ int rightEdge = ((mImageWidth - mX) >> mTileLevel) + TILE_BORDER;
+ int bottomEdge = ((mImageHeight - mY) >> mTileLevel) + TILE_BORDER;
+ setSize(Math.min(BITMAP_SIZE, rightEdge), Math.min(BITMAP_SIZE, bottomEdge));
+
Bitmap bitmap = mDecodedTile;
mDecodedTile = null;
mTileState = STATE_ACTIVATED;
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index be255d2..0b4ac03 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -20,25 +20,27 @@
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
-import android.graphics.Canvas;
import android.graphics.Rect;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.BitmapPool;
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,21 +48,38 @@
}
public synchronized void clear() {
- mBackupImage = null;
+ updateScreenNail(null, false);
mImageWidth = 0;
mImageHeight = 0;
mLevelCount = 0;
mRegionDecoder = null;
- 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;
mLevelCount = 0;
- 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;
+ }
+
+ private void updateScreenNail(ScreenNail screenNail, boolean own) {
+ if (mScreenNail != null && mOwnScreenNail) {
+ mScreenNail.recycle();
+ }
+ mScreenNail = screenNail;
+ mOwnScreenNail = own;
}
public synchronized void setRegionDecoder(BitmapRegionDecoder decoder) {
@@ -68,79 +87,80 @@
mImageWidth = decoder.getWidth();
mImageHeight = decoder.getHeight();
mLevelCount = calculateLevelCount();
- mFailedToLoad = false;
}
private int calculateLevelCount() {
return Math.max(0, Utils.ceilLog2(
- (float) mImageWidth / mBackupImage.getWidth()));
+ (float) mImageWidth / mScreenNail.getWidth()));
}
+ // Gets a sub image on a rectangle of the current photo. For example,
+ // getTile(1, 50, 50, 100, 3, pool) means to get the region located
+ // at (50, 50) with sample level 1 (ie, down sampled by 2^1) and the
+ // target tile size (after sampling) 100 with border 3.
+ //
+ // From this spec, we can infer the actual tile size to be
+ // 100 + 3x2 = 106, and the size of the region to be extracted from the
+ // photo to be 200 with border 6.
+ //
+ // As a result, we should decode region (50-6, 50-6, 250+6, 250+6) or
+ // (44, 44, 256, 256) from the original photo and down sample it to 106.
@Override
- public synchronized Bitmap getTile(int level, int x, int y, int tileSize,
- int borderSize) {
- if (mRegionDecoder == null) return null;
-
- // wantRegion is the rectangle on the original image we want. askRegion
- // is the rectangle on the original image that we will ask from
- // mRegionDecoder. Both are in the coordinates of the original image,
- // not the coordinates of the scaled-down images.
- Rect wantRegion = new Rect();
- Rect askRegion = new Rect();
-
+ public Bitmap getTile(int level, int x, int y, int tileSize,
+ int borderSize, BitmapPool pool) {
int b = borderSize << level;
- wantRegion.set(x - b, y - b, x + (tileSize << level) + b,
- y + (tileSize << level) + b);
+ int t = tileSize << level;
- // askRegion is the intersection of wantRegion and the original image.
- askRegion.set(0, 0, mImageWidth, mImageHeight);
- Utils.assertTrue(askRegion.intersect(wantRegion));
+ Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b);
+
+ boolean needClear;
+ BitmapRegionDecoder regionDecoder = null;
+
+ synchronized (this) {
+ regionDecoder = mRegionDecoder;
+ if (regionDecoder == null) return null;
+
+ // We need to clear a reused bitmap, if wantRegion is not fully
+ // within the image.
+ needClear = !new Rect(0, 0, mImageWidth, mImageHeight)
+ .contains(wantRegion);
+ }
+
+ Bitmap bitmap = pool == null ? null : pool.getBitmap();
+ if (bitmap != null) {
+ if (needClear) bitmap.eraseColor(0);
+ } else {
+ int s = tileSize + 2 * borderSize;
+ bitmap = Bitmap.createBitmap(s, s, Config.ARGB_8888);
+ }
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.ARGB_8888;
options.inPreferQualityOverSpeed = true;
options.inSampleSize = (1 << level);
+ options.inBitmap = bitmap;
- Bitmap bitmap;
-
- // In CropImage, we may call the decodeRegion() concurrently.
- synchronized (mRegionDecoder) {
- bitmap = mRegionDecoder.decodeRegion(askRegion, options);
+ try {
+ // In CropImage, we may call the decodeRegion() concurrently.
+ synchronized (regionDecoder) {
+ bitmap = regionDecoder.decodeRegion(wantRegion, options);
+ }
+ } finally {
+ if (options.inBitmap != bitmap && options.inBitmap != null) {
+ if (pool != null) pool.recycle(options.inBitmap);
+ options.inBitmap = null;
+ }
}
if (bitmap == null) {
Log.w(TAG, "fail in decoding region");
- return null;
}
-
- if (wantRegion.equals(askRegion)) return bitmap;
-
- // Now the wantRegion does not match the askRegion. This means we are at
- // a boundary tile, and we need to add paddings. Create a new Bitmap
- // and copy over.
- int size = tileSize + 2 * borderSize;
- Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
- Canvas canvas = new Canvas(result);
- int offsetX = (askRegion.left - wantRegion.left) >> level;
- int offsetY = (askRegion.top - wantRegion.top) >> level;
- canvas.drawBitmap(bitmap, offsetX, offsetY, null);
-
- // If the valid region (covered by bitmap or border) is smaller than the
- // result bitmap, subset it.
- int endX = offsetX + bitmap.getWidth() + borderSize;
- int endY = offsetY + bitmap.getHeight() + borderSize;
- bitmap.recycle();
- if (endX < size || endY < size) {
- return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX),
- Math.min(size, endY));
- } else {
- return result;
- }
+ return bitmap;
}
@Override
- public Bitmap getBackupImage() {
- return mBackupImage;
+ public ScreenNail getScreenNail() {
+ return mScreenNail;
}
@Override
@@ -157,13 +177,4 @@
public int getLevelCount() {
return mLevelCount;
}
-
- public void setFailedToLoad() {
- mFailedToLoad = true;
- }
-
- @Override
- public boolean isFailedToLoad() {
- return mFailedToLoad;
- }
}
diff --git a/src/com/android/gallery3d/ui/UploadedTexture.java b/src/com/android/gallery3d/ui/UploadedTexture.java
index 1777048..0fe5067 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;
@@ -51,6 +52,9 @@
@SuppressWarnings("unused")
private static final String TAG = "Texture";
private boolean mContentValid = true;
+
+ // indicate this textures is being uploaded in background
+ private boolean mIsUploading = false;
private boolean mOpaque = true;
private boolean mThrottled = false;
private static int sUploadedCount;
@@ -71,6 +75,14 @@
}
}
+ protected void setIsUploading(boolean uploading) {
+ mIsUploading = uploading;
+ }
+
+ public boolean isUploading() {
+ return mIsUploading;
+ }
+
private static class BorderKey implements Cloneable {
public boolean vertical;
public Config config;
@@ -127,10 +139,6 @@
int h = mBitmap.getHeight() + mBorder * 2;
if (mWidth == UNSPECIFIED) {
setSize(w, h);
- } else if (mWidth != w || mHeight != h) {
- throw new IllegalStateException(String.format(
- "cannot change size: this = %s, orig = %sx%s, new = %sx%s",
- toString(), mWidth, mHeight, w, h));
}
}
return mBitmap;
@@ -168,8 +176,8 @@
/**
* Whether the content on GPU is valid.
*/
- public boolean isContentValid(GLCanvas canvas) {
- return isLoaded(canvas) && mContentValid;
+ public boolean isContentValid() {
+ return isLoaded() && mContentValid;
}
/**
@@ -177,7 +185,7 @@
* @param canvas
*/
public void updateContent(GLCanvas canvas) {
- if (!isLoaded(canvas)) {
+ if (!isLoaded()) {
if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
return;
}
@@ -217,6 +225,9 @@
int height = bHeight + mBorder * 2;
int texWidth = getTextureWidth();
int texHeight = getTextureHeight();
+
+ Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
+
// Define a vertically flipped crop rectangle for
// OES_draw_texture.
// The four values in sCropRect are: left, bottom, width, and
@@ -227,7 +238,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);
@@ -284,7 +295,7 @@
// Update texture state.
setAssociatedCanvas(canvas);
mId = sTextureId[0];
- mState = UploadedTexture.STATE_LOADED;
+ mState = STATE_LOADED;
mContentValid = true;
} else {
mState = STATE_ERROR;
@@ -295,7 +306,12 @@
@Override
protected boolean onBind(GLCanvas canvas) {
updateContent(canvas);
- return isContentValid(canvas);
+ return isContentValid();
+ }
+
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
}
public void setOpaque(boolean isOpaque) {
diff --git a/src/com/android/gallery3d/ui/PositionProvider.java b/src/com/android/gallery3d/util/BucketNames.java
similarity index 65%
rename from src/com/android/gallery3d/ui/PositionProvider.java
rename to src/com/android/gallery3d/util/BucketNames.java
index 930c61e..043dd3d 100644
--- a/src/com/android/gallery3d/ui/PositionProvider.java
+++ b/src/com/android/gallery3d/util/BucketNames.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.
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package com.android.gallery3d.ui;
+package com.android.gallery3d.util;
-import com.android.gallery3d.ui.PositionRepository.Position;
+/**
+ * Bucket names for buckets that are created and used in the Gallery.
+ */
+public class BucketNames {
-public interface PositionProvider {
- public Position getPosition(long identity, Position target);
+ public static final String IMPORTED = "Imported";
+ public static final String DOWNLOAD = "download";
}
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..1d70914 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;
@@ -49,6 +50,7 @@
private static final String TAG = "GalleryUtils";
private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";
+ private static final String CAMERA_LAUNCHER_NAME = "com.android.camera.CameraLauncher";
private static final String MIME_TYPE_IMAGE = "image/*";
private static final String MIME_TYPE_VIDEO = "video/*";
@@ -62,13 +64,11 @@
private static final String KEY_CAMERA_UPDATE = "camera-update";
private static final String KEY_HAS_CAMERA = "has-camera";
- private static Context sContext;
-
-
- static float sPixelDensity = -1f;
+ private static float sPixelDensity = -1f;
+ private static boolean sCameraAvailableInitialized = false;
+ private static boolean sCameraAvailable;
public static void initialize(Context context) {
- sContext = context;
if (sPixelDensity < 0) {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager)
@@ -191,7 +191,7 @@
return prefs.getBoolean(hasKey, true);
}
- public static boolean isCameraAvailable(Context context) {
+ public static boolean isAnyCameraAvailable(Context context) {
int version = PackagesMonitor.getPackagesVersion(context);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getInt(KEY_CAMERA_UPDATE, 0) != version) {
@@ -205,6 +205,25 @@
return prefs.getBoolean(KEY_HAS_CAMERA, true);
}
+ public static boolean isCameraAvailable(Context context) {
+ if (sCameraAvailableInitialized) return sCameraAvailable;
+ PackageManager pm = context.getPackageManager();
+ ComponentName name = new ComponentName(context, CAMERA_LAUNCHER_NAME);
+ int state = pm.getComponentEnabledSetting(name);
+ sCameraAvailableInitialized = true;
+ sCameraAvailable =
+ (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+ || (state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ return sCameraAvailable;
+ }
+
+ public static void startCameraActivity(Context context) {
+ Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
+ .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+
public static boolean isValidLocation(double latitude, double longitude) {
// TODO: change || to && after we fix the default location issue
return (latitude != MediaItem.INVALID_LATLNG || longitude != MediaItem.INVALID_LATLNG);
@@ -271,11 +290,6 @@
return durationValue;
}
- public static void setSpinnerVisibility(final Activity activity,
- final boolean visible) {
- SpinnerVisibilitySetter.getInstance(activity).setSpinnerVisibility(visible);
- }
-
public static int determineTypeBits(Context context, Intent intent) {
int typeBits = 0;
String type = intent.resolveType(context);
@@ -324,31 +338,6 @@
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();
diff --git a/src/com/android/gallery3d/util/HelpUtils.java b/src/com/android/gallery3d/util/HelpUtils.java
new file mode 100644
index 0000000..2f02642
--- /dev/null
+++ b/src/com/android/gallery3d/util/HelpUtils.java
@@ -0,0 +1,141 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.MenuItem;
+
+import java.util.Locale;
+
+/**
+ * Functions to easily prepare contextual help menu option items with an intent that opens up the
+ * browser to a particular URL, while taking into account the preferred language and app version.
+ */
+public class HelpUtils {
+ private final static String TAG = HelpUtils.class.getName();
+
+ /**
+ * Help URL query parameter key for the preferred language.
+ */
+ private final static String PARAM_LANGUAGE_CODE = "hl";
+
+ /**
+ * Help URL query parameter key for the app version.
+ */
+ private final static String PARAM_VERSION = "version";
+
+ /**
+ * Cached version code to prevent repeated calls to the package manager.
+ */
+ private static String sCachedVersionCode = null;
+
+ /** Static helper that is not instantiable*/
+ private HelpUtils() { }
+
+ /**
+ * Prepares the help menu item by doing the following.
+ * - If the string corresponding to the helpUrlResourceId is empty or null, then the help menu
+ * item is made invisible.
+ * - Otherwise, this makes the help menu item visible and sets the intent for the help menu
+ * item to view the URL.
+ *
+ * @return returns whether the help menu item has been made visible.
+ */
+ public static boolean prepareHelpMenuItem(Context context, MenuItem helpMenuItem,
+ int helpUrlResourceId) {
+ String helpUrlString = context.getResources().getString(helpUrlResourceId);
+ return prepareHelpMenuItem(context, helpMenuItem, helpUrlString);
+ }
+
+ /**
+ * Prepares the help menu item by doing the following.
+ * - If the helpUrlString is empty or null, the help menu item is made invisible.
+ * - Otherwise, this makes the help menu item visible and sets the intent for the help menu
+ * item to view the URL.
+ *
+ * @return returns whether the help menu item has been made visible.
+ */
+ public static boolean prepareHelpMenuItem(Context context, MenuItem helpMenuItem,
+ String helpUrlString) {
+ if (TextUtils.isEmpty(helpUrlString)) {
+ // The help url string is empty or null, so set the help menu item to be invisible.
+ helpMenuItem.setVisible(false);
+
+ // return that the help menu item is not visible (i.e. false)
+ return false;
+ } else {
+ // The help url string exists, so first add in some extra query parameters.
+ final Uri fullUri = uriWithAddedParameters(context, Uri.parse(helpUrlString));
+
+ // Then, create an intent that will be fired when the user
+ // selects this help menu item.
+ Intent intent = new Intent(Intent.ACTION_VIEW, fullUri);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+
+ // Set the intent to the help menu item, show the help menu item in the overflow
+ // menu, and make it visible.
+ helpMenuItem.setIntent(intent);
+ helpMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ helpMenuItem.setVisible(true);
+
+ // return that the help menu item is visible (i.e., true)
+ return true;
+ }
+ }
+
+ /**
+ * Adds two query parameters into the Uri, namely the language code and the version code
+ * of the app's package as gotten via the context.
+ * @return the uri with added query parameters
+ */
+ private static Uri uriWithAddedParameters(Context context, Uri baseUri) {
+ Uri.Builder builder = baseUri.buildUpon();
+
+ // Add in the preferred language
+ builder.appendQueryParameter(PARAM_LANGUAGE_CODE, Locale.getDefault().toString());
+
+ // Add in the package version code
+ if (sCachedVersionCode == null) {
+ // There is no cached version code, so try to get it from the package manager.
+ try {
+ // cache the version code
+ PackageInfo info = context.getPackageManager().getPackageInfo(
+ context.getPackageName(), 0);
+ sCachedVersionCode = Integer.toString(info.versionCode);
+
+ // append the version code to the uri
+ builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode);
+ } catch (NameNotFoundException e) {
+ // Cannot find the package name, so don't add in the version parameter
+ // This shouldn't happen.
+ Log.wtf(TAG, "Invalid package name for context", e);
+ }
+ } else {
+ builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode);
+ }
+
+ // Build the full uri and return it
+ return builder.build();
+ }
+}
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..3bb3f8b 100644
--- a/src/com/android/gallery3d/util/MediaSetUtils.java
+++ b/src/com/android/gallery3d/util/MediaSetUtils.java
@@ -16,12 +16,11 @@
package com.android.gallery3d.util;
-import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.data.MtpContext;
-import com.android.gallery3d.data.Path;
-
import android.os.Environment;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
+
import java.util.Comparator;
public class MediaSetUtils {
@@ -30,10 +29,14 @@
public static final int CAMERA_BUCKET_ID = GalleryUtils.getBucketId(
Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");
public static final int DOWNLOAD_BUCKET_ID = GalleryUtils.getBucketId(
- Environment.getExternalStorageDirectory().toString() + "/download");
+ Environment.getExternalStorageDirectory().toString() + "/"
+ + BucketNames.DOWNLOAD);
public static final int IMPORTED_BUCKET_ID = GalleryUtils.getBucketId(
Environment.getExternalStorageDirectory().toString() + "/"
- + MtpContext.NAME_IMPORTED_FOLDER);
+ + BucketNames.IMPORTED);
+ 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
deleted file mode 100644
index 6ccc264..0000000
--- a/src/com/android/gallery3d/util/SpinnerVisibilitySetter.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.util;
-
-import android.app.Activity;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-
-import java.util.WeakHashMap;
-
-/**
- * This class manages the visibility of the progress spinner in the action bar for an
- * Activity. It filters out short-lived appearances of the progress spinner by only
- * showing the spinner if it hasn't been hidden again before the end of a specified
- * delay period. It also enforces a minimum display time once the spinner is made visible.
- * This meant to cut down on the frequent "flashes" of the progress spinner.
- */
-public class SpinnerVisibilitySetter {
-
- private static final int MSG_SHOW_SPINNER = 1;
- private static final int MSG_HIDE_SPINNER = 2;
-
- // Amount of time after a show request that the progress spinner is actually made visible.
- // This means that any show/hide requests that happen subsequently within this period
- // of time will be ignored.
- private static final long SPINNER_DISPLAY_DELAY = 1000;
-
- // The minimum amount of time the progress spinner must be visible before it can be hidden.
- private static final long MIN_SPINNER_DISPLAY_TIME = 2000;
-
- static final WeakHashMap<Activity, SpinnerVisibilitySetter> sInstanceMap =
- new WeakHashMap<Activity, SpinnerVisibilitySetter>();
-
- private long mSpinnerVisibilityStartTime = -1;
- private Activity mActivity;
-
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case MSG_SHOW_SPINNER:
- removeMessages(MSG_SHOW_SPINNER);
- if (mSpinnerVisibilityStartTime >= 0) break;
- mSpinnerVisibilityStartTime = SystemClock.elapsedRealtime();
- mActivity.setProgressBarIndeterminateVisibility(true);
- break;
- case MSG_HIDE_SPINNER:
- removeMessages(MSG_HIDE_SPINNER);
- if (mSpinnerVisibilityStartTime < 0) break;
- long t = SystemClock.elapsedRealtime() - mSpinnerVisibilityStartTime;
- if (t >= MIN_SPINNER_DISPLAY_TIME) {
- mSpinnerVisibilityStartTime = -1;
- mActivity.setProgressBarIndeterminateVisibility(false);
- } else {
- sendEmptyMessageDelayed(MSG_HIDE_SPINNER, MIN_SPINNER_DISPLAY_TIME - t);
- }
- break;
- }
- }
- };
-
- /**
- * Gets the <code>SpinnerVisibilitySetter</code> for the given <code>activity</code>.
- *
- * This method must be called from the main thread.
- */
- public static SpinnerVisibilitySetter getInstance(Activity activity) {
- synchronized(sInstanceMap) {
- SpinnerVisibilitySetter setter = sInstanceMap.get(activity);
- if (setter == null) {
- setter = new SpinnerVisibilitySetter(activity);
- sInstanceMap.put(activity, setter);
- }
- return setter;
- }
- }
-
- private SpinnerVisibilitySetter(Activity activity) {
- mActivity = activity;
- }
-
- public void setSpinnerVisibility(boolean visible) {
- if (visible) {
- mHandler.removeMessages(MSG_HIDE_SPINNER);
- mHandler.sendEmptyMessageDelayed(MSG_SHOW_SPINNER, SPINNER_DISPLAY_DELAY);
- } else {
- mHandler.removeMessages(MSG_SHOW_SPINNER);
- mHandler.sendEmptyMessage(MSG_HIDE_SPINNER);
- }
- }
-}
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..5f749d8 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,7 @@
}
public void deleteRecycledResources() {}
public void multiplyMatrix(float[] mMatrix, int offset) {}
+ public void dumpStatisticsAndClear() {}
+ public void beginRenderTarget(RawTexture texture) {}
+ public void endRenderTarget() {}
}
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/GLRootMock.java b/tests/src/com/android/gallery3d/ui/GLRootMock.java
index c83e943..467edfc 100644
--- a/tests/src/com/android/gallery3d/ui/GLRootMock.java
+++ b/tests/src/com/android/gallery3d/ui/GLRootMock.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.ui;
+import android.graphics.Matrix;
import com.android.gallery3d.anim.CanvasAnimation;
public class GLRootMock implements GLRoot {
@@ -34,4 +35,11 @@
public void lockRenderThread() {}
public void unlockRenderThread() {}
public void setContentPane(GLView content) {}
+ public void setOrientationSource(OrientationSource source) {}
+ public int getDisplayRotation() { return 0; }
+ public int getCompensation() { return 0; }
+ public Matrix getCompensationMatrix() { return null; }
+ public void freeze() {}
+ public void unfreeze() {}
+ public void setLightsOutMode(boolean enabled) {}
}
diff --git a/tests/src/com/android/gallery3d/ui/GLRootStub.java b/tests/src/com/android/gallery3d/ui/GLRootStub.java
index d6bc678..0f3a001 100644
--- a/tests/src/com/android/gallery3d/ui/GLRootStub.java
+++ b/tests/src/com/android/gallery3d/ui/GLRootStub.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.ui;
+import android.graphics.Matrix;
import com.android.gallery3d.anim.CanvasAnimation;
public class GLRootStub implements GLRoot {
@@ -27,4 +28,11 @@
public void lockRenderThread() {}
public void unlockRenderThread() {}
public void setContentPane(GLView content) {}
+ public void setOrientationSource(OrientationSource source) {}
+ public int getDisplayRotation() { return 0; }
+ public int getCompensation() { return 0; }
+ public Matrix getCompensationMatrix() { return null; }
+ public void freeze() {}
+ public void unfreeze() {}
+ public void setLightsOutMode(boolean enabled) {}
}
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..36446b3 100644
--- a/tests/src/com/android/gallery3d/ui/TextureTest.java
+++ b/tests/src/com/android/gallery3d/ui/TextureTest.java
@@ -20,10 +20,10 @@
import android.graphics.Bitmap.Config;
import android.test.suitebuilder.annotation.SmallTest;
-import javax.microedition.khronos.opengles.GL11;
-
import junit.framework.TestCase;
+import javax.microedition.khronos.opengles.GL11;
+
@SmallTest
public class TextureTest extends TestCase {
@SuppressWarnings("unused")
@@ -34,7 +34,7 @@
int mOpaqueCalled;
MyBasicTexture(GLCanvas canvas, int id) {
- super(canvas, id, BasicTexture.STATE_UNLOADED);
+ super(canvas, id, 0);
}
@Override
@@ -43,6 +43,11 @@
return true;
}
+ @Override
+ protected int getTarget() {
+ return GL11.GL_TEXTURE_2D;
+ }
+
public boolean isOpaque() {
mOpaqueCalled++;
return true;
@@ -71,13 +76,13 @@
assertEquals(4, texture.getTextureWidth());
assertEquals(8, texture.getTextureHeight());
- assertFalse(texture.isLoaded(canvas));
+ assertFalse(texture.isLoaded());
texture.upload();
- assertTrue(texture.isLoaded(canvas));
+ assertTrue(texture.isLoaded());
// For a different GL, it's not loaded.
GLCanvas canvas2 = new GLCanvasImpl(new GLStub());
- assertFalse(texture.isLoaded(canvas2));
+ assertFalse(texture.isLoaded());
assertEquals(0, texture.mOnBindCalled);
assertEquals(0, texture.mOpaqueCalled);
@@ -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);
@@ -156,20 +143,20 @@
assertEquals(0, texture.mGetCalled);
texture.draw(canvas, 0, 0);
assertEquals(1, texture.mGetCalled);
- assertTrue(texture.isLoaded(canvas));
- assertTrue(texture.isContentValid(canvas));
+ assertTrue(texture.isLoaded());
+ assertTrue(texture.isContentValid());
// invalidate content and it should be freed.
texture.invalidateContent();
- assertFalse(texture.isContentValid(canvas));
+ assertFalse(texture.isContentValid());
assertEquals(1, texture.mFreeCalled);
- assertTrue(texture.isLoaded(canvas)); // But it's still loaded
+ assertTrue(texture.isLoaded()); // But it's still loaded
// draw it again and the bitmap should be fetched again.
texture.draw(canvas, 0, 0);
assertEquals(2, texture.mGetCalled);
- assertTrue(texture.isLoaded(canvas));
- assertTrue(texture.isContentValid(canvas));
+ assertTrue(texture.isLoaded());
+ assertTrue(texture.isContentValid());
// recycle the texture and it should be freed again.
texture.recycle();
@@ -181,7 +168,7 @@
class MyTextureForMixed extends BasicTexture {
MyTextureForMixed(GLCanvas canvas, int id) {
- super(canvas, id, BasicTexture.STATE_UNLOADED);
+ super(canvas, id, 0);
}
@Override
@@ -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);
+ }
+}