| /* |
| * 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 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) { |
| activity.runOnUiThread(new Runnable() { |
| public void run() { |
| activity.setProgressBarIndeterminateVisibility(visible); |
| } |
| }); |
| } |
| |
| public static int determineTypeBits(Context context, Intent intent) { |
| int typeBits = 0; |
| String type = intent.resolveType(context); |
| if (intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false)) { |
| if (MIME_TYPE_ALL.equals(type)) { |
| typeBits = DataManager.INCLUDE_LOCAL_ALL_ONLY; |
| } else if (MIME_TYPE_IMAGE.equals(type)) { |
| typeBits = DataManager.INCLUDE_LOCAL_IMAGE_ONLY; |
| } else if (MIME_TYPE_VIDEO.equals(type)) { |
| typeBits = DataManager.INCLUDE_LOCAL_VIDEO_ONLY; |
| } |
| } else { |
| if (MIME_TYPE_ALL.equals(type)) { |
| typeBits = DataManager.INCLUDE_ALL; |
| } else if (MIME_TYPE_IMAGE.equals(type)) { |
| typeBits = DataManager.INCLUDE_IMAGE; |
| } else if (MIME_TYPE_VIDEO.equals(type)) { |
| typeBits = DataManager.INCLUDE_VIDEO; |
| } |
| } |
| if (typeBits == 0) typeBits = DataManager.INCLUDE_ALL; |
| |
| return typeBits; |
| } |
| |
| public static int getSelectionModePrompt(int typeBits) { |
| if ((typeBits & DataManager.INCLUDE_VIDEO) != 0) { |
| return (typeBits & DataManager.INCLUDE_IMAGE) == 0 |
| ? R.string.select_video |
| : R.string.select_item; |
| } |
| return R.string.select_image; |
| } |
| |
| public static boolean hasSpaceForSize(long size) { |
| String state = Environment.getExternalStorageState(); |
| if (!Environment.MEDIA_MOUNTED.equals(state)) { |
| return false; |
| } |
| |
| String path = Environment.getExternalStorageDirectory().getPath(); |
| try { |
| StatFs stat = new StatFs(path); |
| return stat.getAvailableBlocks() * (long) stat.getBlockSize() > size; |
| } catch (Exception e) { |
| Log.i(TAG, "Fail to access external storage", e); |
| } |
| return false; |
| } |
| |
| public static void assertInMainThread() { |
| if (Thread.currentThread() == sContext.getMainLooper().getThread()) { |
| throw new AssertionError(); |
| } |
| } |
| } |