Home | History | Annotate | Download | only in cache
      1 /*
      2  * Copyright (C) 2012 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.filtershow.cache;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.Context;
     21 import android.content.res.Resources;
     22 import android.database.Cursor;
     23 import android.database.sqlite.SQLiteException;
     24 import android.graphics.Bitmap;
     25 import android.graphics.Bitmap.CompressFormat;
     26 import android.graphics.BitmapFactory;
     27 import android.graphics.BitmapRegionDecoder;
     28 import android.graphics.Matrix;
     29 import android.graphics.Rect;
     30 import android.graphics.Bitmap.CompressFormat;
     31 import android.net.Uri;
     32 import android.provider.MediaStore;
     33 import android.util.Log;
     34 
     35 import com.adobe.xmp.XMPException;
     36 import com.adobe.xmp.XMPMeta;
     37 import com.android.gallery3d.R;
     38 import com.android.gallery3d.common.Utils;
     39 import com.android.gallery3d.exif.ExifTag;
     40 import com.android.gallery3d.exif.ExifInterface;
     41 import com.android.gallery3d.filtershow.FilterShowActivity;
     42 import com.android.gallery3d.filtershow.HistoryAdapter;
     43 import com.android.gallery3d.filtershow.filters.FiltersManager;
     44 import com.android.gallery3d.filtershow.imageshow.ImageShow;
     45 import com.android.gallery3d.filtershow.imageshow.MasterImage;
     46 import com.android.gallery3d.filtershow.presets.ImagePreset;
     47 import com.android.gallery3d.filtershow.tools.BitmapTask;
     48 import com.android.gallery3d.filtershow.tools.SaveCopyTask;
     49 import com.android.gallery3d.util.InterruptableOutputStream;
     50 import com.android.gallery3d.util.XmpUtilHelper;
     51 
     52 import java.io.ByteArrayInputStream;
     53 import java.io.Closeable;
     54 import java.io.File;
     55 import java.io.FileInputStream;
     56 import java.io.FileNotFoundException;
     57 import java.io.IOException;
     58 import java.io.InputStream;
     59 import java.io.OutputStream;
     60 import java.util.Vector;
     61 import java.util.concurrent.locks.ReentrantLock;
     62 
     63 
     64 // TODO: this class has waaaay to much bitmap copying.  Cleanup.
     65 public class ImageLoader {
     66 
     67     private static final String LOGTAG = "ImageLoader";
     68     private final Vector<ImageShow> mListeners = new Vector<ImageShow>();
     69     private Bitmap mOriginalBitmapSmall = null;
     70     private Bitmap mOriginalBitmapLarge = null;
     71     private Bitmap mOriginalBitmapHighres = null;
     72     private Bitmap mBackgroundBitmap = null;
     73 
     74     private final ZoomCache mZoomCache = new ZoomCache();
     75 
     76     private int mOrientation = 0;
     77     private HistoryAdapter mAdapter = null;
     78 
     79     private FilterShowActivity mActivity = null;
     80 
     81     public static final String JPEG_MIME_TYPE = "image/jpeg";
     82 
     83     public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
     84     public static final int DEFAULT_COMPRESS_QUALITY = 95;
     85 
     86     public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT;
     87     public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP;
     88     public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT;
     89     public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM;
     90     public static final int ORI_FLIP_HOR = ExifInterface.Orientation.TOP_RIGHT;
     91     public static final int ORI_FLIP_VERT = ExifInterface.Orientation.BOTTOM_RIGHT;
     92     public static final int ORI_TRANSPOSE = ExifInterface.Orientation.LEFT_TOP;
     93     public static final int ORI_TRANSVERSE = ExifInterface.Orientation.LEFT_BOTTOM;
     94 
     95     private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
     96     private Context mContext = null;
     97     private Uri mUri = null;
     98 
     99     private Rect mOriginalBounds = null;
    100     private static int mZoomOrientation = ORI_NORMAL;
    101 
    102     static final int MAX_BITMAP_DIM = 900;
    103 
    104     private ReentrantLock mLoadingLock = new ReentrantLock();
    105 
    106     public ImageLoader(FilterShowActivity activity, Context context) {
    107         mActivity = activity;
    108         mContext = context;
    109     }
    110 
    111     public static int getZoomOrientation() {
    112         return mZoomOrientation;
    113     }
    114 
    115     public FilterShowActivity getActivity() {
    116         return mActivity;
    117     }
    118 
    119     public boolean loadBitmap(Uri uri, int size) {
    120         mLoadingLock.lock();
    121         mUri = uri;
    122         mOrientation = getOrientation(mContext, uri);
    123         mOriginalBitmapSmall = loadScaledBitmap(uri, 160);
    124         if (mOriginalBitmapSmall == null) {
    125             // Couldn't read the bitmap, let's exit
    126             mLoadingLock.unlock();
    127             return false;
    128         }
    129         mOriginalBitmapLarge = loadScaledBitmap(uri, size);
    130         if (mOriginalBitmapLarge == null) {
    131             mLoadingLock.unlock();
    132             return false;
    133         }
    134         if (MasterImage.getImage().supportsHighRes()) {
    135             int highresPreviewSize = mOriginalBitmapLarge.getWidth() * 2;
    136             if (highresPreviewSize > mOriginalBounds.width()) {
    137                 highresPreviewSize = mOriginalBounds.width();
    138             }
    139             mOriginalBitmapHighres = loadScaledBitmap(uri, highresPreviewSize, false);
    140         }
    141         updateBitmaps();
    142         mLoadingLock.unlock();
    143         return true;
    144     }
    145 
    146     public Uri getUri() {
    147         return mUri;
    148     }
    149 
    150     public Rect getOriginalBounds() {
    151         return mOriginalBounds;
    152     }
    153 
    154     public static int getOrientation(Context context, Uri uri) {
    155         if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
    156             String mimeType = context.getContentResolver().getType(uri);
    157             if (mimeType != ImageLoader.JPEG_MIME_TYPE) {
    158                 return -1;
    159             }
    160             String path = uri.getPath();
    161             int orientation = -1;
    162             InputStream is = null;
    163             ExifInterface exif = new ExifInterface();
    164             try {
    165                 exif.readExif(path);
    166                 orientation = ExifInterface.getRotationForOrientationValue(
    167                         exif.getTagIntValue(ExifInterface.TAG_ORIENTATION).shortValue());
    168             } catch (IOException e) {
    169                 Log.w(LOGTAG, "Failed to read EXIF orientation", e);
    170             }
    171             return orientation;
    172         }
    173         Cursor cursor = null;
    174         try {
    175             cursor = context.getContentResolver().query(uri,
    176                     new String[] {
    177                         MediaStore.Images.ImageColumns.ORIENTATION
    178                     },
    179                     null, null, null);
    180             if (cursor.moveToNext()) {
    181                 int ori = cursor.getInt(0);
    182 
    183                 switch (ori) {
    184                     case 0:
    185                         return ORI_NORMAL;
    186                     case 90:
    187                         return ORI_ROTATE_90;
    188                     case 270:
    189                         return ORI_ROTATE_270;
    190                     case 180:
    191                         return ORI_ROTATE_180;
    192                     default:
    193                         return -1;
    194                 }
    195             } else {
    196                 return -1;
    197             }
    198         } catch (SQLiteException e) {
    199             return -1;
    200         } catch (IllegalArgumentException e) {
    201             return -1;
    202         } finally {
    203             Utils.closeSilently(cursor);
    204         }
    205     }
    206 
    207     private void updateBitmaps() {
    208         if (mOrientation > 1) {
    209             mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall, mOrientation);
    210             mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge, mOrientation);
    211             if (mOriginalBitmapHighres != null) {
    212                 mOriginalBitmapHighres = rotateToPortrait(mOriginalBitmapHighres, mOrientation);
    213             }
    214         }
    215         mZoomOrientation = mOrientation;
    216         warnListeners();
    217     }
    218 
    219     public Bitmap decodeImage(int id, BitmapFactory.Options options) {
    220         return BitmapFactory.decodeResource(mContext.getResources(), id, options);
    221     }
    222 
    223     public static Bitmap rotateToPortrait(Bitmap bitmap, int ori) {
    224         Matrix matrix = new Matrix();
    225         int w = bitmap.getWidth();
    226         int h = bitmap.getHeight();
    227         if (ori == ORI_ROTATE_90 ||
    228                 ori == ORI_ROTATE_270 ||
    229                 ori == ORI_TRANSPOSE ||
    230                 ori == ORI_TRANSVERSE) {
    231             int tmp = w;
    232             w = h;
    233             h = tmp;
    234         }
    235         switch (ori) {
    236             case ORI_ROTATE_90:
    237                 matrix.setRotate(90, w / 2f, h / 2f);
    238                 break;
    239             case ORI_ROTATE_180:
    240                 matrix.setRotate(180, w / 2f, h / 2f);
    241                 break;
    242             case ORI_ROTATE_270:
    243                 matrix.setRotate(270, w / 2f, h / 2f);
    244                 break;
    245             case ORI_FLIP_HOR:
    246                 matrix.preScale(-1, 1);
    247                 break;
    248             case ORI_FLIP_VERT:
    249                 matrix.preScale(1, -1);
    250                 break;
    251             case ORI_TRANSPOSE:
    252                 matrix.setRotate(90, w / 2f, h / 2f);
    253                 matrix.preScale(1, -1);
    254                 break;
    255             case ORI_TRANSVERSE:
    256                 matrix.setRotate(270, w / 2f, h / 2f);
    257                 matrix.preScale(1, -1);
    258                 break;
    259             case ORI_NORMAL:
    260             default:
    261                 return bitmap;
    262         }
    263 
    264         return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
    265                 bitmap.getHeight(), matrix, true);
    266     }
    267 
    268     private Bitmap loadRegionBitmap(Uri uri, BitmapFactory.Options options, Rect bounds) {
    269         InputStream is = null;
    270         try {
    271             is = mContext.getContentResolver().openInputStream(uri);
    272             BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
    273             Rect r = new Rect(0, 0, decoder.getWidth(), decoder.getHeight());
    274             // return null if bounds are not entirely within the bitmap
    275             if (!r.contains(bounds)) {
    276                 return null;
    277             }
    278             return decoder.decodeRegion(bounds, options);
    279         } catch (FileNotFoundException e) {
    280             Log.e(LOGTAG, "FileNotFoundException: " + uri);
    281         } catch (Exception e) {
    282             e.printStackTrace();
    283         } finally {
    284             Utils.closeSilently(is);
    285         }
    286         return null;
    287     }
    288 
    289     private Bitmap loadScaledBitmap(Uri uri, int size) {
    290         return loadScaledBitmap(uri, size, true);
    291     }
    292 
    293     private Bitmap loadScaledBitmap(Uri uri, int size, boolean enforceSize) {
    294         InputStream is = null;
    295         try {
    296             is = mContext.getContentResolver().openInputStream(uri);
    297             Log.v(LOGTAG, "loading uri " + uri.getPath() + " input stream: "
    298                     + is);
    299             BitmapFactory.Options o = new BitmapFactory.Options();
    300             o.inJustDecodeBounds = true;
    301             BitmapFactory.decodeStream(is, null, o);
    302 
    303             int width_tmp = o.outWidth;
    304             int height_tmp = o.outHeight;
    305 
    306             mOriginalBounds = new Rect(0, 0, width_tmp, height_tmp);
    307 
    308             int scale = 1;
    309             while (true) {
    310                 if (width_tmp <= 2 || height_tmp <= 2) {
    311                     break;
    312                 }
    313                 if (!enforceSize
    314                         || (width_tmp <= MAX_BITMAP_DIM
    315                         && height_tmp <= MAX_BITMAP_DIM)) {
    316                     if (width_tmp / 2 < size || height_tmp / 2 < size) {
    317                         break;
    318                     }
    319                 }
    320                 width_tmp /= 2;
    321                 height_tmp /= 2;
    322                 scale *= 2;
    323             }
    324 
    325             // decode with inSampleSize
    326             BitmapFactory.Options o2 = new BitmapFactory.Options();
    327             o2.inSampleSize = scale;
    328             o2.inMutable = true;
    329 
    330             Utils.closeSilently(is);
    331             is = mContext.getContentResolver().openInputStream(uri);
    332             return BitmapFactory.decodeStream(is, null, o2);
    333         } catch (FileNotFoundException e) {
    334             Log.e(LOGTAG, "FileNotFoundException: " + uri);
    335         } catch (Exception e) {
    336             e.printStackTrace();
    337         } finally {
    338             Utils.closeSilently(is);
    339         }
    340         return null;
    341     }
    342 
    343     public Bitmap getBackgroundBitmap(Resources resources) {
    344         if (mBackgroundBitmap == null) {
    345             mBackgroundBitmap = BitmapFactory.decodeResource(resources,
    346                     R.drawable.filtershow_background);
    347         }
    348         return mBackgroundBitmap;
    349 
    350     }
    351 
    352     public Bitmap getOriginalBitmapSmall() {
    353         return mOriginalBitmapSmall;
    354     }
    355 
    356     public Bitmap getOriginalBitmapLarge() {
    357         return mOriginalBitmapLarge;
    358     }
    359 
    360     public Bitmap getOriginalBitmapHighres() {
    361         return mOriginalBitmapHighres;
    362     }
    363 
    364     public void addListener(ImageShow imageShow) {
    365         mLoadingLock.lock();
    366         if (!mListeners.contains(imageShow)) {
    367             mListeners.add(imageShow);
    368         }
    369         mLoadingLock.unlock();
    370     }
    371 
    372     private void warnListeners() {
    373         mActivity.runOnUiThread(mWarnListenersRunnable);
    374     }
    375 
    376     private Runnable mWarnListenersRunnable = new Runnable() {
    377 
    378         @Override
    379         public void run() {
    380             for (int i = 0; i < mListeners.size(); i++) {
    381                 ImageShow imageShow = mListeners.elementAt(i);
    382                 imageShow.imageLoaded();
    383             }
    384         }
    385     };
    386 
    387     public Bitmap getScaleOneImageForPreset(Rect bounds, Rect destination) {
    388         mLoadingLock.lock();
    389         BitmapFactory.Options options = new BitmapFactory.Options();
    390         options.inMutable = true;
    391         if (destination != null) {
    392             if (bounds.width() > destination.width()) {
    393                 int sampleSize = 1;
    394                 int w = bounds.width();
    395                 while (w > destination.width()) {
    396                     sampleSize *= 2;
    397                     w /= sampleSize;
    398                 }
    399                 options.inSampleSize = sampleSize;
    400             }
    401         }
    402         Bitmap bmp = loadRegionBitmap(mUri, options, bounds);
    403         mLoadingLock.unlock();
    404         return bmp;
    405     }
    406 
    407     public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
    408             File destination) {
    409         new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() {
    410 
    411             @Override
    412             public void onComplete(Uri result) {
    413                 filterShowActivity.completeSaveImage(result);
    414             }
    415 
    416         }).execute(preset);
    417     }
    418 
    419     public static Bitmap loadMutableBitmap(Context context, Uri sourceUri) {
    420         BitmapFactory.Options options = new BitmapFactory.Options();
    421         return loadMutableBitmap(context, sourceUri, options);
    422     }
    423 
    424     public static Bitmap loadMutableBitmap(Context context, Uri sourceUri,
    425             BitmapFactory.Options options) {
    426         // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
    427         // exist)
    428         options.inMutable = true;
    429 
    430         Bitmap bitmap = decodeUriWithBackouts(context, sourceUri, options);
    431         if (bitmap == null) {
    432             return null;
    433         }
    434         int orientation = ImageLoader.getOrientation(context, sourceUri);
    435         bitmap = ImageLoader.rotateToPortrait(bitmap, orientation);
    436         return bitmap;
    437     }
    438 
    439     public static Bitmap decodeUriWithBackouts(Context context, Uri sourceUri,
    440             BitmapFactory.Options options) {
    441         boolean noBitmap = true;
    442         int num_tries = 0;
    443         InputStream is = getInputStream(context, sourceUri);
    444 
    445         if (options.inSampleSize < 1) {
    446             options.inSampleSize = 1;
    447         }
    448         // Stopgap fix for low-memory devices.
    449         Bitmap bmap = null;
    450         while (noBitmap) {
    451             if (is == null) {
    452                 return null;
    453             }
    454             try {
    455                 // Try to decode, downsample if low-memory.
    456                 bmap = BitmapFactory.decodeStream(is, null, options);
    457                 noBitmap = false;
    458             } catch (java.lang.OutOfMemoryError e) {
    459                 // Try 5 times before failing for good.
    460                 if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
    461                     throw e;
    462                 }
    463                 is = null;
    464                 bmap = null;
    465                 System.gc();
    466                 is = getInputStream(context, sourceUri);
    467                 options.inSampleSize *= 2;
    468             }
    469         }
    470         Utils.closeSilently(is);
    471         return bmap;
    472     }
    473 
    474     private static InputStream getInputStream(Context context, Uri sourceUri) {
    475         InputStream is = null;
    476         try {
    477             is = context.getContentResolver().openInputStream(sourceUri);
    478         } catch (FileNotFoundException e) {
    479             Log.w(LOGTAG, "could not load bitmap ", e);
    480             Utils.closeSilently(is);
    481             is = null;
    482         }
    483         return is;
    484     }
    485 
    486     public static Bitmap decodeResourceWithBackouts(Resources res, BitmapFactory.Options options,
    487             int id) {
    488         boolean noBitmap = true;
    489         int num_tries = 0;
    490         if (options.inSampleSize < 1) {
    491             options.inSampleSize = 1;
    492         }
    493         // Stopgap fix for low-memory devices.
    494         Bitmap bmap = null;
    495         while (noBitmap) {
    496             try {
    497                 // Try to decode, downsample if low-memory.
    498                 bmap = BitmapFactory.decodeResource(
    499                         res, id, options);
    500                 noBitmap = false;
    501             } catch (java.lang.OutOfMemoryError e) {
    502                 // Try 5 times before failing for good.
    503                 if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
    504                     throw e;
    505                 }
    506                 bmap = null;
    507                 System.gc();
    508                 options.inSampleSize *= 2;
    509             }
    510         }
    511         return bmap;
    512     }
    513 
    514     public void returnFilteredResult(ImagePreset preset,
    515             final FilterShowActivity filterShowActivity) {
    516         BitmapTask.Callbacks<ImagePreset> cb = new BitmapTask.Callbacks<ImagePreset>() {
    517 
    518             @Override
    519             public void onComplete(Bitmap result) {
    520                 filterShowActivity.onFilteredResult(result);
    521             }
    522 
    523             @Override
    524             public void onCancel() {
    525             }
    526 
    527             @Override
    528             public Bitmap onExecute(ImagePreset param) {
    529                 if (param == null || mUri == null) {
    530                     return null;
    531                 }
    532                 BitmapFactory.Options options = new BitmapFactory.Options();
    533                 boolean noBitmap = true;
    534                 int num_tries = 0;
    535                 if (options.inSampleSize < 1) {
    536                     options.inSampleSize = 1;
    537                 }
    538                 Bitmap bitmap = null;
    539                 // Stopgap fix for low-memory devices.
    540                 while (noBitmap) {
    541                     try {
    542                         // Try to do bitmap operations, downsample if low-memory
    543                         bitmap = loadMutableBitmap(mContext, mUri, options);
    544                         if (bitmap == null) {
    545                             Log.w(LOGTAG, "Failed to save image!");
    546                             return null;
    547                         }
    548                         CachingPipeline pipeline = new CachingPipeline(
    549                                 FiltersManager.getManager(), "Saving");
    550                         bitmap = pipeline.renderFinalImage(bitmap, param);
    551                         noBitmap = false;
    552                     } catch (java.lang.OutOfMemoryError e) {
    553                         // Try 5 times before failing for good.
    554                         if (++num_tries >= 5) {
    555                             throw e;
    556                         }
    557                         bitmap = null;
    558                         System.gc();
    559                         options.inSampleSize *= 2;
    560                     }
    561                 }
    562                 return bitmap;
    563             }
    564         };
    565 
    566         (new BitmapTask<ImagePreset>(cb)).execute(preset);
    567     }
    568 
    569     private String getFileExtension(String requestFormat) {
    570         String outputFormat = (requestFormat == null)
    571                 ? "jpg"
    572                 : requestFormat;
    573         outputFormat = outputFormat.toLowerCase();
    574         return (outputFormat.equals("png") || outputFormat.equals("gif"))
    575                 ? "png" // We don't support gif compression.
    576                 : "jpg";
    577     }
    578 
    579     private CompressFormat convertExtensionToCompressFormat(String extension) {
    580         return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
    581     }
    582 
    583     public void saveToUri(Bitmap bmap, Uri uri, final String outputFormat,
    584             final FilterShowActivity filterShowActivity) {
    585 
    586         OutputStream out = null;
    587         try {
    588             out = filterShowActivity.getContentResolver().openOutputStream(uri);
    589         } catch (FileNotFoundException e) {
    590             Log.w(LOGTAG, "cannot write output", e);
    591             out = null;
    592         } finally {
    593             if (bmap == null || out == null) {
    594                 return;
    595             }
    596         }
    597 
    598         final InterruptableOutputStream ios = new InterruptableOutputStream(out);
    599 
    600         BitmapTask.Callbacks<Bitmap> cb = new BitmapTask.Callbacks<Bitmap>() {
    601 
    602             @Override
    603             public void onComplete(Bitmap result) {
    604                 filterShowActivity.done();
    605             }
    606 
    607             @Override
    608             public void onCancel() {
    609                 ios.interrupt();
    610             }
    611 
    612             @Override
    613             public Bitmap onExecute(Bitmap param) {
    614                 CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(outputFormat));
    615                 param.compress(cf, DEFAULT_COMPRESS_QUALITY, ios);
    616                 Utils.closeSilently(ios);
    617                 return null;
    618             }
    619         };
    620 
    621         (new BitmapTask<Bitmap>(cb)).execute(bmap);
    622     }
    623 
    624     public void setAdapter(HistoryAdapter adapter) {
    625         mAdapter = adapter;
    626     }
    627 
    628     public HistoryAdapter getHistory() {
    629         return mAdapter;
    630     }
    631 
    632     public XMPMeta getXmpObject() {
    633         try {
    634             InputStream is = mContext.getContentResolver().openInputStream(getUri());
    635             return XmpUtilHelper.extractXMPMeta(is);
    636         } catch (FileNotFoundException e) {
    637             return null;
    638         }
    639     }
    640 
    641     /**
    642      * Determine if this is a light cycle 360 image
    643      *
    644      * @return true if it is a light Cycle image that is full 360
    645      */
    646     public boolean queryLightCycle360() {
    647         InputStream is = null;
    648         try {
    649             is = mContext.getContentResolver().openInputStream(getUri());
    650             XMPMeta meta = XmpUtilHelper.extractXMPMeta(is);
    651             if (meta == null) {
    652                 return false;
    653             }
    654             String name = meta.getPacketHeader();
    655             String namespace = "http://ns.google.com/photos/1.0/panorama/";
    656             String cropWidthName = "GPano:CroppedAreaImageWidthPixels";
    657             String fullWidthName = "GPano:FullPanoWidthPixels";
    658 
    659             if (!meta.doesPropertyExist(namespace, cropWidthName)) {
    660                 return false;
    661             }
    662             if (!meta.doesPropertyExist(namespace, fullWidthName)) {
    663                 return false;
    664             }
    665 
    666             Integer cropValue = meta.getPropertyInteger(namespace, cropWidthName);
    667             Integer fullValue = meta.getPropertyInteger(namespace, fullWidthName);
    668 
    669             // Definition of a 360:
    670             // GFullPanoWidthPixels == CroppedAreaImageWidthPixels
    671             if (cropValue != null && fullValue != null) {
    672                 return cropValue.equals(fullValue);
    673             }
    674 
    675             return false;
    676         } catch (FileNotFoundException e) {
    677             return false;
    678         } catch (XMPException e) {
    679             return false;
    680         } finally {
    681             Utils.closeSilently(is);
    682         }
    683     }
    684 }
    685