Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2010 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.gallery3d.util; |
| 18 | |
| 19 | import com.android.gallery3d.R; |
| 20 | import com.android.gallery3d.app.PackagesMonitor; |
| 21 | import com.android.gallery3d.data.DataManager; |
| 22 | import com.android.gallery3d.data.MediaItem; |
| 23 | import com.android.gallery3d.util.ThreadPool.CancelListener; |
| 24 | import com.android.gallery3d.util.ThreadPool.JobContext; |
| 25 | |
| 26 | import android.app.Activity; |
| 27 | import android.content.ActivityNotFoundException; |
| 28 | import android.content.ComponentName; |
| 29 | import android.content.Context; |
| 30 | import android.content.Intent; |
| 31 | import android.content.SharedPreferences; |
| 32 | import android.content.pm.PackageManager; |
| 33 | import android.content.pm.ResolveInfo; |
| 34 | import android.net.Uri; |
| 35 | import android.os.ConditionVariable; |
| 36 | import android.os.Environment; |
| 37 | import android.os.StatFs; |
| 38 | import android.preference.PreferenceManager; |
| 39 | import android.provider.MediaStore; |
| 40 | import android.util.DisplayMetrics; |
| 41 | import android.util.Log; |
| 42 | import android.view.WindowManager; |
| 43 | |
| 44 | import java.util.Arrays; |
| 45 | import java.util.List; |
Ray Chen | 376be10 | 2011-08-19 10:45:53 +0800 | [diff] [blame] | 46 | import java.util.Locale; |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 47 | |
| 48 | public class GalleryUtils { |
| 49 | private static final String TAG = "GalleryUtils"; |
| 50 | private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps"; |
| 51 | private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity"; |
| 52 | |
| 53 | private static final String MIME_TYPE_IMAGE = "image/*"; |
| 54 | private static final String MIME_TYPE_VIDEO = "video/*"; |
| 55 | private static final String MIME_TYPE_ALL = "*/*"; |
| 56 | |
| 57 | private static final String PREFIX_PHOTO_EDITOR_UPDATE = "editor-update-"; |
| 58 | private static final String PREFIX_HAS_PHOTO_EDITOR = "has-editor-"; |
| 59 | |
| 60 | private static final String KEY_CAMERA_UPDATE = "camera-update"; |
| 61 | private static final String KEY_HAS_CAMERA = "has-camera"; |
| 62 | |
| 63 | private static Context sContext; |
| 64 | |
| 65 | |
| 66 | static float sPixelDensity = -1f; |
| 67 | |
| 68 | public static void initialize(Context context) { |
| 69 | sContext = context; |
| 70 | if (sPixelDensity < 0) { |
| 71 | DisplayMetrics metrics = new DisplayMetrics(); |
| 72 | WindowManager wm = (WindowManager) |
| 73 | context.getSystemService(Context.WINDOW_SERVICE); |
| 74 | wm.getDefaultDisplay().getMetrics(metrics); |
| 75 | sPixelDensity = metrics.density; |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | public static float dpToPixel(float dp) { |
| 80 | return sPixelDensity * dp; |
| 81 | } |
| 82 | |
| 83 | public static int dpToPixel(int dp) { |
| 84 | return Math.round(dpToPixel((float) dp)); |
| 85 | } |
| 86 | |
| 87 | public static int meterToPixel(float meter) { |
| 88 | // 1 meter = 39.37 inches, 1 inch = 160 dp. |
| 89 | return Math.round(dpToPixel(meter * 39.37f * 160)); |
| 90 | } |
| 91 | |
| 92 | public static byte[] getBytes(String in) { |
| 93 | byte[] result = new byte[in.length() * 2]; |
| 94 | int output = 0; |
| 95 | for (char ch : in.toCharArray()) { |
| 96 | result[output++] = (byte) (ch & 0xFF); |
| 97 | result[output++] = (byte) (ch >> 8); |
| 98 | } |
| 99 | return result; |
| 100 | } |
| 101 | |
| 102 | // Below are used the detect using database in the render thread. It only |
| 103 | // works most of the time, but that's ok because it's for debugging only. |
| 104 | |
| 105 | private static volatile Thread sCurrentThread; |
| 106 | private static volatile boolean sWarned; |
| 107 | |
| 108 | public static void setRenderThread() { |
| 109 | sCurrentThread = Thread.currentThread(); |
| 110 | } |
| 111 | |
| 112 | public static void assertNotInRenderThread() { |
| 113 | if (!sWarned) { |
| 114 | if (Thread.currentThread() == sCurrentThread) { |
| 115 | sWarned = true; |
| 116 | Log.w(TAG, new Throwable("Should not do this in render thread")); |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | private static final double RAD_PER_DEG = Math.PI / 180.0; |
| 122 | private static final double EARTH_RADIUS_METERS = 6367000.0; |
| 123 | |
| 124 | public static double fastDistanceMeters(double latRad1, double lngRad1, |
| 125 | double latRad2, double lngRad2) { |
| 126 | if ((Math.abs(latRad1 - latRad2) > RAD_PER_DEG) |
| 127 | || (Math.abs(lngRad1 - lngRad2) > RAD_PER_DEG)) { |
| 128 | return accurateDistanceMeters(latRad1, lngRad1, latRad2, lngRad2); |
| 129 | } |
| 130 | // Approximate sin(x) = x. |
| 131 | double sineLat = (latRad1 - latRad2); |
| 132 | |
| 133 | // Approximate sin(x) = x. |
| 134 | double sineLng = (lngRad1 - lngRad2); |
| 135 | |
| 136 | // Approximate cos(lat1) * cos(lat2) using |
| 137 | // cos((lat1 + lat2)/2) ^ 2 |
| 138 | double cosTerms = Math.cos((latRad1 + latRad2) / 2.0); |
| 139 | cosTerms = cosTerms * cosTerms; |
| 140 | double trigTerm = sineLat * sineLat + cosTerms * sineLng * sineLng; |
| 141 | trigTerm = Math.sqrt(trigTerm); |
| 142 | |
| 143 | // Approximate arcsin(x) = x |
| 144 | return EARTH_RADIUS_METERS * trigTerm; |
| 145 | } |
| 146 | |
| 147 | public static double accurateDistanceMeters(double lat1, double lng1, |
| 148 | double lat2, double lng2) { |
| 149 | double dlat = Math.sin(0.5 * (lat2 - lat1)); |
| 150 | double dlng = Math.sin(0.5 * (lng2 - lng1)); |
| 151 | double x = dlat * dlat + dlng * dlng * Math.cos(lat1) * Math.cos(lat2); |
| 152 | return (2 * Math.atan2(Math.sqrt(x), Math.sqrt(Math.max(0.0, |
| 153 | 1.0 - x)))) * EARTH_RADIUS_METERS; |
| 154 | } |
| 155 | |
| 156 | |
| 157 | public static final double toMile(double meter) { |
| 158 | return meter / 1609; |
| 159 | } |
| 160 | |
| 161 | // For debugging, it will block the caller for timeout millis. |
| 162 | public static void fakeBusy(JobContext jc, int timeout) { |
| 163 | final ConditionVariable cv = new ConditionVariable(); |
| 164 | jc.setCancelListener(new CancelListener() { |
| 165 | public void onCancel() { |
| 166 | cv.open(); |
| 167 | } |
| 168 | }); |
| 169 | cv.block(timeout); |
| 170 | jc.setCancelListener(null); |
| 171 | } |
| 172 | |
| 173 | public static boolean isEditorAvailable(Context context, String mimeType) { |
| 174 | int version = PackagesMonitor.getPackagesVersion(context); |
| 175 | |
| 176 | String updateKey = PREFIX_PHOTO_EDITOR_UPDATE + mimeType; |
| 177 | String hasKey = PREFIX_HAS_PHOTO_EDITOR + mimeType; |
| 178 | |
| 179 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); |
| 180 | if (prefs.getInt(updateKey, 0) != version) { |
| 181 | PackageManager packageManager = context.getPackageManager(); |
| 182 | List<ResolveInfo> infos = packageManager.queryIntentActivities( |
| 183 | new Intent(Intent.ACTION_EDIT).setType(mimeType), 0); |
| 184 | prefs.edit().putInt(updateKey, version) |
| 185 | .putBoolean(hasKey, !infos.isEmpty()) |
| 186 | .commit(); |
| 187 | } |
| 188 | |
| 189 | return prefs.getBoolean(hasKey, true); |
| 190 | } |
| 191 | |
| 192 | public static boolean isCameraAvailable(Context context) { |
| 193 | int version = PackagesMonitor.getPackagesVersion(context); |
| 194 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); |
| 195 | if (prefs.getInt(KEY_CAMERA_UPDATE, 0) != version) { |
| 196 | PackageManager packageManager = context.getPackageManager(); |
| 197 | List<ResolveInfo> infos = packageManager.queryIntentActivities( |
| 198 | new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA), 0); |
| 199 | prefs.edit().putInt(KEY_CAMERA_UPDATE, version) |
| 200 | .putBoolean(KEY_HAS_CAMERA, !infos.isEmpty()) |
| 201 | .commit(); |
| 202 | } |
| 203 | return prefs.getBoolean(KEY_HAS_CAMERA, true); |
| 204 | } |
| 205 | |
| 206 | public static boolean isValidLocation(double latitude, double longitude) { |
| 207 | // TODO: change || to && after we fix the default location issue |
| 208 | return (latitude != MediaItem.INVALID_LATLNG || longitude != MediaItem.INVALID_LATLNG); |
| 209 | } |
Ray Chen | 376be10 | 2011-08-19 10:45:53 +0800 | [diff] [blame] | 210 | |
| 211 | public static String formatLatitudeLongitude(String format, double latitude, |
| 212 | double longitude) { |
| 213 | // We need to specify the locale otherwise it may go wrong in some language |
| 214 | // (e.g. Locale.FRENCH) |
| 215 | return String.format(Locale.ENGLISH, format, latitude, longitude); |
| 216 | } |
| 217 | |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 218 | public static void showOnMap(Context context, double latitude, double longitude) { |
| 219 | try { |
| 220 | // We don't use "geo:latitude,longitude" because it only centers |
| 221 | // the MapView to the specified location, but we need a marker |
| 222 | // for further operations (routing to/from). |
| 223 | // The q=(lat, lng) syntax is suggested by geo-team. |
Ray Chen | 376be10 | 2011-08-19 10:45:53 +0800 | [diff] [blame] | 224 | String uri = formatLatitudeLongitude("http://maps.google.com/maps?f=q&q=(%f,%f)", |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 225 | latitude, longitude); |
| 226 | ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME, |
| 227 | MAPS_CLASS_NAME); |
| 228 | Intent mapsIntent = new Intent(Intent.ACTION_VIEW, |
| 229 | Uri.parse(uri)).setComponent(compName); |
| 230 | context.startActivity(mapsIntent); |
| 231 | } catch (ActivityNotFoundException e) { |
| 232 | // Use the "geo intent" if no GMM is installed |
| 233 | Log.e(TAG, "GMM activity not found!", e); |
Ray Chen | 376be10 | 2011-08-19 10:45:53 +0800 | [diff] [blame] | 234 | String url = formatLatitudeLongitude("geo:%f,%f", latitude, longitude); |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 235 | Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); |
| 236 | context.startActivity(mapsIntent); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | public static void setViewPointMatrix( |
| 241 | float matrix[], float x, float y, float z) { |
| 242 | // The matrix is |
| 243 | // -z, 0, x, 0 |
| 244 | // 0, -z, y, 0 |
| 245 | // 0, 0, 1, 0 |
| 246 | // 0, 0, 1, -z |
| 247 | Arrays.fill(matrix, 0, 16, 0); |
| 248 | matrix[0] = matrix[5] = matrix[15] = -z; |
| 249 | matrix[8] = x; |
| 250 | matrix[9] = y; |
| 251 | matrix[10] = matrix[11] = 1; |
| 252 | } |
| 253 | |
| 254 | public static int getBucketId(String path) { |
| 255 | return path.toLowerCase().hashCode(); |
| 256 | } |
| 257 | |
| 258 | // Returns a (localized) string for the given duration (in seconds). |
| 259 | public static String formatDuration(final Context context, int duration) { |
| 260 | int h = duration / 3600; |
| 261 | int m = (duration - h * 3600) / 60; |
| 262 | int s = duration - (h * 3600 + m * 60); |
| 263 | String durationValue; |
| 264 | if (h == 0) { |
| 265 | durationValue = String.format(context.getString(R.string.details_ms), m, s); |
| 266 | } else { |
| 267 | durationValue = String.format(context.getString(R.string.details_hms), h, m, s); |
| 268 | } |
| 269 | return durationValue; |
| 270 | } |
| 271 | |
| 272 | public static void setSpinnerVisibility(final Activity activity, |
| 273 | final boolean visible) { |
| 274 | activity.runOnUiThread(new Runnable() { |
| 275 | public void run() { |
| 276 | activity.setProgressBarIndeterminateVisibility(visible); |
| 277 | } |
| 278 | }); |
| 279 | } |
| 280 | |
| 281 | public static int determineTypeBits(Context context, Intent intent) { |
| 282 | int typeBits = 0; |
| 283 | String type = intent.resolveType(context); |
| 284 | if (intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false)) { |
| 285 | if (MIME_TYPE_ALL.equals(type)) { |
| 286 | typeBits = DataManager.INCLUDE_LOCAL_ALL_ONLY; |
| 287 | } else if (MIME_TYPE_IMAGE.equals(type)) { |
| 288 | typeBits = DataManager.INCLUDE_LOCAL_IMAGE_ONLY; |
| 289 | } else if (MIME_TYPE_VIDEO.equals(type)) { |
| 290 | typeBits = DataManager.INCLUDE_LOCAL_VIDEO_ONLY; |
| 291 | } |
| 292 | } else { |
| 293 | if (MIME_TYPE_ALL.equals(type)) { |
| 294 | typeBits = DataManager.INCLUDE_ALL; |
| 295 | } else if (MIME_TYPE_IMAGE.equals(type)) { |
| 296 | typeBits = DataManager.INCLUDE_IMAGE; |
| 297 | } else if (MIME_TYPE_VIDEO.equals(type)) { |
| 298 | typeBits = DataManager.INCLUDE_VIDEO; |
| 299 | } |
| 300 | } |
| 301 | if (typeBits == 0) typeBits = DataManager.INCLUDE_ALL; |
| 302 | |
| 303 | return typeBits; |
| 304 | } |
| 305 | |
| 306 | public static int getSelectionModePrompt(int typeBits) { |
| 307 | if ((typeBits & DataManager.INCLUDE_VIDEO) != 0) { |
| 308 | return (typeBits & DataManager.INCLUDE_IMAGE) == 0 |
| 309 | ? R.string.select_video |
| 310 | : R.string.select_item; |
| 311 | } |
| 312 | return R.string.select_image; |
| 313 | } |
| 314 | |
| 315 | public static boolean hasSpaceForSize(long size) { |
| 316 | String state = Environment.getExternalStorageState(); |
| 317 | if (!Environment.MEDIA_MOUNTED.equals(state)) { |
| 318 | return false; |
| 319 | } |
| 320 | |
| 321 | String path = Environment.getExternalStorageDirectory().getPath(); |
| 322 | try { |
| 323 | StatFs stat = new StatFs(path); |
| 324 | return stat.getAvailableBlocks() * (long) stat.getBlockSize() > size; |
| 325 | } catch (Exception e) { |
| 326 | Log.i(TAG, "Fail to access external storage", e); |
| 327 | } |
| 328 | return false; |
| 329 | } |
| 330 | |
| 331 | public static void assertInMainThread() { |
| 332 | if (Thread.currentThread() == sContext.getMainLooper().getThread()) { |
| 333 | throw new AssertionError(); |
| 334 | } |
| 335 | } |
Owen Lin | abf2790 | 2011-09-02 21:15:42 +0800 | [diff] [blame] | 336 | |
| 337 | public static void doubleToRational(double value, long[] output) { |
| 338 | // error is a magic number to control the tollerance of error |
| 339 | doubleToRational(value, output, 0.00001); |
| 340 | } |
| 341 | |
| 342 | private static void doubleToRational(double value, long[] output, double error) { |
| 343 | long number = (long) value; |
| 344 | value -= number; |
| 345 | if (value < 0.000001 || error > 1) { |
| 346 | output[0] = (int) (number + value + 0.5); |
| 347 | output[1] = 1; |
| 348 | } else { |
| 349 | doubleToRational(1.0 / value, output, error / value); |
| 350 | number = number * output[0] + output[1]; |
| 351 | output[1] = output[0]; |
| 352 | output[0] = number; |
| 353 | } |
| 354 | } |
Chih-Chung Chang | bc21541 | 2011-09-19 11:09:39 +0800 | [diff] [blame^] | 355 | |
| 356 | public static boolean isPanorama(MediaItem item) { |
| 357 | if (item == null) return false; |
| 358 | int w = item.getWidth(); |
| 359 | int h = item.getHeight(); |
| 360 | return (h > 0 && w / h >= 2); |
| 361 | } |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 362 | } |