summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/util')
-rw-r--r--src/com/android/gallery3d/util/CacheManager.java82
-rw-r--r--src/com/android/gallery3d/util/Future.java35
-rw-r--r--src/com/android/gallery3d/util/FutureListener.java21
-rw-r--r--src/com/android/gallery3d/util/FutureTask.java86
-rw-r--r--src/com/android/gallery3d/util/GalleryUtils.java327
-rw-r--r--src/com/android/gallery3d/util/IdentityCache.java74
-rw-r--r--src/com/android/gallery3d/util/IntArray.java54
-rw-r--r--src/com/android/gallery3d/util/InterruptableOutputStream.java67
-rw-r--r--src/com/android/gallery3d/util/LinkedNode.java75
-rw-r--r--src/com/android/gallery3d/util/Log.java53
-rw-r--r--src/com/android/gallery3d/util/MediaSetUtils.java56
-rw-r--r--src/com/android/gallery3d/util/PriorityThreadFactory.java48
-rw-r--r--src/com/android/gallery3d/util/ReverseGeocoder.java417
-rw-r--r--src/com/android/gallery3d/util/ThreadPool.java252
-rw-r--r--src/com/android/gallery3d/util/UpdateHelper.java67
15 files changed, 1714 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/util/CacheManager.java b/src/com/android/gallery3d/util/CacheManager.java
new file mode 100644
index 000000000..fcc444e98
--- /dev/null
+++ b/src/com/android/gallery3d/util/CacheManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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 com.android.gallery3d.common.BlobCache;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+
+public class CacheManager {
+ private static final String TAG = "CacheManager";
+ private static final String KEY_CACHE_UP_TO_DATE = "cache-up-to-date";
+ private static HashMap<String, BlobCache> sCacheMap =
+ new HashMap<String, BlobCache>();
+ private static boolean sOldCheckDone = false;
+
+ // Return null when we cannot instantiate a BlobCache, e.g.:
+ // there is no SD card found.
+ // This can only be called from data thread.
+ public static BlobCache getCache(Context context, String filename,
+ int maxEntries, int maxBytes, int version) {
+ synchronized (sCacheMap) {
+ if (!sOldCheckDone) {
+ removeOldFilesIfNecessary(context);
+ sOldCheckDone = true;
+ }
+ BlobCache cache = sCacheMap.get(filename);
+ if (cache == null) {
+ File cacheDir = context.getExternalCacheDir();
+ String path = cacheDir.getAbsolutePath() + "/" + filename;
+ try {
+ cache = new BlobCache(path, maxEntries, maxBytes, false,
+ version);
+ sCacheMap.put(filename, cache);
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot instantiate cache!", e);
+ }
+ }
+ return cache;
+ }
+ }
+
+ // Removes the old files if the data is wiped.
+ private static void removeOldFilesIfNecessary(Context context) {
+ SharedPreferences pref = PreferenceManager
+ .getDefaultSharedPreferences(context);
+ int n = 0;
+ try {
+ n = pref.getInt(KEY_CACHE_UP_TO_DATE, 0);
+ } catch (Throwable t) {
+ // ignore.
+ }
+ if (n != 0) return;
+ pref.edit().putInt(KEY_CACHE_UP_TO_DATE, 1).commit();
+
+ File cacheDir = context.getExternalCacheDir();
+ String prefix = cacheDir.getAbsolutePath() + "/";
+
+ BlobCache.deleteFiles(prefix + "imgcache");
+ BlobCache.deleteFiles(prefix + "rev_geocoding");
+ BlobCache.deleteFiles(prefix + "bookmark");
+ }
+}
diff --git a/src/com/android/gallery3d/util/Future.java b/src/com/android/gallery3d/util/Future.java
new file mode 100644
index 000000000..580a2a120
--- /dev/null
+++ b/src/com/android/gallery3d/util/Future.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+// This Future differs from the java.util.concurrent.Future in these aspects:
+//
+// - Once cancel() is called, isCancelled() always returns true. It is a sticky
+// flag used to communicate to the implementation. The implmentation may
+// ignore that flag. Regardless whether the Future is cancelled, a return
+// value will be provided to get(). The implementation may choose to return
+// null if it finds the Future is cancelled.
+//
+// - get() does not throw exceptions.
+//
+public interface Future<T> {
+ public void cancel();
+ public boolean isCancelled();
+ public boolean isDone();
+ public T get();
+ public void waitDone();
+}
diff --git a/src/com/android/gallery3d/util/FutureListener.java b/src/com/android/gallery3d/util/FutureListener.java
new file mode 100644
index 000000000..ed1f820c7
--- /dev/null
+++ b/src/com/android/gallery3d/util/FutureListener.java
@@ -0,0 +1,21 @@
+/*
+ * 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;
+
+public interface FutureListener<T> {
+ public void onFutureDone(Future<T> future);
+}
diff --git a/src/com/android/gallery3d/util/FutureTask.java b/src/com/android/gallery3d/util/FutureTask.java
new file mode 100644
index 000000000..9cfab27cb
--- /dev/null
+++ b/src/com/android/gallery3d/util/FutureTask.java
@@ -0,0 +1,86 @@
+/*
+ * 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
new file mode 100644
index 000000000..2fed46a22
--- /dev/null
+++ b/src/com/android/gallery3d/util/GalleryUtils.java
@@ -0,0 +1,327 @@
+/*
+ * 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 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;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.ConditionVariable;
+import android.os.Environment;
+import android.os.StatFs;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class GalleryUtils {
+ 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 MIME_TYPE_IMAGE = "image/*";
+ private static final String MIME_TYPE_VIDEO = "video/*";
+ private static final String MIME_TYPE_ALL = "*/*";
+
+ private static final String PREFIX_PHOTO_EDITOR_UPDATE = "editor-update-";
+ private static final String PREFIX_HAS_PHOTO_EDITOR = "has-editor-";
+
+ 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;
+
+ public static void initialize(Context context) {
+ sContext = context;
+ if (sPixelDensity < 0) {
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager wm = (WindowManager)
+ context.getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getMetrics(metrics);
+ sPixelDensity = metrics.density;
+ }
+ }
+
+ public static float dpToPixel(float dp) {
+ return sPixelDensity * dp;
+ }
+
+ public static int dpToPixel(int dp) {
+ return Math.round(dpToPixel((float) dp));
+ }
+
+ public static int meterToPixel(float meter) {
+ // 1 meter = 39.37 inches, 1 inch = 160 dp.
+ return Math.round(dpToPixel(meter * 39.37f * 160));
+ }
+
+ public static byte[] getBytes(String in) {
+ byte[] result = new byte[in.length() * 2];
+ int output = 0;
+ for (char ch : in.toCharArray()) {
+ result[output++] = (byte) (ch & 0xFF);
+ result[output++] = (byte) (ch >> 8);
+ }
+ return result;
+ }
+
+ // Below are used the detect using database in the render thread. It only
+ // works most of the time, but that's ok because it's for debugging only.
+
+ private static volatile Thread sCurrentThread;
+ private static volatile boolean sWarned;
+
+ public static void setRenderThread() {
+ sCurrentThread = Thread.currentThread();
+ }
+
+ public static void assertNotInRenderThread() {
+ if (!sWarned) {
+ if (Thread.currentThread() == sCurrentThread) {
+ sWarned = true;
+ Log.w(TAG, new Throwable("Should not do this in render thread"));
+ }
+ }
+ }
+
+ private static final double RAD_PER_DEG = Math.PI / 180.0;
+ private static final double EARTH_RADIUS_METERS = 6367000.0;
+
+ public static double fastDistanceMeters(double latRad1, double lngRad1,
+ double latRad2, double lngRad2) {
+ if ((Math.abs(latRad1 - latRad2) > RAD_PER_DEG)
+ || (Math.abs(lngRad1 - lngRad2) > RAD_PER_DEG)) {
+ return accurateDistanceMeters(latRad1, lngRad1, latRad2, lngRad2);
+ }
+ // Approximate sin(x) = x.
+ double sineLat = (latRad1 - latRad2);
+
+ // Approximate sin(x) = x.
+ double sineLng = (lngRad1 - lngRad2);
+
+ // Approximate cos(lat1) * cos(lat2) using
+ // cos((lat1 + lat2)/2) ^ 2
+ double cosTerms = Math.cos((latRad1 + latRad2) / 2.0);
+ cosTerms = cosTerms * cosTerms;
+ double trigTerm = sineLat * sineLat + cosTerms * sineLng * sineLng;
+ trigTerm = Math.sqrt(trigTerm);
+
+ // Approximate arcsin(x) = x
+ return EARTH_RADIUS_METERS * trigTerm;
+ }
+
+ public static double accurateDistanceMeters(double lat1, double lng1,
+ double lat2, double lng2) {
+ double dlat = Math.sin(0.5 * (lat2 - lat1));
+ double dlng = Math.sin(0.5 * (lng2 - lng1));
+ double x = dlat * dlat + dlng * dlng * Math.cos(lat1) * Math.cos(lat2);
+ return (2 * Math.atan2(Math.sqrt(x), Math.sqrt(Math.max(0.0,
+ 1.0 - x)))) * EARTH_RADIUS_METERS;
+ }
+
+
+ public static final double toMile(double meter) {
+ return meter / 1609;
+ }
+
+ // For debugging, it will block the caller for timeout millis.
+ public static void fakeBusy(JobContext jc, int timeout) {
+ final ConditionVariable cv = new ConditionVariable();
+ jc.setCancelListener(new CancelListener() {
+ public void onCancel() {
+ cv.open();
+ }
+ });
+ cv.block(timeout);
+ jc.setCancelListener(null);
+ }
+
+ public static boolean isEditorAvailable(Context context, String mimeType) {
+ int version = PackagesMonitor.getPackagesVersion(context);
+
+ String updateKey = PREFIX_PHOTO_EDITOR_UPDATE + mimeType;
+ String hasKey = PREFIX_HAS_PHOTO_EDITOR + mimeType;
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ if (prefs.getInt(updateKey, 0) != version) {
+ PackageManager packageManager = context.getPackageManager();
+ List<ResolveInfo> infos = packageManager.queryIntentActivities(
+ new Intent(Intent.ACTION_EDIT).setType(mimeType), 0);
+ prefs.edit().putInt(updateKey, version)
+ .putBoolean(hasKey, !infos.isEmpty())
+ .commit();
+ }
+
+ return prefs.getBoolean(hasKey, true);
+ }
+
+ public static boolean isCameraAvailable(Context context) {
+ int version = PackagesMonitor.getPackagesVersion(context);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ if (prefs.getInt(KEY_CAMERA_UPDATE, 0) != version) {
+ PackageManager packageManager = context.getPackageManager();
+ List<ResolveInfo> infos = packageManager.queryIntentActivities(
+ new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA), 0);
+ prefs.edit().putInt(KEY_CAMERA_UPDATE, version)
+ .putBoolean(KEY_HAS_CAMERA, !infos.isEmpty())
+ .commit();
+ }
+ return prefs.getBoolean(KEY_HAS_CAMERA, true);
+ }
+
+ 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);
+ }
+ public static void showOnMap(Context context, double latitude, double longitude) {
+ try {
+ // We don't use "geo:latitude,longitude" because it only centers
+ // the MapView to the specified location, but we need a marker
+ // for further operations (routing to/from).
+ // The q=(lat, lng) syntax is suggested by geo-team.
+ String uri = String.format("http://maps.google.com/maps?f=q&q=(%f,%f)",
+ latitude, longitude);
+ ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
+ MAPS_CLASS_NAME);
+ Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse(uri)).setComponent(compName);
+ context.startActivity(mapsIntent);
+ } catch (ActivityNotFoundException e) {
+ // Use the "geo intent" if no GMM is installed
+ Log.e(TAG, "GMM activity not found!", e);
+ String url = String.format("geo:%f,%f", latitude, longitude);
+ Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ context.startActivity(mapsIntent);
+ }
+ }
+
+ public static void setViewPointMatrix(
+ float matrix[], float x, float y, float z) {
+ // The matrix is
+ // -z, 0, x, 0
+ // 0, -z, y, 0
+ // 0, 0, 1, 0
+ // 0, 0, 1, -z
+ Arrays.fill(matrix, 0, 16, 0);
+ matrix[0] = matrix[5] = matrix[15] = -z;
+ matrix[8] = x;
+ matrix[9] = y;
+ matrix[10] = matrix[11] = 1;
+ }
+
+ public static int getBucketId(String path) {
+ return path.toLowerCase().hashCode();
+ }
+
+ // Returns a (localized) string for the given duration (in seconds).
+ public static String formatDuration(final Context context, int duration) {
+ int h = duration / 3600;
+ int m = (duration - h * 3600) / 60;
+ int s = duration - (h * 3600 + m * 60);
+ String durationValue;
+ if (h == 0) {
+ durationValue = String.format(context.getString(R.string.details_ms), m, s);
+ } else {
+ durationValue = String.format(context.getString(R.string.details_hms), h, m, s);
+ }
+ return durationValue;
+ }
+
+ public static void setSpinnerVisibility(final Activity activity,
+ final boolean visible) {
+ activity.runOnUiThread(new Runnable() {
+ public void run() {
+ activity.setProgressBarIndeterminateVisibility(visible);
+ }
+ });
+ }
+
+ public static int determineTypeBits(Context context, Intent intent) {
+ int typeBits = 0;
+ String type = intent.resolveType(context);
+ if (intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false)) {
+ if (MIME_TYPE_ALL.equals(type)) {
+ typeBits = DataManager.INCLUDE_LOCAL_ALL_ONLY;
+ } else if (MIME_TYPE_IMAGE.equals(type)) {
+ typeBits = DataManager.INCLUDE_LOCAL_IMAGE_ONLY;
+ } else if (MIME_TYPE_VIDEO.equals(type)) {
+ typeBits = DataManager.INCLUDE_LOCAL_VIDEO_ONLY;
+ }
+ } else {
+ if (MIME_TYPE_ALL.equals(type)) {
+ typeBits = DataManager.INCLUDE_ALL;
+ } else if (MIME_TYPE_IMAGE.equals(type)) {
+ typeBits = DataManager.INCLUDE_IMAGE;
+ } else if (MIME_TYPE_VIDEO.equals(type)) {
+ typeBits = DataManager.INCLUDE_VIDEO;
+ }
+ }
+ if (typeBits == 0) typeBits = DataManager.INCLUDE_ALL;
+
+ return typeBits;
+ }
+
+ public static int getSelectionModePrompt(int typeBits) {
+ if ((typeBits & DataManager.INCLUDE_VIDEO) != 0) {
+ return (typeBits & DataManager.INCLUDE_IMAGE) == 0
+ ? R.string.select_video
+ : R.string.select_item;
+ }
+ return R.string.select_image;
+ }
+
+ 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 assertInMainThread() {
+ if (Thread.currentThread() == sContext.getMainLooper().getThread()) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/util/IdentityCache.java b/src/com/android/gallery3d/util/IdentityCache.java
new file mode 100644
index 000000000..02a46aef7
--- /dev/null
+++ b/src/com/android/gallery3d/util/IdentityCache.java
@@ -0,0 +1,74 @@
+/*
+ * 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.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Set;
+
+public class IdentityCache<K, V> {
+
+ private final HashMap<K, Entry<K, V>> mWeakMap =
+ new HashMap<K, Entry<K, V>>();
+ private ReferenceQueue<V> mQueue = new ReferenceQueue<V>();
+
+ public IdentityCache() {
+ }
+
+ private static class Entry<K, V> extends WeakReference<V> {
+ K mKey;
+
+ public Entry(K key, V value, ReferenceQueue<V> queue) {
+ super(value, queue);
+ mKey = key;
+ }
+ }
+
+ private void cleanUpWeakMap() {
+ Entry<K, V> entry = (Entry<K, V>) mQueue.poll();
+ while (entry != null) {
+ mWeakMap.remove(entry.mKey);
+ entry = (Entry<K, V>) mQueue.poll();
+ }
+ }
+
+ public synchronized V put(K key, V value) {
+ cleanUpWeakMap();
+ Entry<K, V> entry = mWeakMap.put(
+ key, new Entry<K, V>(key, value, mQueue));
+ return entry == null ? null : entry.get();
+ }
+
+ public synchronized V get(K key) {
+ cleanUpWeakMap();
+ Entry<K, V> entry = mWeakMap.get(key);
+ return entry == null ? null : entry.get();
+ }
+
+ public synchronized void clear() {
+ mWeakMap.clear();
+ mQueue = new ReferenceQueue<V>();
+ }
+
+ public synchronized ArrayList<K> keys() {
+ Set<K> set = mWeakMap.keySet();
+ ArrayList<K> result = new ArrayList<K>(set);
+ return result;
+ }
+}
diff --git a/src/com/android/gallery3d/util/IntArray.java b/src/com/android/gallery3d/util/IntArray.java
new file mode 100644
index 000000000..88657bbd6
--- /dev/null
+++ b/src/com/android/gallery3d/util/IntArray.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+public class IntArray {
+ private static final int INIT_CAPACITY = 8;
+
+ private int mData[] = new int[INIT_CAPACITY];
+ private int mSize = 0;
+
+ public void add(int value) {
+ if (mData.length == mSize) {
+ int temp[] = new int[mSize + mSize];
+ System.arraycopy(mData, 0, temp, 0, mSize);
+ mData = temp;
+ }
+ mData[mSize++] = value;
+ }
+
+ public int size() {
+ return mSize;
+ }
+
+ public int[] toArray(int[] result) {
+ if (result == null || result.length < mSize) {
+ result = new int[mSize];
+ }
+ System.arraycopy(mData, 0, result, 0, mSize);
+ return result;
+ }
+
+ public int[] getInternalArray() {
+ return mData;
+ }
+
+ public void clear() {
+ mSize = 0;
+ if (mData.length != INIT_CAPACITY) mData = new int[INIT_CAPACITY];
+ }
+}
diff --git a/src/com/android/gallery3d/util/InterruptableOutputStream.java b/src/com/android/gallery3d/util/InterruptableOutputStream.java
new file mode 100644
index 000000000..1ab62ab98
--- /dev/null
+++ b/src/com/android/gallery3d/util/InterruptableOutputStream.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.gallery3d.common.Utils;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+
+public class InterruptableOutputStream extends OutputStream {
+
+ private static final int MAX_WRITE_BYTES = 4096;
+
+ private OutputStream mOutputStream;
+ private volatile boolean mIsInterrupted = false;
+
+ public InterruptableOutputStream(OutputStream outputStream) {
+ mOutputStream = Utils.checkNotNull(outputStream);
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ if (mIsInterrupted) throw new InterruptedIOException();
+ mOutputStream.write(oneByte);
+ }
+
+ @Override
+ public void write(byte[] buffer, int offset, int count) throws IOException {
+ int end = offset + count;
+ while (offset < end) {
+ if (mIsInterrupted) throw new InterruptedIOException();
+ int bytesCount = Math.min(MAX_WRITE_BYTES, end - offset);
+ mOutputStream.write(buffer, offset, bytesCount);
+ offset += bytesCount;
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ mOutputStream.close();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ if (mIsInterrupted) throw new InterruptedIOException();
+ mOutputStream.flush();
+ }
+
+ public void interrupt() {
+ mIsInterrupted = true;
+ }
+}
diff --git a/src/com/android/gallery3d/util/LinkedNode.java b/src/com/android/gallery3d/util/LinkedNode.java
new file mode 100644
index 000000000..8554acd21
--- /dev/null
+++ b/src/com/android/gallery3d/util/LinkedNode.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+
+public class LinkedNode {
+ private LinkedNode mPrev;
+ private LinkedNode mNext;
+
+ public LinkedNode() {
+ mPrev = mNext = this;
+ }
+
+ public void insert(LinkedNode node) {
+ node.mNext = mNext;
+ mNext.mPrev = node;
+ node.mPrev = this;
+ mNext = node;
+ }
+
+ public void remove() {
+ if (mNext == this) throw new IllegalStateException();
+ mPrev.mNext = mNext;
+ mNext.mPrev = mPrev;
+ mPrev = mNext = null;
+ }
+
+ @SuppressWarnings("unchecked")
+ 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);
+ }
+
+ public T getFirst() {
+ return (T) (mHead.mNext == mHead ? null : mHead.mNext);
+ }
+
+ public T getLast() {
+ return (T) (mHead.mPrev == mHead ? null : mHead.mPrev);
+ }
+
+ public T nextOf(T node) {
+ return (T) (node.mNext == mHead ? null : node.mNext);
+ }
+
+ public T previousOf(T node) {
+ return (T) (node.mPrev == mHead ? null : node.mPrev);
+ }
+
+ }
+
+ public static <T extends LinkedNode> List<T> newList() {
+ return new List<T>();
+ }
+}
diff --git a/src/com/android/gallery3d/util/Log.java b/src/com/android/gallery3d/util/Log.java
new file mode 100644
index 000000000..d7f8e85d0
--- /dev/null
+++ b/src/com/android/gallery3d/util/Log.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+public class Log {
+ public static int v(String tag, String msg) {
+ return android.util.Log.v(tag, msg);
+ }
+ public static int v(String tag, String msg, Throwable tr) {
+ return android.util.Log.v(tag, msg, tr);
+ }
+ public static int d(String tag, String msg) {
+ return android.util.Log.d(tag, msg);
+ }
+ public static int d(String tag, String msg, Throwable tr) {
+ return android.util.Log.d(tag, msg, tr);
+ }
+ public static int i(String tag, String msg) {
+ return android.util.Log.i(tag, msg);
+ }
+ public static int i(String tag, String msg, Throwable tr) {
+ return android.util.Log.i(tag, msg, tr);
+ }
+ public static int w(String tag, String msg) {
+ return android.util.Log.w(tag, msg);
+ }
+ public static int w(String tag, String msg, Throwable tr) {
+ return android.util.Log.w(tag, msg, tr);
+ }
+ public static int w(String tag, Throwable tr) {
+ return android.util.Log.w(tag, tr);
+ }
+ public static int e(String tag, String msg) {
+ return android.util.Log.e(tag, msg);
+ }
+ public static int e(String tag, String msg, Throwable tr) {
+ return android.util.Log.e(tag, msg, tr);
+ }
+}
diff --git a/src/com/android/gallery3d/util/MediaSetUtils.java b/src/com/android/gallery3d/util/MediaSetUtils.java
new file mode 100644
index 000000000..817ffedcb
--- /dev/null
+++ b/src/com/android/gallery3d/util/MediaSetUtils.java
@@ -0,0 +1,56 @@
+/*
+ * 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 com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.MtpContext;
+import com.android.gallery3d.data.Path;
+
+import android.os.Environment;
+
+import java.util.Comparator;
+
+public class MediaSetUtils {
+ public static final Comparator<MediaSet> NAME_COMPARATOR = new NameComparator();
+
+ 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");
+ public static final int IMPORTED_BUCKET_ID = GalleryUtils.getBucketId(
+ Environment.getExternalStorageDirectory().toString() + "/"
+ + MtpContext.NAME_IMPORTED_FOLDER);
+
+ private static final Path[] CAMERA_PATHS = {
+ Path.fromString("/local/all/" + CAMERA_BUCKET_ID),
+ Path.fromString("/local/image/" + CAMERA_BUCKET_ID),
+ Path.fromString("/local/video/" + CAMERA_BUCKET_ID)};
+
+ public static boolean isCameraSource(Path path) {
+ return CAMERA_PATHS[0] == path || CAMERA_PATHS[1] == path
+ || CAMERA_PATHS[2] == path;
+ }
+
+ // Sort MediaSets by name
+ public static class NameComparator implements Comparator<MediaSet> {
+ public int compare(MediaSet set1, MediaSet set2) {
+ int result = set1.getName().compareToIgnoreCase(set2.getName());
+ if (result != 0) return result;
+ return set1.getPath().toString().compareTo(set2.getPath().toString());
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/util/PriorityThreadFactory.java b/src/com/android/gallery3d/util/PriorityThreadFactory.java
new file mode 100644
index 000000000..67b215274
--- /dev/null
+++ b/src/com/android/gallery3d/util/PriorityThreadFactory.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.util;
+
+
+import android.os.Process;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A thread factory that creates threads with a given thread priority.
+ */
+public class PriorityThreadFactory implements ThreadFactory {
+
+ private final int mPriority;
+ private final AtomicInteger mNumber = new AtomicInteger();
+ private final String mName;
+
+ public PriorityThreadFactory(String name, int priority) {
+ mName = name;
+ mPriority = priority;
+ }
+
+ public Thread newThread(Runnable r) {
+ return new Thread(r, mName + '-' + mNumber.getAndIncrement()) {
+ @Override
+ public void run() {
+ Process.setThreadPriority(mPriority);
+ super.run();
+ }
+ };
+ }
+
+}
diff --git a/src/com/android/gallery3d/util/ReverseGeocoder.java b/src/com/android/gallery3d/util/ReverseGeocoder.java
new file mode 100644
index 000000000..d253b4b96
--- /dev/null
+++ b/src/com/android/gallery3d/util/ReverseGeocoder.java
@@ -0,0 +1,417 @@
+/*
+ * 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 com.android.gallery3d.common.BlobCache;
+
+import android.content.Context;
+import android.location.Address;
+import android.location.Geocoder;
+import android.location.Location;
+import android.location.LocationManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+public class ReverseGeocoder {
+ private static final String TAG = "ReverseGeocoder";
+ public static final int EARTH_RADIUS_METERS = 6378137;
+ public static final int LAT_MIN = -90;
+ public static final int LAT_MAX = 90;
+ public static final int LON_MIN = -180;
+ public static final int LON_MAX = 180;
+ private static final int MAX_COUNTRY_NAME_LENGTH = 8;
+ // If two points are within 20 miles of each other, use
+ // "Around Palo Alto, CA" or "Around Mountain View, CA".
+ // instead of directly jumping to the next level and saying
+ // "California, US".
+ private static final int MAX_LOCALITY_MILE_RANGE = 20;
+
+ private static final String GEO_CACHE_FILE = "rev_geocoding";
+ private static final int GEO_CACHE_MAX_ENTRIES = 1000;
+ private static final int GEO_CACHE_MAX_BYTES = 500 * 1024;
+ private static final int GEO_CACHE_VERSION = 0;
+
+ public static class SetLatLong {
+ // The latitude and longitude of the min latitude point.
+ public double mMinLatLatitude = LAT_MAX;
+ public double mMinLatLongitude;
+ // The latitude and longitude of the max latitude point.
+ public double mMaxLatLatitude = LAT_MIN;
+ public double mMaxLatLongitude;
+ // The latitude and longitude of the min longitude point.
+ public double mMinLonLatitude;
+ public double mMinLonLongitude = LON_MAX;
+ // The latitude and longitude of the max longitude point.
+ public double mMaxLonLatitude;
+ public double mMaxLonLongitude = LON_MIN;
+ }
+
+ private Context mContext;
+ private Geocoder mGeocoder;
+ private BlobCache mGeoCache;
+ private ConnectivityManager mConnectivityManager;
+ private static Address sCurrentAddress; // last known address
+
+ public ReverseGeocoder(Context context) {
+ mContext = context;
+ mGeocoder = new Geocoder(mContext);
+ mGeoCache = CacheManager.getCache(context, GEO_CACHE_FILE,
+ GEO_CACHE_MAX_ENTRIES, GEO_CACHE_MAX_BYTES,
+ GEO_CACHE_VERSION);
+ mConnectivityManager = (ConnectivityManager)
+ context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ public String computeAddress(SetLatLong set) {
+ // The overall min and max latitudes and longitudes of the set.
+ double setMinLatitude = set.mMinLatLatitude;
+ double setMinLongitude = set.mMinLatLongitude;
+ double setMaxLatitude = set.mMaxLatLatitude;
+ double setMaxLongitude = set.mMaxLatLongitude;
+ if (Math.abs(set.mMaxLatLatitude - set.mMinLatLatitude)
+ < Math.abs(set.mMaxLonLongitude - set.mMinLonLongitude)) {
+ setMinLatitude = set.mMinLonLatitude;
+ setMinLongitude = set.mMinLonLongitude;
+ setMaxLatitude = set.mMaxLonLatitude;
+ setMaxLongitude = set.mMaxLonLongitude;
+ }
+ Address addr1 = lookupAddress(setMinLatitude, setMinLongitude, true);
+ Address addr2 = lookupAddress(setMaxLatitude, setMaxLongitude, true);
+ if (addr1 == null)
+ addr1 = addr2;
+ if (addr2 == null)
+ addr2 = addr1;
+ if (addr1 == null || addr2 == null) {
+ return null;
+ }
+
+ // Get current location, we decide the granularity of the string based
+ // on this.
+ LocationManager locationManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ Location location = null;
+ List<String> providers = locationManager.getAllProviders();
+ for (int i = 0; i < providers.size(); ++i) {
+ String provider = providers.get(i);
+ location = (provider != null) ? locationManager.getLastKnownLocation(provider) : null;
+ if (location != null)
+ break;
+ }
+ String currentCity = "";
+ String currentAdminArea = "";
+ String currentCountry = Locale.getDefault().getCountry();
+ if (location != null) {
+ Address currentAddress = lookupAddress(
+ location.getLatitude(), location.getLongitude(), true);
+ if (currentAddress == null) {
+ currentAddress = sCurrentAddress;
+ } else {
+ sCurrentAddress = currentAddress;
+ }
+ if (currentAddress != null && currentAddress.getCountryCode() != null) {
+ currentCity = checkNull(currentAddress.getLocality());
+ currentCountry = checkNull(currentAddress.getCountryCode());
+ currentAdminArea = checkNull(currentAddress.getAdminArea());
+ }
+ }
+
+ String closestCommonLocation = null;
+ String addr1Locality = checkNull(addr1.getLocality());
+ String addr2Locality = checkNull(addr2.getLocality());
+ String addr1AdminArea = checkNull(addr1.getAdminArea());
+ String addr2AdminArea = checkNull(addr2.getAdminArea());
+ String addr1CountryCode = checkNull(addr1.getCountryCode());
+ String addr2CountryCode = checkNull(addr2.getCountryCode());
+
+ if (currentCity.equals(addr1Locality) || currentCity.equals(addr2Locality)) {
+ String otherCity = currentCity;
+ if (currentCity.equals(addr1Locality)) {
+ otherCity = addr2Locality;
+ if (otherCity.length() == 0) {
+ otherCity = addr2AdminArea;
+ if (!currentCountry.equals(addr2CountryCode)) {
+ otherCity += " " + addr2CountryCode;
+ }
+ }
+ addr2Locality = addr1Locality;
+ addr2AdminArea = addr1AdminArea;
+ addr2CountryCode = addr1CountryCode;
+ } else {
+ otherCity = addr1Locality;
+ if (otherCity.length() == 0) {
+ otherCity = addr1AdminArea;
+ if (!currentCountry.equals(addr1CountryCode)) {
+ otherCity += " " + addr1CountryCode;
+ }
+ }
+ addr1Locality = addr2Locality;
+ addr1AdminArea = addr2AdminArea;
+ addr1CountryCode = addr2CountryCode;
+ }
+ closestCommonLocation = valueIfEqual(addr1.getAddressLine(0), addr2.getAddressLine(0));
+ if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
+ if (!currentCity.equals(otherCity)) {
+ closestCommonLocation += " - " + otherCity;
+ }
+ return closestCommonLocation;
+ }
+
+ // Compare thoroughfare (street address) next.
+ closestCommonLocation = valueIfEqual(addr1.getThoroughfare(), addr2.getThoroughfare());
+ if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
+ return closestCommonLocation;
+ }
+ }
+
+ // Compare the locality.
+ closestCommonLocation = valueIfEqual(addr1Locality, addr2Locality);
+ if (closestCommonLocation != null && !("".equals(closestCommonLocation))) {
+ String adminArea = addr1AdminArea;
+ String countryCode = addr1CountryCode;
+ if (adminArea != null && adminArea.length() > 0) {
+ if (!countryCode.equals(currentCountry)) {
+ closestCommonLocation += ", " + adminArea + " " + countryCode;
+ } else {
+ closestCommonLocation += ", " + adminArea;
+ }
+ }
+ return closestCommonLocation;
+ }
+
+ // If the admin area is the same as the current location, we hide it and
+ // instead show the city name.
+ if (currentAdminArea.equals(addr1AdminArea) && currentAdminArea.equals(addr2AdminArea)) {
+ if ("".equals(addr1Locality)) {
+ addr1Locality = addr2Locality;
+ }
+ if ("".equals(addr2Locality)) {
+ addr2Locality = addr1Locality;
+ }
+ if (!"".equals(addr1Locality)) {
+ if (addr1Locality.equals(addr2Locality)) {
+ closestCommonLocation = addr1Locality + ", " + currentAdminArea;
+ } else {
+ closestCommonLocation = addr1Locality + " - " + addr2Locality;
+ }
+ return closestCommonLocation;
+ }
+ }
+
+ // Just choose one of the localities if within a MAX_LOCALITY_MILE_RANGE
+ // mile radius.
+ float[] distanceFloat = new float[1];
+ Location.distanceBetween(setMinLatitude, setMinLongitude,
+ setMaxLatitude, setMaxLongitude, distanceFloat);
+ int distance = (int) GalleryUtils.toMile(distanceFloat[0]);
+ if (distance < MAX_LOCALITY_MILE_RANGE) {
+ // Try each of the points and just return the first one to have a
+ // valid address.
+ closestCommonLocation = getLocalityAdminForAddress(addr1, true);
+ if (closestCommonLocation != null) {
+ return closestCommonLocation;
+ }
+ closestCommonLocation = getLocalityAdminForAddress(addr2, true);
+ if (closestCommonLocation != null) {
+ return closestCommonLocation;
+ }
+ }
+
+ // Check the administrative area.
+ closestCommonLocation = valueIfEqual(addr1AdminArea, addr2AdminArea);
+ if (closestCommonLocation != null && !("".equals(closestCommonLocation))) {
+ String countryCode = addr1CountryCode;
+ if (!countryCode.equals(currentCountry)) {
+ if (countryCode != null && countryCode.length() > 0) {
+ closestCommonLocation += " " + countryCode;
+ }
+ }
+ return closestCommonLocation;
+ }
+
+ // Check the country codes.
+ closestCommonLocation = valueIfEqual(addr1CountryCode, addr2CountryCode);
+ if (closestCommonLocation != null && !("".equals(closestCommonLocation))) {
+ return closestCommonLocation;
+ }
+ // There is no intersection, let's choose a nicer name.
+ String addr1Country = addr1.getCountryName();
+ String addr2Country = addr2.getCountryName();
+ if (addr1Country == null)
+ addr1Country = addr1CountryCode;
+ if (addr2Country == null)
+ addr2Country = addr2CountryCode;
+ if (addr1Country == null || addr2Country == null)
+ return null;
+ if (addr1Country.length() > MAX_COUNTRY_NAME_LENGTH || addr2Country.length() > MAX_COUNTRY_NAME_LENGTH) {
+ closestCommonLocation = addr1CountryCode + " - " + addr2CountryCode;
+ } else {
+ closestCommonLocation = addr1Country + " - " + addr2Country;
+ }
+ return closestCommonLocation;
+ }
+
+ private String checkNull(String locality) {
+ if (locality == null)
+ return "";
+ if (locality.equals("null"))
+ return "";
+ return locality;
+ }
+
+ private String getLocalityAdminForAddress(final Address addr, final boolean approxLocation) {
+ if (addr == null)
+ return "";
+ String localityAdminStr = addr.getLocality();
+ if (localityAdminStr != null && !("null".equals(localityAdminStr))) {
+ if (approxLocation) {
+ // TODO: Uncomment these lines as soon as we may translations
+ // for Res.string.around.
+ // localityAdminStr =
+ // mContext.getResources().getString(Res.string.around) + " " +
+ // localityAdminStr;
+ }
+ String adminArea = addr.getAdminArea();
+ if (adminArea != null && adminArea.length() > 0) {
+ localityAdminStr += ", " + adminArea;
+ }
+ return localityAdminStr;
+ }
+ return null;
+ }
+
+ public Address lookupAddress(final double latitude, final double longitude,
+ boolean useCache) {
+ try {
+ long locationKey = (long) (((latitude + LAT_MAX) * 2 * LAT_MAX
+ + (longitude + LON_MAX)) * EARTH_RADIUS_METERS);
+ byte[] cachedLocation = null;
+ if (useCache && mGeoCache != null) {
+ cachedLocation = mGeoCache.lookup(locationKey);
+ }
+ Address address = null;
+ NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
+ if (cachedLocation == null || cachedLocation.length == 0) {
+ if (networkInfo == null || !networkInfo.isConnected()) {
+ return null;
+ }
+ List<Address> addresses = mGeocoder.getFromLocation(latitude, longitude, 1);
+ if (!addresses.isEmpty()) {
+ address = addresses.get(0);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+ Locale locale = address.getLocale();
+ writeUTF(dos, locale.getLanguage());
+ writeUTF(dos, locale.getCountry());
+ writeUTF(dos, locale.getVariant());
+
+ writeUTF(dos, address.getThoroughfare());
+ int numAddressLines = address.getMaxAddressLineIndex();
+ dos.writeInt(numAddressLines);
+ for (int i = 0; i < numAddressLines; ++i) {
+ writeUTF(dos, address.getAddressLine(i));
+ }
+ writeUTF(dos, address.getFeatureName());
+ writeUTF(dos, address.getLocality());
+ writeUTF(dos, address.getAdminArea());
+ writeUTF(dos, address.getSubAdminArea());
+
+ writeUTF(dos, address.getCountryName());
+ writeUTF(dos, address.getCountryCode());
+ writeUTF(dos, address.getPostalCode());
+ writeUTF(dos, address.getPhone());
+ writeUTF(dos, address.getUrl());
+
+ dos.flush();
+ if (mGeoCache != null) {
+ mGeoCache.insert(locationKey, bos.toByteArray());
+ }
+ dos.close();
+ }
+ } else {
+ // Parsing the address from the byte stream.
+ DataInputStream dis = new DataInputStream(
+ new ByteArrayInputStream(cachedLocation));
+ String language = readUTF(dis);
+ String country = readUTF(dis);
+ String variant = readUTF(dis);
+ Locale locale = null;
+ if (language != null) {
+ if (country == null) {
+ locale = new Locale(language);
+ } else if (variant == null) {
+ locale = new Locale(language, country);
+ } else {
+ locale = new Locale(language, country, variant);
+ }
+ }
+ if (!locale.getLanguage().equals(Locale.getDefault().getLanguage())) {
+ dis.close();
+ return lookupAddress(latitude, longitude, false);
+ }
+ address = new Address(locale);
+
+ address.setThoroughfare(readUTF(dis));
+ int numAddressLines = dis.readInt();
+ for (int i = 0; i < numAddressLines; ++i) {
+ address.setAddressLine(i, readUTF(dis));
+ }
+ address.setFeatureName(readUTF(dis));
+ address.setLocality(readUTF(dis));
+ address.setAdminArea(readUTF(dis));
+ address.setSubAdminArea(readUTF(dis));
+
+ address.setCountryName(readUTF(dis));
+ address.setCountryCode(readUTF(dis));
+ address.setPostalCode(readUTF(dis));
+ address.setPhone(readUTF(dis));
+ address.setUrl(readUTF(dis));
+ dis.close();
+ }
+ return address;
+ } catch (Exception e) {
+ // Ignore.
+ }
+ return null;
+ }
+
+ private String valueIfEqual(String a, String b) {
+ return (a != null && b != null && a.equalsIgnoreCase(b)) ? a : null;
+ }
+
+ public static final void writeUTF(DataOutputStream dos, String string) throws IOException {
+ if (string == null) {
+ dos.writeUTF("");
+ } else {
+ dos.writeUTF(string);
+ }
+ }
+
+ public static final String readUTF(DataInputStream dis) throws IOException {
+ String retVal = dis.readUTF();
+ if (retVal.length() == 0)
+ return null;
+ return retVal;
+ }
+}
diff --git a/src/com/android/gallery3d/util/ThreadPool.java b/src/com/android/gallery3d/util/ThreadPool.java
new file mode 100644
index 000000000..71bb3c5b7
--- /dev/null
+++ b/src/com/android/gallery3d/util/ThreadPool.java
@@ -0,0 +1,252 @@
+/*
+ * 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.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class ThreadPool {
+ private static final String TAG = "ThreadPool";
+ private static final int CORE_POOL_SIZE = 4;
+ private static final int MAX_POOL_SIZE = 8;
+ private static final int KEEP_ALIVE_TIME = 10; // 10 seconds
+
+ // Resource type
+ public static final int MODE_NONE = 0;
+ public static final int MODE_CPU = 1;
+ public static final int MODE_NETWORK = 2;
+
+ public static final JobContext JOB_CONTEXT_STUB = new JobContextStub();
+
+ ResourceCounter mCpuCounter = new ResourceCounter(2);
+ ResourceCounter mNetworkCounter = new ResourceCounter(2);
+
+ // A Job is like a Callable, but it has an addition JobContext parameter.
+ public interface Job<T> {
+ public T run(JobContext jc);
+ }
+
+ public interface JobContext {
+ boolean isCancelled();
+ void setCancelListener(CancelListener listener);
+ boolean setMode(int mode);
+ }
+
+ private static class JobContextStub implements JobContext {
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public void setCancelListener(CancelListener listener) {
+ }
+
+ @Override
+ public boolean setMode(int mode) {
+ return true;
+ }
+ }
+
+ public interface CancelListener {
+ public void onCancel();
+ }
+
+ private static class ResourceCounter {
+ public int value;
+ public ResourceCounter(int v) {
+ value = v;
+ }
+ }
+
+ private final Executor mExecutor;
+
+ public ThreadPool() {
+ mExecutor = new ThreadPoolExecutor(
+ CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
+ TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
+ new PriorityThreadFactory("thread-pool",
+ android.os.Process.THREAD_PRIORITY_BACKGROUND));
+ }
+
+ // Submit a job to the thread pool. The listener will be called when the
+ // job is finished (or cancelled).
+ public <T> Future<T> submit(Job<T> job, FutureListener<T> listener) {
+ Worker<T> w = new Worker<T>(job, listener);
+ mExecutor.execute(w);
+ return w;
+ }
+
+ public <T> Future<T> submit(Job<T> job) {
+ return submit(job, null);
+ }
+
+ private class Worker<T> implements Runnable, Future<T>, JobContext {
+ private static final String TAG = "Worker";
+ private Job<T> mJob;
+ private FutureListener<T> mListener;
+ private CancelListener mCancelListener;
+ private ResourceCounter mWaitOnResource;
+ private volatile boolean mIsCancelled;
+ private boolean mIsDone;
+ private T mResult;
+ private int mMode;
+
+ public Worker(Job<T> job, FutureListener<T> listener) {
+ mJob = job;
+ mListener = listener;
+ }
+
+ // This is called by a thread in the thread pool.
+ public void run() {
+ T result = null;
+
+ // A job is in CPU mode by default. setMode returns false
+ // if the job is cancelled.
+ if (setMode(MODE_CPU)) {
+ try {
+ result = mJob.run(this);
+ } catch (Throwable ex) {
+ Log.w(TAG, "Exception in running a job", ex);
+ }
+ }
+
+ synchronized(this) {
+ setMode(MODE_NONE);
+ mResult = result;
+ mIsDone = true;
+ notifyAll();
+ }
+ if (mListener != null) mListener.onFutureDone(this);
+ }
+
+ // Below are the methods for Future.
+ public synchronized void cancel() {
+ if (mIsCancelled) return;
+ mIsCancelled = true;
+ if (mWaitOnResource != null) {
+ synchronized (mWaitOnResource) {
+ mWaitOnResource.notifyAll();
+ }
+ }
+ if (mCancelListener != null) {
+ mCancelListener.onCancel();
+ }
+ }
+
+ public boolean isCancelled() {
+ return mIsCancelled;
+ }
+
+ public synchronized boolean isDone() {
+ return mIsDone;
+ }
+
+ public synchronized T get() {
+ while (!mIsDone) {
+ try {
+ wait();
+ } catch (Exception ex) {
+ Log.w(TAG, "ingore exception", ex);
+ // ignore.
+ }
+ }
+ return mResult;
+ }
+
+ public void waitDone() {
+ get();
+ }
+
+ // Below are the methods for JobContext (only called from the
+ // thread running the job)
+ public synchronized void setCancelListener(CancelListener listener) {
+ mCancelListener = listener;
+ if (mIsCancelled && mCancelListener != null) {
+ mCancelListener.onCancel();
+ }
+ }
+
+ public boolean setMode(int mode) {
+ // Release old resource
+ ResourceCounter rc = modeToCounter(mMode);
+ if (rc != null) releaseResource(rc);
+ mMode = MODE_NONE;
+
+ // Acquire new resource
+ rc = modeToCounter(mode);
+ if (rc != null) {
+ if (!acquireResource(rc)) {
+ return false;
+ }
+ mMode = mode;
+ }
+
+ return true;
+ }
+
+ private ResourceCounter modeToCounter(int mode) {
+ if (mode == MODE_CPU) {
+ return mCpuCounter;
+ } else if (mode == MODE_NETWORK) {
+ return mNetworkCounter;
+ } else {
+ return null;
+ }
+ }
+
+ private boolean acquireResource(ResourceCounter counter) {
+ while (true) {
+ synchronized (this) {
+ if (mIsCancelled) {
+ mWaitOnResource = null;
+ return false;
+ }
+ mWaitOnResource = counter;
+ }
+
+ synchronized (counter) {
+ if (counter.value > 0) {
+ counter.value--;
+ break;
+ } else {
+ try {
+ counter.wait();
+ } catch (InterruptedException ex) {
+ // ignore.
+ }
+ }
+ }
+ }
+
+ synchronized (this) {
+ mWaitOnResource = null;
+ }
+
+ return true;
+ }
+
+ private void releaseResource(ResourceCounter counter) {
+ synchronized (counter) {
+ counter.value++;
+ counter.notifyAll();
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/util/UpdateHelper.java b/src/com/android/gallery3d/util/UpdateHelper.java
new file mode 100644
index 000000000..9fdade683
--- /dev/null
+++ b/src/com/android/gallery3d/util/UpdateHelper.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.android.gallery3d.common.Utils;
+
+public class UpdateHelper {
+
+ private boolean mUpdated = false;
+
+ public int update(int original, int update) {
+ if (original != update) {
+ mUpdated = true;
+ original = update;
+ }
+ return original;
+ }
+
+ public long update(long original, long update) {
+ if (original != update) {
+ mUpdated = true;
+ original = update;
+ }
+ return original;
+ }
+
+ public double update(double original, double update) {
+ if (original != update) {
+ mUpdated = true;
+ original = update;
+ }
+ 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;
+ original = update;
+ }
+ return original;
+ }
+
+ public boolean isUpdated() {
+ return mUpdated;
+ }
+}