diff options
Diffstat (limited to 'src/com/android/gallery3d/util')
-rw-r--r-- | src/com/android/gallery3d/util/CacheManager.java | 82 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/Future.java | 35 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/FutureListener.java | 21 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/FutureTask.java | 86 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/GalleryUtils.java | 327 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/IdentityCache.java | 74 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/IntArray.java | 54 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/InterruptableOutputStream.java | 67 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/LinkedNode.java | 75 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/Log.java | 53 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/MediaSetUtils.java | 56 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/PriorityThreadFactory.java | 48 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/ReverseGeocoder.java | 417 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/ThreadPool.java | 252 | ||||
-rw-r--r-- | src/com/android/gallery3d/util/UpdateHelper.java | 67 |
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; + } +} |