Home | History | Annotate | Download | only in util
      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;
     46 import java.util.Locale;
     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     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";
     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 
     65     private static Context sContext;
     66 
     67 
     68     static float sPixelDensity = -1f;
     69 
     70     public static void initialize(Context context) {
     71         sContext = context;
     72         if (sPixelDensity < 0) {
     73             DisplayMetrics metrics = new DisplayMetrics();
     74             WindowManager wm = (WindowManager)
     75                     context.getSystemService(Context.WINDOW_SERVICE);
     76             wm.getDefaultDisplay().getMetrics(metrics);
     77             sPixelDensity = metrics.density;
     78         }
     79     }
     80 
     81     public static float dpToPixel(float dp) {
     82         return sPixelDensity * dp;
     83     }
     84 
     85     public static int dpToPixel(int dp) {
     86         return Math.round(dpToPixel((float) dp));
     87     }
     88 
     89     public static int meterToPixel(float meter) {
     90         // 1 meter = 39.37 inches, 1 inch = 160 dp.
     91         return Math.round(dpToPixel(meter * 39.37f * 160));
     92     }
     93 
     94     public static byte[] getBytes(String in) {
     95         byte[] result = new byte[in.length() * 2];
     96         int output = 0;
     97         for (char ch : in.toCharArray()) {
     98             result[output++] = (byte) (ch & 0xFF);
     99             result[output++] = (byte) (ch >> 8);
    100         }
    101         return result;
    102     }
    103 
    104     // Below are used the detect using database in the render thread. It only
    105     // works most of the time, but that's ok because it's for debugging only.
    106 
    107     private static volatile Thread sCurrentThread;
    108     private static volatile boolean sWarned;
    109 
    110     public static void setRenderThread() {
    111         sCurrentThread = Thread.currentThread();
    112     }
    113 
    114     public static void assertNotInRenderThread() {
    115         if (!sWarned) {
    116             if (Thread.currentThread() == sCurrentThread) {
    117                 sWarned = true;
    118                 Log.w(TAG, new Throwable("Should not do this in render thread"));
    119             }
    120         }
    121     }
    122 
    123     private static final double RAD_PER_DEG = Math.PI / 180.0;
    124     private static final double EARTH_RADIUS_METERS = 6367000.0;
    125 
    126     public static double fastDistanceMeters(double latRad1, double lngRad1,
    127             double latRad2, double lngRad2) {
    128        if ((Math.abs(latRad1 - latRad2) > RAD_PER_DEG)
    129              || (Math.abs(lngRad1 - lngRad2) > RAD_PER_DEG)) {
    130            return accurateDistanceMeters(latRad1, lngRad1, latRad2, lngRad2);
    131        }
    132        // Approximate sin(x) = x.
    133        double sineLat = (latRad1 - latRad2);
    134 
    135        // Approximate sin(x) = x.
    136        double sineLng = (lngRad1 - lngRad2);
    137 
    138        // Approximate cos(lat1) * cos(lat2) using
    139        // cos((lat1 + lat2)/2) ^ 2
    140        double cosTerms = Math.cos((latRad1 + latRad2) / 2.0);
    141        cosTerms = cosTerms * cosTerms;
    142        double trigTerm = sineLat * sineLat + cosTerms * sineLng * sineLng;
    143        trigTerm = Math.sqrt(trigTerm);
    144 
    145        // Approximate arcsin(x) = x
    146        return EARTH_RADIUS_METERS * trigTerm;
    147     }
    148 
    149     public static double accurateDistanceMeters(double lat1, double lng1,
    150             double lat2, double lng2) {
    151         double dlat = Math.sin(0.5 * (lat2 - lat1));
    152         double dlng = Math.sin(0.5 * (lng2 - lng1));
    153         double x = dlat * dlat + dlng * dlng * Math.cos(lat1) * Math.cos(lat2);
    154         return (2 * Math.atan2(Math.sqrt(x), Math.sqrt(Math.max(0.0,
    155                 1.0 - x)))) * EARTH_RADIUS_METERS;
    156     }
    157 
    158 
    159     public static final double toMile(double meter) {
    160         return meter / 1609;
    161     }
    162 
    163     // For debugging, it will block the caller for timeout millis.
    164     public static void fakeBusy(JobContext jc, int timeout) {
    165         final ConditionVariable cv = new ConditionVariable();
    166         jc.setCancelListener(new CancelListener() {
    167             public void onCancel() {
    168                 cv.open();
    169             }
    170         });
    171         cv.block(timeout);
    172         jc.setCancelListener(null);
    173     }
    174 
    175     public static boolean isEditorAvailable(Context context, String mimeType) {
    176         int version = PackagesMonitor.getPackagesVersion(context);
    177 
    178         String updateKey = PREFIX_PHOTO_EDITOR_UPDATE + mimeType;
    179         String hasKey = PREFIX_HAS_PHOTO_EDITOR + mimeType;
    180 
    181         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    182         if (prefs.getInt(updateKey, 0) != version) {
    183             PackageManager packageManager = context.getPackageManager();
    184             List<ResolveInfo> infos = packageManager.queryIntentActivities(
    185                     new Intent(Intent.ACTION_EDIT).setType(mimeType), 0);
    186             prefs.edit().putInt(updateKey, version)
    187                         .putBoolean(hasKey, !infos.isEmpty())
    188                         .commit();
    189         }
    190 
    191         return prefs.getBoolean(hasKey, true);
    192     }
    193 
    194     public static boolean isCameraAvailable(Context context) {
    195         int version = PackagesMonitor.getPackagesVersion(context);
    196         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    197         if (prefs.getInt(KEY_CAMERA_UPDATE, 0) != version) {
    198             PackageManager packageManager = context.getPackageManager();
    199             List<ResolveInfo> infos = packageManager.queryIntentActivities(
    200                     new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA), 0);
    201             prefs.edit().putInt(KEY_CAMERA_UPDATE, version)
    202                         .putBoolean(KEY_HAS_CAMERA, !infos.isEmpty())
    203                         .commit();
    204         }
    205         return prefs.getBoolean(KEY_HAS_CAMERA, true);
    206     }
    207 
    208     public static boolean isValidLocation(double latitude, double longitude) {
    209         // TODO: change || to && after we fix the default location issue
    210         return (latitude != MediaItem.INVALID_LATLNG || longitude != MediaItem.INVALID_LATLNG);
    211     }
    212 
    213     public static String formatLatitudeLongitude(String format, double latitude,
    214             double longitude) {
    215         // We need to specify the locale otherwise it may go wrong in some language
    216         // (e.g. Locale.FRENCH)
    217         return String.format(Locale.ENGLISH, format, latitude, longitude);
    218     }
    219 
    220     public static void showOnMap(Context context, double latitude, double longitude) {
    221         try {
    222             // We don't use "geo:latitude,longitude" because it only centers
    223             // the MapView to the specified location, but we need a marker
    224             // for further operations (routing to/from).
    225             // The q=(lat, lng) syntax is suggested by geo-team.
    226             String uri = formatLatitudeLongitude("http://maps.google.com/maps?f=q&q=(%f,%f)",
    227                     latitude, longitude);
    228             ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
    229                     MAPS_CLASS_NAME);
    230             Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
    231                     Uri.parse(uri)).setComponent(compName);
    232             context.startActivity(mapsIntent);
    233         } catch (ActivityNotFoundException e) {
    234             // Use the "geo intent" if no GMM is installed
    235             Log.e(TAG, "GMM activity not found!", e);
    236             String url = formatLatitudeLongitude("geo:%f,%f", latitude, longitude);
    237             Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
    238             context.startActivity(mapsIntent);
    239         }
    240     }
    241 
    242     public static void setViewPointMatrix(
    243             float matrix[], float x, float y, float z) {
    244         // The matrix is
    245         // -z,  0,  x,  0
    246         //  0, -z,  y,  0
    247         //  0,  0,  1,  0
    248         //  0,  0,  1, -z
    249         Arrays.fill(matrix, 0, 16, 0);
    250         matrix[0] = matrix[5] = matrix[15] = -z;
    251         matrix[8] = x;
    252         matrix[9] = y;
    253         matrix[10] = matrix[11] = 1;
    254     }
    255 
    256     public static int getBucketId(String path) {
    257         return path.toLowerCase().hashCode();
    258     }
    259 
    260     // Returns a (localized) string for the given duration (in seconds).
    261     public static String formatDuration(final Context context, int duration) {
    262         int h = duration / 3600;
    263         int m = (duration - h * 3600) / 60;
    264         int s = duration - (h * 3600 + m * 60);
    265         String durationValue;
    266         if (h == 0) {
    267             durationValue = String.format(context.getString(R.string.details_ms), m, s);
    268         } else {
    269             durationValue = String.format(context.getString(R.string.details_hms), h, m, s);
    270         }
    271         return durationValue;
    272     }
    273 
    274     public static void setSpinnerVisibility(final Activity activity,
    275             final boolean visible) {
    276         SpinnerVisibilitySetter.getInstance(activity).setSpinnerVisibility(visible);
    277     }
    278 
    279     public static int determineTypeBits(Context context, Intent intent) {
    280         int typeBits = 0;
    281         String type = intent.resolveType(context);
    282 
    283         if (MIME_TYPE_ALL.equals(type)) {
    284             typeBits = DataManager.INCLUDE_ALL;
    285         } else if (MIME_TYPE_IMAGE.equals(type) ||
    286                 DIR_TYPE_IMAGE.equals(type)) {
    287             typeBits = DataManager.INCLUDE_IMAGE;
    288         } else if (MIME_TYPE_VIDEO.equals(type) ||
    289                 DIR_TYPE_VIDEO.equals(type)) {
    290             typeBits = DataManager.INCLUDE_VIDEO;
    291         } else {
    292             typeBits = DataManager.INCLUDE_ALL;
    293         }
    294 
    295         if (intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false)) {
    296             typeBits |= DataManager.INCLUDE_LOCAL_ONLY;
    297         }
    298 
    299         return typeBits;
    300     }
    301 
    302     public static int getSelectionModePrompt(int typeBits) {
    303         if ((typeBits & DataManager.INCLUDE_VIDEO) != 0) {
    304             return (typeBits & DataManager.INCLUDE_IMAGE) == 0
    305                     ? R.string.select_video
    306                     : R.string.select_item;
    307         }
    308         return R.string.select_image;
    309     }
    310 
    311     public static boolean hasSpaceForSize(long size) {
    312         String state = Environment.getExternalStorageState();
    313         if (!Environment.MEDIA_MOUNTED.equals(state)) {
    314             return false;
    315         }
    316 
    317         String path = Environment.getExternalStorageDirectory().getPath();
    318         try {
    319             StatFs stat = new StatFs(path);
    320             return stat.getAvailableBlocks() * (long) stat.getBlockSize() > size;
    321         } catch (Exception e) {
    322             Log.i(TAG, "Fail to access external storage", e);
    323         }
    324         return false;
    325     }
    326 
    327     public static void assertInMainThread() {
    328         if (Thread.currentThread() == sContext.getMainLooper().getThread()) {
    329             throw new AssertionError();
    330         }
    331     }
    332 
    333     public static void doubleToRational(double value, long[] output) {
    334         // error is a magic number to control the tollerance of error
    335         doubleToRational(value, output, 0.00001);
    336     }
    337 
    338     private static void doubleToRational(double value, long[] output, double error) {
    339         long number = (long) value;
    340         value -= number;
    341         if (value < 0.000001 || error > 1) {
    342             output[0] = (int) (number + value + 0.5);
    343             output[1] = 1;
    344         } else {
    345             doubleToRational(1.0 / value, output, error / value);
    346             number = number * output[0] + output[1];
    347             output[1] = output[0];
    348             output[0] = number;
    349         }
    350     }
    351 
    352     public static boolean isPanorama(MediaItem item) {
    353         if (item == null) return false;
    354         int w = item.getWidth();
    355         int h = item.getHeight();
    356         return (h > 0 && w / h >= 2);
    357     }
    358 }
    359