diff options
Diffstat (limited to 'src/com/android/gallery3d/util')
22 files changed, 2476 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/util/AccessibilityUtils.java b/src/com/android/gallery3d/util/AccessibilityUtils.java new file mode 100644 index 000000000..9df8e4ece --- /dev/null +++ b/src/com/android/gallery3d/util/AccessibilityUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +import android.content.Context; +import android.support.v4.view.accessibility.AccessibilityRecordCompat; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import com.android.gallery3d.common.ApiHelper; + +/** + * AccessibilityUtils provides functions needed in accessibility mode. All the functions + * in this class are made compatible with gingerbread and later API's +*/ +public class AccessibilityUtils { + public static void makeAnnouncement(View view, CharSequence announcement) { + if (view == null) + return; + if (ApiHelper.HAS_ANNOUNCE_FOR_ACCESSIBILITY) { + view.announceForAccessibility(announcement); + } else { + // For API 15 and earlier, we need to construct an accessibility event + Context ctx = view.getContext(); + AccessibilityManager am = (AccessibilityManager) ctx.getSystemService( + Context.ACCESSIBILITY_SERVICE); + if (!am.isEnabled()) return; + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + AccessibilityRecordCompat arc = new AccessibilityRecordCompat(event); + arc.setSource(view); + event.setClassName(view.getClass().getName()); + event.setPackageName(view.getContext().getPackageName()); + event.setEnabled(view.isEnabled()); + event.getText().add(announcement); + am.sendAccessibilityEvent(event); + } + } +}
\ No newline at end of file diff --git a/src/com/android/gallery3d/util/BucketNames.java b/src/com/android/gallery3d/util/BucketNames.java new file mode 100644 index 000000000..990dc8224 --- /dev/null +++ b/src/com/android/gallery3d/util/BucketNames.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +/** + * Bucket names for buckets that are created and used in the Gallery. + */ +public class BucketNames { + + public static final String CAMERA = "DCIM/Camera"; + public static final String IMPORTED = "Imported"; + public static final String DOWNLOAD = "download"; + public static final String EDITED_ONLINE_PHOTOS = "EditedOnlinePhotos"; + public static final String SCREENSHOTS = "Pictures/Screenshots"; +} diff --git a/src/com/android/gallery3d/util/CacheManager.java b/src/com/android/gallery3d/util/CacheManager.java new file mode 100644 index 000000000..ba466f79b --- /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 android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import com.android.gallery3d.common.BlobCache; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; + +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/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java new file mode 100644 index 000000000..9245e2c5f --- /dev/null +++ b/src/com/android/gallery3d/util/GalleryUtils.java @@ -0,0 +1,404 @@ +/* + * 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.annotation.TargetApi; +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.content.res.Resources; +import android.graphics.Color; +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 com.android.gallery3d.R; +import com.android.gallery3d.app.Gallery; +import com.android.gallery3d.app.PackagesMonitor; +import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.data.DataManager; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.ui.TiledScreenNail; +import com.android.gallery3d.util.ThreadPool.CancelListener; +import com.android.gallery3d.util.ThreadPool.JobContext; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +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 CAMERA_LAUNCHER_NAME = "com.android.camera.CameraLauncher"; + + public static final String MIME_TYPE_IMAGE = "image/*"; + public static final String MIME_TYPE_VIDEO = "video/*"; + public static final String MIME_TYPE_PANORAMA360 = "application/vnd.google.panorama360+jpg"; + public static final String MIME_TYPE_ALL = "*/*"; + + private static final String DIR_TYPE_IMAGE = "vnd.android.cursor.dir/image"; + private static final String DIR_TYPE_VIDEO = "vnd.android.cursor.dir/video"; + + 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 float sPixelDensity = -1f; + private static boolean sCameraAvailableInitialized = false; + private static boolean sCameraAvailable; + + public static void initialize(Context context) { + DisplayMetrics metrics = new DisplayMetrics(); + WindowManager wm = (WindowManager) + context.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(metrics); + sPixelDensity = metrics.density; + Resources r = context.getResources(); + TiledScreenNail.setPlaceholderColor(r.getColor( + R.color.bitmap_screennail_placeholder)); + initializeThumbnailSizes(metrics, r); + } + + private static void initializeThumbnailSizes(DisplayMetrics metrics, Resources r) { + int maxPixels = Math.max(metrics.heightPixels, metrics.widthPixels); + + // For screen-nails, we never need to completely fill the screen + MediaItem.setThumbnailSizes(maxPixels / 2, maxPixels / 5); + TiledScreenNail.setMaxSide(maxPixels / 2); + } + + public static float[] intColorToFloatARGBArray(int from) { + return new float[] { + Color.alpha(from) / 255f, + Color.red(from) / 255f, + Color.green(from) / 255f, + Color.blue(from) / 255f + }; + } + + 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() { + @Override + 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 isAnyCameraAvailable(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 isCameraAvailable(Context context) { + if (sCameraAvailableInitialized) return sCameraAvailable; + PackageManager pm = context.getPackageManager(); + ComponentName name = new ComponentName(context, CAMERA_LAUNCHER_NAME); + int state = pm.getComponentEnabledSetting(name); + sCameraAvailableInitialized = true; + sCameraAvailable = + (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + || (state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + return sCameraAvailable; + } + + public static void startCameraActivity(Context context) { + Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA) + .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + public static void startGalleryActivity(Context context) { + Intent intent = new Intent(context, Gallery.class) + .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + + public static boolean isValidLocation(double latitude, double longitude) { + // TODO: change || to && after we fix the default location issue + return (latitude != MediaItem.INVALID_LATLNG || longitude != MediaItem.INVALID_LATLNG); + } + + public static String formatLatitudeLongitude(String format, double latitude, + double longitude) { + // We need to specify the locale otherwise it may go wrong in some language + // (e.g. Locale.FRENCH) + return String.format(Locale.ENGLISH, format, latitude, longitude); + } + + 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 = formatLatitudeLongitude("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 = formatLatitudeLongitude("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(); + } + + // Return the local path that matches the given bucketId. If no match is + // found, return null + public static String searchDirForPath(File dir, int bucketId) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + String path = file.getAbsolutePath(); + if (GalleryUtils.getBucketId(path) == bucketId) { + return path; + } else { + path = searchDirForPath(file, bucketId); + if (path != null) return path; + } + } + } + } + return null; + } + + // 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; + } + + @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) + public static int determineTypeBits(Context context, Intent intent) { + int typeBits = 0; + String type = intent.resolveType(context); + + if (MIME_TYPE_ALL.equals(type)) { + typeBits = DataManager.INCLUDE_ALL; + } else if (MIME_TYPE_IMAGE.equals(type) || + DIR_TYPE_IMAGE.equals(type)) { + typeBits = DataManager.INCLUDE_IMAGE; + } else if (MIME_TYPE_VIDEO.equals(type) || + DIR_TYPE_VIDEO.equals(type)) { + typeBits = DataManager.INCLUDE_VIDEO; + } else { + typeBits = DataManager.INCLUDE_ALL; + } + + if (ApiHelper.HAS_INTENT_EXTRA_LOCAL_ONLY) { + if (intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false)) { + typeBits |= DataManager.INCLUDE_LOCAL_ONLY; + } + } + + 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 boolean isPanorama(MediaItem item) { + if (item == null) return false; + int w = item.getWidth(); + int h = item.getHeight(); + return (h > 0 && w / h >= 2); + } +} diff --git a/src/com/android/gallery3d/util/Holder.java b/src/com/android/gallery3d/util/Holder.java new file mode 100644 index 000000000..0ce914c1d --- /dev/null +++ b/src/com/android/gallery3d/util/Holder.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +public class Holder<T> { + private T mObject; + + public void set(T object) { + mObject = object; + } + + public T get() { + return mObject; + } +} diff --git a/src/com/android/gallery3d/util/IdentityCache.java b/src/com/android/gallery3d/util/IdentityCache.java new file mode 100644 index 000000000..3edc424a3 --- /dev/null +++ b/src/com/android/gallery3d/util/IdentityCache.java @@ -0,0 +1,78 @@ +/* + * 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.ArrayList; +import java.util.HashMap; +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(); + } + + // This is currently unused. + /* + public synchronized void clear() { + mWeakMap.clear(); + mQueue = new ReferenceQueue<V>(); + } + */ + + // This is for debugging only + public synchronized ArrayList<K> keys() { + Set<K> set = mWeakMap.keySet(); + ArrayList<K> result = new ArrayList<K>(set); + 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..2c4dc2c83 --- /dev/null +++ b/src/com/android/gallery3d/util/IntArray.java @@ -0,0 +1,60 @@ +/* + * 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 removeLast() { + mSize--; + return mData[mSize]; + } + + public int size() { + return mSize; + } + + // For testing only + 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/JobLimiter.java b/src/com/android/gallery3d/util/JobLimiter.java new file mode 100644 index 000000000..42b754153 --- /dev/null +++ b/src/com/android/gallery3d/util/JobLimiter.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.util.ThreadPool.Job; +import com.android.gallery3d.util.ThreadPool.JobContext; + +import java.util.LinkedList; + +// Limit the number of concurrent jobs that has been submitted into a ThreadPool +@SuppressWarnings("rawtypes") +public class JobLimiter implements FutureListener { + private static final String TAG = "JobLimiter"; + + // State Transition: + // INIT -> DONE, CANCELLED + // DONE -> CANCELLED + private static final int STATE_INIT = 0; + private static final int STATE_DONE = 1; + private static final int STATE_CANCELLED = 2; + + private final LinkedList<JobWrapper<?>> mJobs = new LinkedList<JobWrapper<?>>(); + private final ThreadPool mPool; + private int mLimit; + + private static class JobWrapper<T> implements Future<T>, Job<T> { + private int mState = STATE_INIT; + private Job<T> mJob; + private Future<T> mDelegate; + private FutureListener<T> mListener; + private T mResult; + + public JobWrapper(Job<T> job, FutureListener<T> listener) { + mJob = job; + mListener = listener; + } + + public synchronized void setFuture(Future<T> future) { + if (mState != STATE_INIT) return; + mDelegate = future; + } + + @Override + public void cancel() { + FutureListener<T> listener = null; + synchronized (this) { + if (mState != STATE_DONE) { + listener = mListener; + mJob = null; + mListener = null; + if (mDelegate != null) { + mDelegate.cancel(); + mDelegate = null; + } + } + mState = STATE_CANCELLED; + mResult = null; + notifyAll(); + } + if (listener != null) listener.onFutureDone(this); + } + + @Override + public synchronized boolean isCancelled() { + return mState == STATE_CANCELLED; + } + + @Override + public boolean isDone() { + // Both CANCELLED AND DONE is considered as done + return mState != STATE_INIT; + } + + @Override + public synchronized T get() { + while (mState == STATE_INIT) { + // handle the interrupted exception of wait() + Utils.waitWithoutInterrupt(this); + } + return mResult; + } + + @Override + public void waitDone() { + get(); + } + + @Override + public T run(JobContext jc) { + Job<T> job = null; + synchronized (this) { + if (mState == STATE_CANCELLED) return null; + job = mJob; + } + T result = null; + try { + result = job.run(jc); + } catch (Throwable t) { + Log.w(TAG, "error executing job: " + job, t); + } + FutureListener<T> listener = null; + synchronized (this) { + if (mState == STATE_CANCELLED) return null; + mState = STATE_DONE; + listener = mListener; + mListener = null; + mJob = null; + mResult = result; + notifyAll(); + } + if (listener != null) listener.onFutureDone(this); + return result; + } + } + + public JobLimiter(ThreadPool pool, int limit) { + mPool = Utils.checkNotNull(pool); + mLimit = limit; + } + + public synchronized <T> Future<T> submit(Job<T> job, FutureListener<T> listener) { + JobWrapper<T> future = new JobWrapper<T>(Utils.checkNotNull(job), listener); + mJobs.addLast(future); + submitTasksIfAllowed(); + return future; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private void submitTasksIfAllowed() { + while (mLimit > 0 && !mJobs.isEmpty()) { + JobWrapper wrapper = mJobs.removeFirst(); + if (!wrapper.isCancelled()) { + --mLimit; + wrapper.setFuture(mPool.submit(wrapper, this)); + } + } + } + + @Override + public synchronized void onFutureDone(Future future) { + ++mLimit; + submitTasksIfAllowed(); + } +} diff --git a/src/com/android/gallery3d/util/LinkedNode.java b/src/com/android/gallery3d/util/LinkedNode.java new file mode 100644 index 000000000..4cfc3cded --- /dev/null +++ b/src/com/android/gallery3d/util/LinkedNode.java @@ -0,0 +1,71 @@ +/* + * 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 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..043800561 --- /dev/null +++ b/src/com/android/gallery3d/util/MediaSetUtils.java @@ -0,0 +1,66 @@ +/* + * 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.Environment; + +import com.android.gallery3d.data.LocalAlbum; +import com.android.gallery3d.data.LocalMergeAlbum; +import com.android.gallery3d.data.MediaSet; +import com.android.gallery3d.data.Path; + +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() + "/" + + BucketNames.CAMERA); + public static final int DOWNLOAD_BUCKET_ID = GalleryUtils.getBucketId( + Environment.getExternalStorageDirectory().toString() + "/" + + BucketNames.DOWNLOAD); + public static final int EDITED_ONLINE_PHOTOS_BUCKET_ID = GalleryUtils.getBucketId( + Environment.getExternalStorageDirectory().toString() + "/" + + BucketNames.EDITED_ONLINE_PHOTOS); + public static final int IMPORTED_BUCKET_ID = GalleryUtils.getBucketId( + Environment.getExternalStorageDirectory().toString() + "/" + + BucketNames.IMPORTED); + public static final int SNAPSHOT_BUCKET_ID = GalleryUtils.getBucketId( + Environment.getExternalStorageDirectory().toString() + + "/" + BucketNames.SCREENSHOTS); + + 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> { + @Override + 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/MotionEventHelper.java b/src/com/android/gallery3d/util/MotionEventHelper.java new file mode 100644 index 000000000..715f7fa69 --- /dev/null +++ b/src/com/android/gallery3d/util/MotionEventHelper.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.gallery3d.util; + +import android.annotation.TargetApi; +import android.graphics.Matrix; +import android.util.FloatMath; +import android.view.MotionEvent; +import android.view.MotionEvent.PointerCoords; + +import com.android.gallery3d.common.ApiHelper; + +public final class MotionEventHelper { + private MotionEventHelper() {} + + public static MotionEvent transformEvent(MotionEvent e, Matrix m) { + // We try to use the new transform method if possible because it uses + // less memory. + if (ApiHelper.HAS_MOTION_EVENT_TRANSFORM) { + return transformEventNew(e, m); + } else { + return transformEventOld(e, m); + } + } + + @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) + private static MotionEvent transformEventNew(MotionEvent e, Matrix m) { + MotionEvent newEvent = MotionEvent.obtain(e); + newEvent.transform(m); + return newEvent; + } + + // This is copied from Input.cpp in the android framework. + private static MotionEvent transformEventOld(MotionEvent e, Matrix m) { + long downTime = e.getDownTime(); + long eventTime = e.getEventTime(); + int action = e.getAction(); + int pointerCount = e.getPointerCount(); + int[] pointerIds = getPointerIds(e); + PointerCoords[] pointerCoords = getPointerCoords(e); + int metaState = e.getMetaState(); + float xPrecision = e.getXPrecision(); + float yPrecision = e.getYPrecision(); + int deviceId = e.getDeviceId(); + int edgeFlags = e.getEdgeFlags(); + int source = e.getSource(); + int flags = e.getFlags(); + + // Copy the x and y coordinates into an array, map them, and copy back. + float[] xy = new float[pointerCoords.length * 2]; + for (int i = 0; i < pointerCount;i++) { + xy[2 * i] = pointerCoords[i].x; + xy[2 * i + 1] = pointerCoords[i].y; + } + m.mapPoints(xy); + for (int i = 0; i < pointerCount;i++) { + pointerCoords[i].x = xy[2 * i]; + pointerCoords[i].y = xy[2 * i + 1]; + pointerCoords[i].orientation = transformAngle( + m, pointerCoords[i].orientation); + } + + MotionEvent n = MotionEvent.obtain(downTime, eventTime, action, + pointerCount, pointerIds, pointerCoords, metaState, xPrecision, + yPrecision, deviceId, edgeFlags, source, flags); + + return n; + } + + private static int[] getPointerIds(MotionEvent e) { + int n = e.getPointerCount(); + int[] r = new int[n]; + for (int i = 0; i < n; i++) { + r[i] = e.getPointerId(i); + } + return r; + } + + private static PointerCoords[] getPointerCoords(MotionEvent e) { + int n = e.getPointerCount(); + PointerCoords[] r = new PointerCoords[n]; + for (int i = 0; i < n; i++) { + r[i] = new PointerCoords(); + e.getPointerCoords(i, r[i]); + } + return r; + } + + private static float transformAngle(Matrix m, float angleRadians) { + // Construct and transform a vector oriented at the specified clockwise + // angle from vertical. Coordinate system: down is increasing Y, right is + // increasing X. + float[] v = new float[2]; + v[0] = FloatMath.sin(angleRadians); + v[1] = -FloatMath.cos(angleRadians); + m.mapVectors(v); + + // Derive the transformed vector's clockwise angle from vertical. + float result = (float) Math.atan2(v[0], -v[1]); + if (result < -Math.PI / 2) { + result += Math.PI; + } else if (result > Math.PI / 2) { + result -= Math.PI; + } + return result; + } +} diff --git a/src/com/android/gallery3d/util/Profile.java b/src/com/android/gallery3d/util/Profile.java new file mode 100644 index 000000000..7ed72c90e --- /dev/null +++ b/src/com/android/gallery3d/util/Profile.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; + +import java.util.ArrayList; +import java.util.Random; + +// The Profile class is used to collect profiling information for a thread. It +// samples stack traces for a thread periodically. enable() and disable() is +// used to enable and disable profiling for the calling thread. The profiling +// information can then be dumped to a file using the dumpToFile() method. +// +// The disableAll() method can be used to disable profiling for all threads and +// can be called in onPause() to ensure all profiling is disabled when an +// activity is paused. +public class Profile { + @SuppressWarnings("unused") + private static final String TAG = "Profile"; + private static final int NS_PER_MS = 1000000; + + // This is a watchdog entry for one thread. + // For every cycleTime period, we dump the stack of the thread. + private static class WatchEntry { + Thread thread; + + // Both are in milliseconds + int cycleTime; + int wakeTime; + + boolean isHolding; + ArrayList<String[]> holdingStacks = new ArrayList<String[]>(); + } + + // This is a watchdog thread which dumps stacks of other threads periodically. + private static Watchdog sWatchdog = new Watchdog(); + + private static class Watchdog { + private ArrayList<WatchEntry> mList = new ArrayList<WatchEntry>(); + private HandlerThread mHandlerThread; + private Handler mHandler; + private Runnable mProcessRunnable = new Runnable() { + @Override + public void run() { + synchronized (Watchdog.this) { + processList(); + } + } + }; + private Random mRandom = new Random(); + private ProfileData mProfileData = new ProfileData(); + + public Watchdog() { + mHandlerThread = new HandlerThread("Watchdog Handler", + Process.THREAD_PRIORITY_FOREGROUND); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + } + + public synchronized void addWatchEntry(Thread thread, int cycleTime) { + WatchEntry e = new WatchEntry(); + e.thread = thread; + e.cycleTime = cycleTime; + int firstDelay = 1 + mRandom.nextInt(cycleTime); + e.wakeTime = (int) (System.nanoTime() / NS_PER_MS) + firstDelay; + mList.add(e); + processList(); + } + + public synchronized void removeWatchEntry(Thread thread) { + for (int i = 0; i < mList.size(); i++) { + if (mList.get(i).thread == thread) { + mList.remove(i); + break; + } + } + processList(); + } + + public synchronized void removeAllWatchEntries() { + mList.clear(); + processList(); + } + + private void processList() { + mHandler.removeCallbacks(mProcessRunnable); + if (mList.size() == 0) return; + + int currentTime = (int) (System.nanoTime() / NS_PER_MS); + int nextWakeTime = 0; + + for (WatchEntry entry : mList) { + if (currentTime > entry.wakeTime) { + entry.wakeTime += entry.cycleTime; + Thread thread = entry.thread; + sampleStack(entry); + } + + if (entry.wakeTime > nextWakeTime) { + nextWakeTime = entry.wakeTime; + } + } + + long delay = nextWakeTime - currentTime; + mHandler.postDelayed(mProcessRunnable, delay); + } + + private void sampleStack(WatchEntry entry) { + Thread thread = entry.thread; + StackTraceElement[] stack = thread.getStackTrace(); + String[] lines = new String[stack.length]; + for (int i = 0; i < stack.length; i++) { + lines[i] = stack[i].toString(); + } + if (entry.isHolding) { + entry.holdingStacks.add(lines); + } else { + mProfileData.addSample(lines); + } + } + + private WatchEntry findEntry(Thread thread) { + for (int i = 0; i < mList.size(); i++) { + WatchEntry entry = mList.get(i); + if (entry.thread == thread) return entry; + } + return null; + } + + public synchronized void dumpToFile(String filename) { + mProfileData.dumpToFile(filename); + } + + public synchronized void reset() { + mProfileData.reset(); + } + + public synchronized void hold(Thread t) { + WatchEntry entry = findEntry(t); + + // This can happen if the profiling is disabled (probably from + // another thread). Same check is applied in commit() and drop() + // below. + if (entry == null) return; + + entry.isHolding = true; + } + + public synchronized void commit(Thread t) { + WatchEntry entry = findEntry(t); + if (entry == null) return; + ArrayList<String[]> stacks = entry.holdingStacks; + for (int i = 0; i < stacks.size(); i++) { + mProfileData.addSample(stacks.get(i)); + } + entry.isHolding = false; + entry.holdingStacks.clear(); + } + + public synchronized void drop(Thread t) { + WatchEntry entry = findEntry(t); + if (entry == null) return; + entry.isHolding = false; + entry.holdingStacks.clear(); + } + } + + // Enable profiling for the calling thread. Periodically (every cycleTimeInMs + // milliseconds) sample the stack trace of the calling thread. + public static void enable(int cycleTimeInMs) { + Thread t = Thread.currentThread(); + sWatchdog.addWatchEntry(t, cycleTimeInMs); + } + + // Disable profiling for the calling thread. + public static void disable() { + sWatchdog.removeWatchEntry(Thread.currentThread()); + } + + // Disable profiling for all threads. + public static void disableAll() { + sWatchdog.removeAllWatchEntries(); + } + + // Dump the profiling data to a file. + public static void dumpToFile(String filename) { + sWatchdog.dumpToFile(filename); + } + + // Reset the collected profiling data. + public static void reset() { + sWatchdog.reset(); + } + + // Hold the future samples coming from current thread until commit() or + // drop() is called, and those samples are recorded or ignored as a result. + // This must called after enable() to be effective. + public static void hold() { + sWatchdog.hold(Thread.currentThread()); + } + + public static void commit() { + sWatchdog.commit(Thread.currentThread()); + } + + public static void drop() { + sWatchdog.drop(Thread.currentThread()); + } +} diff --git a/src/com/android/gallery3d/util/ProfileData.java b/src/com/android/gallery3d/util/ProfileData.java new file mode 100644 index 000000000..a1bb8e1e4 --- /dev/null +++ b/src/com/android/gallery3d/util/ProfileData.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +import android.util.Log; + +import com.android.gallery3d.common.Utils; + +import java.io.DataOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map.Entry; + +// ProfileData keeps profiling samples in a tree structure. +// The addSample() method adds a sample. The dumpToFile() method saves the data +// to a file. The reset() method clears all samples. +public class ProfileData { + @SuppressWarnings("unused") + private static final String TAG = "ProfileData"; + + private static class Node { + public int id; // this is the name of this node, mapped from mNameToId + public Node parent; + public int sampleCount; + public ArrayList<Node> children; + public Node(Node parent, int id) { + this.parent = parent; + this.id = id; + } + } + + private Node mRoot; + private int mNextId; + private HashMap<String, Integer> mNameToId; + private DataOutputStream mOut; + private byte mScratch[] = new byte[4]; // scratch space for writeInt() + + public ProfileData() { + mRoot = new Node(null, -1); // The id of the root node is unused. + mNameToId = new HashMap<String, Integer>(); + } + + public void reset() { + mRoot = new Node(null, -1); + mNameToId.clear(); + mNextId = 0; + } + + private int nameToId(String name) { + Integer id = mNameToId.get(name); + if (id == null) { + id = ++mNextId; // The tool doesn't want id=0, so we start from 1. + mNameToId.put(name, id); + } + return id; + } + + public void addSample(String[] stack) { + int[] ids = new int[stack.length]; + for (int i = 0; i < stack.length; i++) { + ids[i] = nameToId(stack[i]); + } + + Node node = mRoot; + for (int i = stack.length - 1; i >= 0; i--) { + if (node.children == null) { + node.children = new ArrayList<Node>(); + } + + int id = ids[i]; + ArrayList<Node> children = node.children; + int j; + for (j = 0; j < children.size(); j++) { + if (children.get(j).id == id) break; + } + if (j == children.size()) { + children.add(new Node(node, id)); + } + + node = children.get(j); + } + + node.sampleCount++; + } + + public void dumpToFile(String filename) { + try { + mOut = new DataOutputStream(new FileOutputStream(filename)); + // Start record + writeInt(0); + writeInt(3); + writeInt(1); + writeInt(20000); // Sampling period: 20ms + writeInt(0); + + // Samples + writeAllStacks(mRoot, 0); + + // End record + writeInt(0); + writeInt(1); + writeInt(0); + writeAllSymbols(); + } catch (IOException ex) { + Log.w("Failed to dump to file", ex); + } finally { + Utils.closeSilently(mOut); + } + } + + // Writes out one stack, consisting of N+2 words: + // first word: sample count + // second word: depth of the stack (N) + // N words: each word is the id of one address in the stack + private void writeOneStack(Node node, int depth) throws IOException { + writeInt(node.sampleCount); + writeInt(depth); + while (depth-- > 0) { + writeInt(node.id); + node = node.parent; + } + } + + private void writeAllStacks(Node node, int depth) throws IOException { + if (node.sampleCount > 0) { + writeOneStack(node, depth); + } + + ArrayList<Node> children = node.children; + if (children != null) { + for (int i = 0; i < children.size(); i++) { + writeAllStacks(children.get(i), depth + 1); + } + } + } + + // Writes out the symbol table. Each line is like: + // 0x17e java.util.ArrayList.isEmpty(ArrayList.java:319) + private void writeAllSymbols() throws IOException { + for (Entry<String, Integer> entry : mNameToId.entrySet()) { + mOut.writeBytes(String.format("0x%x %s\n", entry.getValue(), entry.getKey())); + } + } + + private void writeInt(int v) throws IOException { + mScratch[0] = (byte) v; + mScratch[1] = (byte) (v >> 8); + mScratch[2] = (byte) (v >> 16); + mScratch[3] = (byte) (v >> 24); + mOut.write(mScratch); + } +} diff --git a/src/com/android/gallery3d/util/RangeArray.java b/src/com/android/gallery3d/util/RangeArray.java new file mode 100644 index 000000000..8e61348a3 --- /dev/null +++ b/src/com/android/gallery3d/util/RangeArray.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +// This is an array whose index ranges from min to max (inclusive). +public class RangeArray<T> { + private T[] mData; + private int mOffset; + + public RangeArray(int min, int max) { + mData = (T[]) new Object[max - min + 1]; + mOffset = min; + } + + // Wraps around an existing array + public RangeArray(T[] src, int min, int max) { + if (max - min + 1 != src.length) { + throw new AssertionError(); + } + mData = src; + mOffset = min; + } + + public void put(int i, T object) { + mData[i - mOffset] = object; + } + + public T get(int i) { + return mData[i - mOffset]; + } + + public int indexOf(T object) { + for (int i = 0; i < mData.length; i++) { + if (mData[i] == object) return i + mOffset; + } + return Integer.MAX_VALUE; + } +} diff --git a/src/com/android/gallery3d/util/RangeBoolArray.java b/src/com/android/gallery3d/util/RangeBoolArray.java new file mode 100644 index 000000000..035fc40a4 --- /dev/null +++ b/src/com/android/gallery3d/util/RangeBoolArray.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +// This is an array whose index ranges from min to max (inclusive). +public class RangeBoolArray { + private boolean[] mData; + private int mOffset; + + public RangeBoolArray(int min, int max) { + mData = new boolean[max - min + 1]; + mOffset = min; + } + + // Wraps around an existing array + public RangeBoolArray(boolean[] src, int min, int max) { + mData = src; + mOffset = min; + } + + public void put(int i, boolean object) { + mData[i - mOffset] = object; + } + + public boolean get(int i) { + return mData[i - mOffset]; + } + + public int indexOf(boolean object) { + for (int i = 0; i < mData.length; i++) { + if (mData[i] == object) return i + mOffset; + } + return Integer.MAX_VALUE; + } +} diff --git a/src/com/android/gallery3d/util/RangeIntArray.java b/src/com/android/gallery3d/util/RangeIntArray.java new file mode 100644 index 000000000..9dbb99fac --- /dev/null +++ b/src/com/android/gallery3d/util/RangeIntArray.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +// This is an array whose index ranges from min to max (inclusive). +public class RangeIntArray { + private int[] mData; + private int mOffset; + + public RangeIntArray(int min, int max) { + mData = new int[max - min + 1]; + mOffset = min; + } + + // Wraps around an existing array + public RangeIntArray(int[] src, int min, int max) { + mData = src; + mOffset = min; + } + + public void put(int i, int object) { + mData[i - mOffset] = object; + } + + public int get(int i) { + return mData[i - mOffset]; + } + + public int indexOf(int object) { + for (int i = 0; i < mData.length; i++) { + if (mData[i] == object) return i + mOffset; + } + return Integer.MAX_VALUE; + } +} diff --git a/src/com/android/gallery3d/util/ReverseGeocoder.java b/src/com/android/gallery3d/util/ReverseGeocoder.java new file mode 100644 index 000000000..a8b26d9b5 --- /dev/null +++ b/src/com/android/gallery3d/util/ReverseGeocoder.java @@ -0,0 +1,418 @@ +/* + * 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.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 com.android.gallery3d.common.BlobCache; + +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 { + @SuppressWarnings("unused") + 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/SaveVideoFileInfo.java b/src/com/android/gallery3d/util/SaveVideoFileInfo.java new file mode 100644 index 000000000..c7e5e8568 --- /dev/null +++ b/src/com/android/gallery3d/util/SaveVideoFileInfo.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +import java.io.File; + +public class SaveVideoFileInfo { + public File mFile = null; + public String mFileName = null; + // This the full directory path. + public File mDirectory = null; + // This is just the folder's name. + public String mFolderName = null; + +} diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java new file mode 100644 index 000000000..10c41de90 --- /dev/null +++ b/src/com/android/gallery3d/util/SaveVideoFileUtils.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore.Video; +import android.provider.MediaStore.Video.VideoColumns; + +import com.android.gallery3d.filtershow.tools.SaveImage.ContentResolverQueryCallback; + +import java.io.File; +import java.sql.Date; +import java.text.SimpleDateFormat; + +public class SaveVideoFileUtils { + // This function can decide which folder to save the video file, and generate + // the needed information for the video file including filename. + public static SaveVideoFileInfo getDstMp4FileInfo(String fileNameFormat, + ContentResolver contentResolver, Uri uri, String defaultFolderName) { + SaveVideoFileInfo dstFileInfo = new SaveVideoFileInfo(); + // Use the default save directory if the source directory cannot be + // saved. + dstFileInfo.mDirectory = getSaveDirectory(contentResolver, uri); + if ((dstFileInfo.mDirectory == null) || !dstFileInfo.mDirectory.canWrite()) { + dstFileInfo.mDirectory = new File(Environment.getExternalStorageDirectory(), + BucketNames.DOWNLOAD); + dstFileInfo.mFolderName = defaultFolderName; + } else { + dstFileInfo.mFolderName = dstFileInfo.mDirectory.getName(); + } + dstFileInfo.mFileName = new SimpleDateFormat(fileNameFormat).format( + new Date(System.currentTimeMillis())); + + dstFileInfo.mFile = new File(dstFileInfo.mDirectory, dstFileInfo.mFileName + ".mp4"); + return dstFileInfo; + } + + private static void querySource(ContentResolver contentResolver, Uri uri, + String[] projection, ContentResolverQueryCallback callback) { + Cursor cursor = null; + try { + cursor = contentResolver.query(uri, projection, null, null, null); + if ((cursor != null) && cursor.moveToNext()) { + callback.onCursorResult(cursor); + } + } catch (Exception e) { + // Ignore error for lacking the data column from the source. + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + private static File getSaveDirectory(ContentResolver contentResolver, Uri uri) { + final File[] dir = new File[1]; + querySource(contentResolver, uri, + new String[] { VideoColumns.DATA }, + new ContentResolverQueryCallback() { + @Override + public void onCursorResult(Cursor cursor) { + dir[0] = new File(cursor.getString(0)).getParentFile(); + } + }); + return dir[0]; + } + + + /** + * Insert the content (saved file) with proper video properties. + */ + public static Uri insertContent(SaveVideoFileInfo mDstFileInfo, + ContentResolver contentResolver, Uri uri ) { + long nowInMs = System.currentTimeMillis(); + long nowInSec = nowInMs / 1000; + final ContentValues values = new ContentValues(13); + values.put(Video.Media.TITLE, mDstFileInfo.mFileName); + values.put(Video.Media.DISPLAY_NAME, mDstFileInfo.mFile.getName()); + values.put(Video.Media.MIME_TYPE, "video/mp4"); + values.put(Video.Media.DATE_TAKEN, nowInMs); + values.put(Video.Media.DATE_MODIFIED, nowInSec); + values.put(Video.Media.DATE_ADDED, nowInSec); + values.put(Video.Media.DATA, mDstFileInfo.mFile.getAbsolutePath()); + values.put(Video.Media.SIZE, mDstFileInfo.mFile.length()); + int durationMs = retriveVideoDurationMs(mDstFileInfo.mFile.getPath()); + values.put(Video.Media.DURATION, durationMs); + // Copy the data taken and location info from src. + String[] projection = new String[] { + VideoColumns.DATE_TAKEN, + VideoColumns.LATITUDE, + VideoColumns.LONGITUDE, + VideoColumns.RESOLUTION, + }; + + // Copy some info from the source file. + querySource(contentResolver, uri, projection, + new ContentResolverQueryCallback() { + @Override + public void onCursorResult(Cursor cursor) { + long timeTaken = cursor.getLong(0); + if (timeTaken > 0) { + values.put(Video.Media.DATE_TAKEN, timeTaken); + } + double latitude = cursor.getDouble(1); + double longitude = cursor.getDouble(2); + // TODO: Change || to && after the default location + // issue is + // fixed. + if ((latitude != 0f) || (longitude != 0f)) { + values.put(Video.Media.LATITUDE, latitude); + values.put(Video.Media.LONGITUDE, longitude); + } + values.put(Video.Media.RESOLUTION, cursor.getString(3)); + + } + }); + + return contentResolver.insert(Video.Media.EXTERNAL_CONTENT_URI, values); + } + + public static int retriveVideoDurationMs(String path) { + int durationMs = 0; + // Calculate the duration of the destination file. + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(path); + String duration = retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_DURATION); + if (duration != null) { + durationMs = Integer.parseInt(duration); + } + retriever.release(); + return durationMs; + } + +} diff --git a/src/com/android/gallery3d/util/UpdateHelper.java b/src/com/android/gallery3d/util/UpdateHelper.java new file mode 100644 index 000000000..f76705d06 --- /dev/null +++ b/src/com/android/gallery3d/util/UpdateHelper.java @@ -0,0 +1,59 @@ +/* + * 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 <T> T update(T original, T update) { + if (!Utils.equals(original, update)) { + mUpdated = true; + original = update; + } + return original; + } + + public boolean isUpdated() { + return mUpdated; + } +} |