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 | |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 19 | import android.content.ActivityNotFoundException; |
| 20 | import android.content.ComponentName; |
| 21 | import android.content.Context; |
| 22 | import android.content.Intent; |
| 23 | import android.content.SharedPreferences; |
| 24 | import android.content.pm.PackageManager; |
| 25 | import android.content.pm.ResolveInfo; |
| 26 | import android.net.Uri; |
| 27 | import android.os.ConditionVariable; |
| 28 | import android.os.Environment; |
| 29 | import android.os.StatFs; |
| 30 | import android.preference.PreferenceManager; |
| 31 | import android.provider.MediaStore; |
| 32 | import android.util.DisplayMetrics; |
| 33 | import android.util.Log; |
| 34 | import android.view.WindowManager; |
| 35 | |
Owen Lin | 2b3ee0e | 2012-03-14 17:27:24 +0800 | [diff] [blame] | 36 | import com.android.gallery3d.R; |
| 37 | import com.android.gallery3d.app.PackagesMonitor; |
| 38 | import com.android.gallery3d.data.DataManager; |
| 39 | import com.android.gallery3d.data.MediaItem; |
| 40 | import com.android.gallery3d.util.ThreadPool.CancelListener; |
| 41 | import com.android.gallery3d.util.ThreadPool.JobContext; |
| 42 | |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 43 | import java.util.Arrays; |
| 44 | import java.util.List; |
Ray Chen | 376be10 | 2011-08-19 10:45:53 +0800 | [diff] [blame] | 45 | import java.util.Locale; |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 46 | |
| 47 | public class GalleryUtils { |
| 48 | private static final String TAG = "GalleryUtils"; |
| 49 | private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps"; |
| 50 | private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity"; |
Chih-Chung Chang | ab82ed9 | 2012-05-17 23:47:07 -0700 | [diff] [blame] | 51 | private static final String CAMERA_LAUNCHER_NAME = "com.android.camera.CameraLauncher"; |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 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 = "*/*"; |
Chih-Chung Chang | bd47a5c | 2011-10-18 19:54:31 +0800 | [diff] [blame] | 56 | private static final String DIR_TYPE_IMAGE = "vnd.android.cursor.dir/image"; |
| 57 | private static final String DIR_TYPE_VIDEO = "vnd.android.cursor.dir/video"; |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 58 | |
| 59 | private static final String PREFIX_PHOTO_EDITOR_UPDATE = "editor-update-"; |
| 60 | private static final String PREFIX_HAS_PHOTO_EDITOR = "has-editor-"; |
| 61 | |
| 62 | private static final String KEY_CAMERA_UPDATE = "camera-update"; |
| 63 | private static final String KEY_HAS_CAMERA = "has-camera"; |
| 64 | |
Chih-Chung Chang | ab82ed9 | 2012-05-17 23:47:07 -0700 | [diff] [blame] | 65 | private static float sPixelDensity = -1f; |
| 66 | private static boolean sCameraAvailableInitialized = false; |
| 67 | private static boolean sCameraAvailable; |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 68 | |
| 69 | public static void initialize(Context context) { |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 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 | |
Chih-Chung Chang | ab82ed9 | 2012-05-17 23:47:07 -0700 | [diff] [blame] | 192 | public static boolean isAnyCameraAvailable(Context context) { |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 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 | |
Chih-Chung Chang | ab82ed9 | 2012-05-17 23:47:07 -0700 | [diff] [blame] | 206 | public static boolean isCameraAvailable(Context context) { |
| 207 | if (sCameraAvailableInitialized) return sCameraAvailable; |
| 208 | PackageManager pm = context.getPackageManager(); |
| 209 | ComponentName name = new ComponentName(context, CAMERA_LAUNCHER_NAME); |
| 210 | int state = pm.getComponentEnabledSetting(name); |
| 211 | sCameraAvailableInitialized = true; |
| 212 | sCameraAvailable = |
| 213 | (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) |
| 214 | || (state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED); |
| 215 | return sCameraAvailable; |
| 216 | } |
| 217 | |
Chih-Chung Chang | 402237b | 2012-05-03 17:53:05 +0800 | [diff] [blame] | 218 | public static void startCameraActivity(Context context) { |
| 219 | Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA) |
| 220 | .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
| 221 | | Intent.FLAG_ACTIVITY_NEW_TASK); |
| 222 | context.startActivity(intent); |
| 223 | } |
| 224 | |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 225 | public static boolean isValidLocation(double latitude, double longitude) { |
| 226 | // TODO: change || to && after we fix the default location issue |
| 227 | return (latitude != MediaItem.INVALID_LATLNG || longitude != MediaItem.INVALID_LATLNG); |
| 228 | } |
Ray Chen | 376be10 | 2011-08-19 10:45:53 +0800 | [diff] [blame] | 229 | |
| 230 | public static String formatLatitudeLongitude(String format, double latitude, |
| 231 | double longitude) { |
| 232 | // We need to specify the locale otherwise it may go wrong in some language |
| 233 | // (e.g. Locale.FRENCH) |
| 234 | return String.format(Locale.ENGLISH, format, latitude, longitude); |
| 235 | } |
| 236 | |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 237 | public static void showOnMap(Context context, double latitude, double longitude) { |
| 238 | try { |
| 239 | // We don't use "geo:latitude,longitude" because it only centers |
| 240 | // the MapView to the specified location, but we need a marker |
| 241 | // for further operations (routing to/from). |
| 242 | // The q=(lat, lng) syntax is suggested by geo-team. |
Ray Chen | 376be10 | 2011-08-19 10:45:53 +0800 | [diff] [blame] | 243 | 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] | 244 | latitude, longitude); |
| 245 | ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME, |
| 246 | MAPS_CLASS_NAME); |
| 247 | Intent mapsIntent = new Intent(Intent.ACTION_VIEW, |
| 248 | Uri.parse(uri)).setComponent(compName); |
| 249 | context.startActivity(mapsIntent); |
| 250 | } catch (ActivityNotFoundException e) { |
| 251 | // Use the "geo intent" if no GMM is installed |
| 252 | Log.e(TAG, "GMM activity not found!", e); |
Ray Chen | 376be10 | 2011-08-19 10:45:53 +0800 | [diff] [blame] | 253 | String url = formatLatitudeLongitude("geo:%f,%f", latitude, longitude); |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 254 | Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); |
| 255 | context.startActivity(mapsIntent); |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | public static void setViewPointMatrix( |
| 260 | float matrix[], float x, float y, float z) { |
| 261 | // The matrix is |
| 262 | // -z, 0, x, 0 |
| 263 | // 0, -z, y, 0 |
| 264 | // 0, 0, 1, 0 |
| 265 | // 0, 0, 1, -z |
| 266 | Arrays.fill(matrix, 0, 16, 0); |
| 267 | matrix[0] = matrix[5] = matrix[15] = -z; |
| 268 | matrix[8] = x; |
| 269 | matrix[9] = y; |
| 270 | matrix[10] = matrix[11] = 1; |
| 271 | } |
| 272 | |
| 273 | public static int getBucketId(String path) { |
| 274 | return path.toLowerCase().hashCode(); |
| 275 | } |
| 276 | |
| 277 | // Returns a (localized) string for the given duration (in seconds). |
| 278 | public static String formatDuration(final Context context, int duration) { |
| 279 | int h = duration / 3600; |
| 280 | int m = (duration - h * 3600) / 60; |
| 281 | int s = duration - (h * 3600 + m * 60); |
| 282 | String durationValue; |
| 283 | if (h == 0) { |
| 284 | durationValue = String.format(context.getString(R.string.details_ms), m, s); |
| 285 | } else { |
| 286 | durationValue = String.format(context.getString(R.string.details_hms), h, m, s); |
| 287 | } |
| 288 | return durationValue; |
| 289 | } |
| 290 | |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 291 | public static int determineTypeBits(Context context, Intent intent) { |
| 292 | int typeBits = 0; |
| 293 | String type = intent.resolveType(context); |
Chih-Chung Chang | bd47a5c | 2011-10-18 19:54:31 +0800 | [diff] [blame] | 294 | |
| 295 | if (MIME_TYPE_ALL.equals(type)) { |
| 296 | typeBits = DataManager.INCLUDE_ALL; |
| 297 | } else if (MIME_TYPE_IMAGE.equals(type) || |
| 298 | DIR_TYPE_IMAGE.equals(type)) { |
| 299 | typeBits = DataManager.INCLUDE_IMAGE; |
| 300 | } else if (MIME_TYPE_VIDEO.equals(type) || |
| 301 | DIR_TYPE_VIDEO.equals(type)) { |
| 302 | typeBits = DataManager.INCLUDE_VIDEO; |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 303 | } else { |
Chih-Chung Chang | bd47a5c | 2011-10-18 19:54:31 +0800 | [diff] [blame] | 304 | typeBits = DataManager.INCLUDE_ALL; |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 305 | } |
Chih-Chung Chang | bd47a5c | 2011-10-18 19:54:31 +0800 | [diff] [blame] | 306 | |
| 307 | if (intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false)) { |
| 308 | typeBits |= DataManager.INCLUDE_LOCAL_ONLY; |
| 309 | } |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 310 | |
| 311 | return typeBits; |
| 312 | } |
| 313 | |
| 314 | public static int getSelectionModePrompt(int typeBits) { |
| 315 | if ((typeBits & DataManager.INCLUDE_VIDEO) != 0) { |
| 316 | return (typeBits & DataManager.INCLUDE_IMAGE) == 0 |
| 317 | ? R.string.select_video |
| 318 | : R.string.select_item; |
| 319 | } |
| 320 | return R.string.select_image; |
| 321 | } |
| 322 | |
| 323 | public static boolean hasSpaceForSize(long size) { |
| 324 | String state = Environment.getExternalStorageState(); |
| 325 | if (!Environment.MEDIA_MOUNTED.equals(state)) { |
| 326 | return false; |
| 327 | } |
| 328 | |
| 329 | String path = Environment.getExternalStorageDirectory().getPath(); |
| 330 | try { |
| 331 | StatFs stat = new StatFs(path); |
| 332 | return stat.getAvailableBlocks() * (long) stat.getBlockSize() > size; |
| 333 | } catch (Exception e) { |
| 334 | Log.i(TAG, "Fail to access external storage", e); |
| 335 | } |
| 336 | return false; |
| 337 | } |
| 338 | |
Chih-Chung Chang | bc21541 | 2011-09-19 11:09:39 +0800 | [diff] [blame] | 339 | public static boolean isPanorama(MediaItem item) { |
| 340 | if (item == null) return false; |
| 341 | int w = item.getWidth(); |
| 342 | int h = item.getHeight(); |
| 343 | return (h > 0 && w / h >= 2); |
| 344 | } |
Owen Lin | f9a0a43 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 345 | } |