/* * 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; 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 MIME_TYPE_IMAGE = "image/*"; private static final String MIME_TYPE_VIDEO = "video/*"; private 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 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 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(); } // 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) { SpinnerVisibilitySetter.getInstance(activity).setSpinnerVisibility(visible); } 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 (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 void assertInMainThread() { if (Thread.currentThread() == sContext.getMainLooper().getThread()) { throw new AssertionError(); } } public static void doubleToRational(double value, long[] output) { // error is a magic number to control the tollerance of error doubleToRational(value, output, 0.00001); } private static void doubleToRational(double value, long[] output, double error) { long number = (long) value; value -= number; if (value < 0.000001 || error > 1) { output[0] = (int) (number + value + 0.5); output[1] = 1; } else { doubleToRational(1.0 / value, output, error / value); number = number * output[0] + output[1]; output[1] = output[0]; output[0] = number; } } public static boolean isPanorama(MediaItem item) { if (item == null) return false; int w = item.getWidth(); int h = item.getHeight(); return (h > 0 && w / h >= 2); } }