OSDN Git Service

Merge "Add api check for FLAG_SECURE." into gb-ub-photos-arches
authorWu-Cheng Li <wuchengli@google.com>
Fri, 31 Aug 2012 06:26:53 +0000 (23:26 -0700)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Fri, 31 Aug 2012 06:26:54 +0000 (23:26 -0700)
46 files changed:
res/layout/simple_action_bar.xml [deleted file]
res/values-am/strings.xml
res/values-be/strings.xml
res/values-ca/strings.xml
res/values-da/strings.xml
res/values-de/strings.xml
res/values-el/strings.xml
res/values-en-rGB/strings.xml
res/values-fr/strings.xml
res/values-it/strings.xml
res/values-iw/strings.xml
res/values-nb/strings.xml
res/values-nl/strings.xml
res/values-pl/strings.xml
res/values-pt-rPT/strings.xml
res/values-sv/strings.xml
res/values-sw/strings.xml
res/values-zu/strings.xml
src/com/android/gallery3d/app/AlbumPage.java
src/com/android/gallery3d/app/AlbumSetPage.java
src/com/android/gallery3d/app/CropImage.java
src/com/android/gallery3d/app/ManageCachePage.java
src/com/android/gallery3d/app/MoviePlayer.java
src/com/android/gallery3d/app/PhotoPage.java
src/com/android/gallery3d/data/LocalAlbumSet.java
src/com/android/gallery3d/data/MtpDevice.java
src/com/android/gallery3d/data/MtpImage.java
src/com/android/gallery3d/data/SizeClustering.java
src/com/android/gallery3d/data/UriSource.java
src/com/android/gallery3d/exif/ExifData.java
src/com/android/gallery3d/exif/ExifOutputStream.java [new file with mode: 0644]
src/com/android/gallery3d/exif/ExifParser.java
src/com/android/gallery3d/exif/ExifTag.java
src/com/android/gallery3d/exif/IfdData.java
src/com/android/gallery3d/exif/OrderedDataOutputStream.java [new file with mode: 0644]
src/com/android/gallery3d/exif/Rational.java
src/com/android/gallery3d/exif/Util.java [new file with mode: 0644]
src/com/android/gallery3d/ui/MenuExecutor.java
src/com/android/gallery3d/ui/Paper.java
src/com/android/gallery3d/ui/PhotoView.java
src/com/android/gallery3d/ui/PositionController.java
src/com/android/gallery3d/util/GalleryUtils.java
tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java [new file with mode: 0644]
tests/src/com/android/gallery3d/exif/ExifParserTest.java
tests/src/com/android/gallery3d/exif/ExifReaderTest.java
tests/src/com/android/gallery3d/exif/ExifTestRunner.java

diff --git a/res/layout/simple_action_bar.xml b/res/layout/simple_action_bar.xml
deleted file mode 100644 (file)
index be6a060..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/navigation_bar"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="horizontal">
-    <Button android:id="@+id/menu_button"
-            android:divider="?android:attr/listDividerAlertDialog"
-            style="?android:attr/borderlessButtonStyle"
-            android:singleLine="true"
-            android:gravity="left|center_vertical"
-            android:paddingRight="25dip"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent" />
-</LinearLayout>
index d629bcb..128e7bf 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"መሸጎጫ ጥየቃዎች ሂደት"</string>
     <string name="caching_label" msgid="4521059045896269095">"በመሸጎጥ ላይ..."</string>
     <string name="crop_action" msgid="3427470284074377001">"ከርክም"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"አሳጥር"</string>
     <string name="set_as" msgid="3636764710790507868">"እንደ"</string>
     <string name="video_err" msgid="7003051631792271009">"ቪዲዮ ማጫወት አይቻልም።"</string>
     <string name="group_by_location" msgid="316641628989023253">"በስፍራ"</string>
index 1d0c90d..efb9298 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Запыты на кэшаванне працэсу"</string>
     <string name="caching_label" msgid="4521059045896269095">"Кэшаванне..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Абрэзаць"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Рамка"</string>
     <string name="set_as" msgid="3636764710790507868">"Ўсталяваць у якасці"</string>
     <string name="video_err" msgid="7003051631792271009">"Не атрымліваецца прайграць відэа."</string>
     <string name="group_by_location" msgid="316641628989023253">"Па месцазнаходжанні"</string>
index 419cf98..3dcac36 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"S\'estan processant les sol·licituds de memòria cau"</string>
     <string name="caching_label" msgid="4521059045896269095">"S\'està desant a la memòria cau..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Retalla"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Retalla"</string>
     <string name="set_as" msgid="3636764710790507868">"Defineix com a"</string>
     <string name="video_err" msgid="7003051631792271009">"No es pot reproduir el vídeo."</string>
     <string name="group_by_location" msgid="316641628989023253">"Per ubicació"</string>
index ae9ab1d..1706589 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Håndterer anmodninger om cachelagring"</string>
     <string name="caching_label" msgid="4521059045896269095">"Cachelagrer..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Beskær"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Beskær"</string>
     <string name="set_as" msgid="3636764710790507868">"Indstil som"</string>
     <string name="video_err" msgid="7003051631792271009">"Videoen kan ikke afspilles."</string>
     <string name="group_by_location" msgid="316641628989023253">"Efter placering"</string>
index 4dcd182..ec36413 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Caching-Anfragen werden verarbeitet."</string>
     <string name="caching_label" msgid="4521059045896269095">"Caching läuft..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Zuschneiden"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Zuschneiden"</string>
     <string name="set_as" msgid="3636764710790507868">"Festlegen als"</string>
     <string name="video_err" msgid="7003051631792271009">"Video kann nicht wiedergegeben werden."</string>
     <string name="group_by_location" msgid="316641628989023253">"Nach Standort"</string>
index 44bc364..5754385 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Επεξεργασία αιτημάτων προσωρινής αποθήκευσης"</string>
     <string name="caching_label" msgid="4521059045896269095">"Προσωρ. αποθ..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Περικοπή"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Περικοπή"</string>
     <string name="set_as" msgid="3636764710790507868">"Ορισμός ως"</string>
     <string name="video_err" msgid="7003051631792271009">"Δεν είναι δυνατή η αναπαραγωγή βίντεο."</string>
     <string name="group_by_location" msgid="316641628989023253">"Κατά τοποθεσία"</string>
index 4798413..3b25782 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Process Caching Requests"</string>
     <string name="caching_label" msgid="4521059045896269095">"Caching..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Crop"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Trim"</string>
     <string name="set_as" msgid="3636764710790507868">"Set as"</string>
     <string name="video_err" msgid="7003051631792271009">"Cannot play video"</string>
     <string name="group_by_location" msgid="316641628989023253">"By location"</string>
index e04fc32..159d749 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Demandes de mise en cache en cours de traitement"</string>
     <string name="caching_label" msgid="4521059045896269095">"Mise en cache…"</string>
     <string name="crop_action" msgid="3427470284074377001">"Rogner"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Rogner"</string>
     <string name="set_as" msgid="3636764710790507868">"Définir comme"</string>
     <string name="video_err" msgid="7003051631792271009">"Impossible de lire la vidéo."</string>
     <string name="group_by_location" msgid="316641628989023253">"Par emplacement"</string>
index 787c25f..94901a8 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Elaborazione richieste memorizzazione cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"Memorizzazione..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Ritaglia"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Ritaglia"</string>
     <string name="set_as" msgid="3636764710790507868">"Imposta come"</string>
     <string name="video_err" msgid="7003051631792271009">"Impossibile riprodurre il video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Per luogo"</string>
index f6d8445..da4ab77 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"מעבד בקשות העברה למטמון"</string>
     <string name="caching_label" msgid="4521059045896269095">"מעביר למטמון..."</string>
     <string name="crop_action" msgid="3427470284074377001">"חתוך"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"חתוך"</string>
     <string name="set_as" msgid="3636764710790507868">"הגדר בתור"</string>
     <string name="video_err" msgid="7003051631792271009">"לא ניתן להפעיל את סרטון הווידאו."</string>
     <string name="group_by_location" msgid="316641628989023253">"לפי מיקום"</string>
index 8872e09..82d3452 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Behandler forespørsler om bufring"</string>
     <string name="caching_label" msgid="4521059045896269095">"Henter …"</string>
     <string name="crop_action" msgid="3427470284074377001">"Beskjær"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Beskjær"</string>
     <string name="set_as" msgid="3636764710790507868">"Angi som"</string>
     <string name="video_err" msgid="7003051631792271009">"Kan ikke spille av video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Etter posisjon"</string>
index 6327709..4303642 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Cacheverzoeken verwerken"</string>
     <string name="caching_label" msgid="4521059045896269095">"In cache opslaan..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Bijsnijden"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Bijsnijden"</string>
     <string name="set_as" msgid="3636764710790507868">"Instellen als"</string>
     <string name="video_err" msgid="7003051631792271009">"Video kan niet worden afgespeeld."</string>
     <string name="group_by_location" msgid="316641628989023253">"Op locatie"</string>
index c52db52..287d3c7 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Przetwarzanie żądań dotyczących buforowania"</string>
     <string name="caching_label" msgid="4521059045896269095">"Buforowanie…"</string>
     <string name="crop_action" msgid="3427470284074377001">"Przytnij"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Przytnij"</string>
     <string name="set_as" msgid="3636764710790507868">"Ustaw jako"</string>
     <string name="video_err" msgid="7003051631792271009">"Nie można odtworzyć filmu."</string>
     <string name="group_by_location" msgid="316641628989023253">"Według lokalizacji"</string>
index f857c14..77241c7 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"A processar pedidos de colocação em cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"A col. cache..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Recortar"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Recortar"</string>
     <string name="set_as" msgid="3636764710790507868">"Definir como"</string>
     <string name="video_err" msgid="7003051631792271009">"Não é possível reproduzir vídeo."</string>
     <string name="group_by_location" msgid="316641628989023253">"Por localização"</string>
index 1e759ad..744223f 100644 (file)
@@ -87,8 +87,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Begäran om cachelagring bearbetas"</string>
     <string name="caching_label" msgid="4521059045896269095">"Cachelagrar ..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Beskär"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Beskär"</string>
     <string name="set_as" msgid="3636764710790507868">"Använd som"</string>
     <string name="video_err" msgid="7003051631792271009">"Det går inte att spela upp videon."</string>
     <string name="group_by_location" msgid="316641628989023253">"Efter plats"</string>
index d95f55f..8e7b0e5 100644 (file)
@@ -90,8 +90,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Maombi ya kuakibisha michakato"</string>
     <string name="caching_label" msgid="4521059045896269095">"Inaakibisha..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Punguza"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Punguza"</string>
     <string name="set_as" msgid="3636764710790507868">"Weka kama"</string>
     <string name="video_err" msgid="7003051631792271009">"Video haiwezi kuchezwa."</string>
     <string name="group_by_location" msgid="316641628989023253">"Kwa mahali"</string>
index bae840e..1830b9b 100644 (file)
@@ -89,8 +89,7 @@
     <string name="process_caching_requests" msgid="8722939570307386071">"Ilungisa izicelo zokufaka kwinqolobane"</string>
     <string name="caching_label" msgid="4521059045896269095">"Ukulondoloza isikhashana..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Khropha"</string>
-    <!-- no translation found for trim_action (703098114452883524) -->
-    <skip />
+    <string name="trim_action" msgid="703098114452883524">"Lungisa"</string>
     <string name="set_as" msgid="3636764710790507868">"Hlela njenge"</string>
     <string name="video_err" msgid="7003051631792271009">"Ayikwazi ukudlala ividiyo"</string>
     <string name="group_by_location" msgid="316641628989023253">"Ngendawo"</string>
index f106392..6c5c695 100644 (file)
@@ -22,8 +22,6 @@ import android.content.Intent;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
 import android.os.Vibrator;
 import android.provider.MediaStore;
 import android.widget.Toast;
@@ -45,7 +43,6 @@ import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
 import com.android.gallery3d.ui.AlbumSlotRenderer;
 import com.android.gallery3d.ui.DetailsHelper;
 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
-import com.android.gallery3d.ui.FadeTexture;
 import com.android.gallery3d.ui.GLCanvas;
 import com.android.gallery3d.ui.GLRoot;
 import com.android.gallery3d.ui.GLView;
@@ -55,7 +52,6 @@ import com.android.gallery3d.ui.RawTexture;
 import com.android.gallery3d.ui.RelativePosition;
 import com.android.gallery3d.ui.SelectionManager;
 import com.android.gallery3d.ui.SlotView;
-import com.android.gallery3d.ui.SynchronizedHandler;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.MediaSetUtils;
@@ -105,8 +101,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster
     private MediaSet mMediaSet;
     private boolean mShowDetails;
     private float mUserDistance; // in pixel
-    private Handler mHandler;
-
     private Future<Integer> mSyncTask = null;
 
     private int mLoadingBits = 0;
@@ -385,19 +379,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster
                 mSlotView.startScatteringAnimation(mOpenCenter);
             }
         }
-
-        mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
-            @Override
-            public void handleMessage(Message message) {
-                switch (message.what) {
-                    case MSG_PICK_PHOTO: {
-                        pickPhoto(message.arg1);
-                        break;
-                    }
-                    default: throw new AssertionError(message.what);
-                }
-            }
-        };
     }
 
     @Override
index c7046be..c34e83e 100644 (file)
@@ -20,9 +20,8 @@ import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.DialogInterface.OnCancelListener;
 import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -312,7 +311,7 @@ public class AlbumSetPage extends ActivityState implements
                             R.string.empty_album, Toast.LENGTH_LONG).show();
                     mActivity.getStateManager().finishState(this);
                 } else {
-                    emptyGalleryPrompt((Activity) mActivity);
+                    emptyGalleryPrompt(mActivity);
                 }
             } else if (mEmptyGalleryPrompt != null) {
                 mEmptyGalleryPrompt = null;
index c962149..70d0873 100644 (file)
@@ -46,16 +46,21 @@ import com.actionbarsherlock.app.ActionBar;
 import com.actionbarsherlock.view.Menu;
 import com.actionbarsherlock.view.MenuItem;
 import com.actionbarsherlock.view.Window;
+import com.android.camera.Util;
 import com.android.gallery3d.R;
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.ExifTags;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.LocalImage;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.exif.ExifData;
+import com.android.gallery3d.exif.ExifOutputStream;
+import com.android.gallery3d.exif.ExifReader;
+import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.IfdId;
 import com.android.gallery3d.picasasource.PicasaSource;
 import com.android.gallery3d.ui.BitmapTileProvider;
 import com.android.gallery3d.ui.CropView;
@@ -72,6 +77,7 @@ import com.android.gallery3d.util.ThreadPool.Job;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -318,7 +324,7 @@ public class CropImage extends AbstractGalleryActivity {
     }
 
     private File saveMedia(
-            JobContext jc, Bitmap cropped, File directory, String filename) {
+            JobContext jc, Bitmap cropped, File directory, String filename, ExifData exifData) {
         // Try file-1.jpg, file-2.jpg, ... until we find a filename
         // which does not exist yet.
         File candidate = null;
@@ -344,8 +350,15 @@ public class CropImage extends AbstractGalleryActivity {
         try {
             FileOutputStream fos = new FileOutputStream(candidate);
             try {
-                saveBitmapToOutputStream(jc, cropped,
-                        convertExtensionToCompressFormat(fileExtension), fos);
+                if (exifData != null) {
+                    ExifOutputStream eos = new ExifOutputStream(fos);
+                    eos.setExifData(exifData);
+                    saveBitmapToOutputStream(jc, cropped,
+                            convertExtensionToCompressFormat(fileExtension), eos);
+                } else {
+                    saveBitmapToOutputStream(jc, cropped,
+                            convertExtensionToCompressFormat(fileExtension), fos);
+                }
             } finally {
                 fos.close();
             }
@@ -364,6 +377,27 @@ public class CropImage extends AbstractGalleryActivity {
         return candidate;
     }
 
+    private ExifData getExifData(String path) {
+        FileInputStream is = null;
+        try {
+            is = new FileInputStream(path);
+            ExifReader reader = new ExifReader();
+            ExifData data = reader.read(is);
+            return data;
+        } catch (Throwable t) {
+            Log.w(TAG, "Cannot read EXIF data", t);
+            return null;
+        } finally {
+            Util.closeSilently(is);
+        }
+    }
+
+    private void changeExifImageSizeTag(ExifData data, int width, int height) {
+        data.getIfdData(IfdId.TYPE_IFD_0).getTag(ExifTag.TIFF_TAG.TAG_IMAGE_WIDTH).setValue(width);
+        data.getIfdData(IfdId.TYPE_IFD_0).getTag(ExifTag.TIFF_TAG.TAG_IMAGE_HEIGHT).setValue(
+                height);
+    }
+
     private Uri saveToMediaProvider(JobContext jc, Bitmap cropped) {
         if (PicasaSource.isPicasaImage(mMediaItem)) {
             return savePicasaImage(jc, cropped);
@@ -391,7 +425,7 @@ public class CropImage extends AbstractGalleryActivity {
         String filename = PicasaSource.getImageTitle(mMediaItem);
         int pos = filename.lastIndexOf('.');
         if (pos >= 0) filename = filename.substring(0, pos);
-        File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename);
+        File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, null);
         if (output == null) return null;
 
         copyExif(mMediaItem, output.getAbsolutePath(), cropped.getWidth(), cropped.getHeight());
@@ -428,11 +462,17 @@ public class CropImage extends AbstractGalleryActivity {
         String filename = oldPath.getName();
         int pos = filename.lastIndexOf('.');
         if (pos >= 0) filename = filename.substring(0, pos);
-        File output = saveMedia(jc, cropped, directory, filename);
-        if (output == null) return null;
+        File output = null;
 
-        copyExif(oldPath.getAbsolutePath(), output.getAbsolutePath(),
-                cropped.getWidth(), cropped.getHeight());
+        ExifData exifData = null;
+        if (convertExtensionToCompressFormat(getFileExtension()) == CompressFormat.JPEG) {
+            exifData = getExifData(oldPath.getAbsolutePath());
+            if (exifData != null) {
+                changeExifImageSizeTag(exifData, cropped.getWidth(), cropped.getHeight());
+            }
+        }
+        output = saveMedia(jc, cropped, directory, filename, exifData);
+        if (output == null) return null;
 
         long now = System.currentTimeMillis() / 1000;
         ContentValues values = new ContentValues();
@@ -465,7 +505,7 @@ public class CropImage extends AbstractGalleryActivity {
         String filename = new SimpleDateFormat(TIME_STAMP_NAME).
                 format(new Date(now));
 
-        File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename);
+        File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, null);
         if (output == null) return null;
 
         ContentValues values = new ContentValues();
@@ -496,19 +536,23 @@ public class CropImage extends AbstractGalleryActivity {
                 }
             });
         try {
-            bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os);
+            bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, ios);
             return !jc.isCancelled();
         } finally {
             jc.setCancelListener(null);
-            Utils.closeSilently(os);
+            Utils.closeSilently(ios);
         }
     }
 
     private boolean saveBitmapToUri(JobContext jc, Bitmap bitmap, Uri uri) {
         try {
-            return saveBitmapToOutputStream(jc, bitmap,
-                    convertExtensionToCompressFormat(getFileExtension()),
-                    getContentResolver().openOutputStream(uri));
+            OutputStream out = getContentResolver().openOutputStream(uri);
+            try {
+                return saveBitmapToOutputStream(jc, bitmap,
+                        convertExtensionToCompressFormat(getFileExtension()), out);
+            } finally {
+                Utils.closeSilently(out);
+            }
         } catch (FileNotFoundException e) {
             Log.w(TAG, "cannot write output", e);
         }
@@ -957,23 +1001,6 @@ public class CropImage extends AbstractGalleryActivity {
         }
     }
 
-    private static final String[] EXIF_TAGS = {
-            ExifInterface.TAG_DATETIME,
-            ExifInterface.TAG_MAKE,
-            ExifInterface.TAG_MODEL,
-            ExifInterface.TAG_FLASH,
-            ExifInterface.TAG_GPS_LATITUDE,
-            ExifInterface.TAG_GPS_LONGITUDE,
-            ExifInterface.TAG_GPS_LATITUDE_REF,
-            ExifInterface.TAG_GPS_LONGITUDE_REF,
-            ExifInterface.TAG_GPS_ALTITUDE,
-            ExifInterface.TAG_GPS_ALTITUDE_REF,
-            ExifInterface.TAG_GPS_TIMESTAMP,
-            ExifInterface.TAG_GPS_DATESTAMP,
-            ExifInterface.TAG_WHITE_BALANCE,
-            ExifInterface.TAG_FOCAL_LENGTH,
-            ExifInterface.TAG_GPS_PROCESSING_METHOD};
-
     private static void copyExif(MediaItem item, String destination, int newWidth, int newHeight) {
         try {
             ExifInterface newExif = new ExifInterface(destination);
@@ -986,60 +1013,4 @@ public class CropImage extends AbstractGalleryActivity {
             Log.w(TAG, "cannot copy exif: " + item, t);
         }
     }
-
-    private static void copyExif(String source, String destination, int newWidth, int newHeight) {
-        try {
-            ExifInterface oldExif = new ExifInterface(source);
-            ExifInterface newExif = new ExifInterface(destination);
-
-            newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(newWidth));
-            newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(newHeight));
-            newExif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(0));
-
-            for (String tag : EXIF_TAGS) {
-                String value = oldExif.getAttribute(tag);
-                if (value != null) {
-                    newExif.setAttribute(tag, value);
-                }
-            }
-
-            // Handle some special values here
-            String value = oldExif.getAttribute(ExifTags.TAG_APERTURE);
-            if (value != null) {
-                try {
-                    float aperture = Float.parseFloat(value);
-                    newExif.setAttribute(ExifTags.TAG_APERTURE,
-                            String.valueOf((int) (aperture * 10 + 0.5f)) + "/10");
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "cannot parse aperture: " + value);
-                }
-            }
-
-            // TODO: The code is broken, need to fix the JHEAD lib
-            /*
-            value = oldExif.getAttribute(ExifTags.TAG_EXPOSURE_TIME);
-            if (value != null) {
-                try {
-                    double exposure = Double.parseDouble(value);
-                    testToRational("test exposure", exposure);
-                    newExif.setAttribute(ExifTags.TAG_EXPOSURE_TIME, value);
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "cannot parse exposure time: " + value);
-                }
-            }
-
-            value = oldExif.getAttribute(ExifTags.TAG_ISO);
-            if (value != null) {
-                try {
-                    int iso = Integer.parseInt(value);
-                    newExif.setAttribute(ExifTags.TAG_ISO, String.valueOf(iso) + "/1");
-                } catch (NumberFormatException e) {
-                    Log.w(TAG, "cannot parse exposure time: " + value);
-                }
-            }*/
-            newExif.saveAttributes();
-        } catch (Throwable t) {
-            Log.w(TAG, "cannot copy exif: " + source, t);
-        }
-    }
 }
index 8f95112..37a9762 100644 (file)
@@ -112,11 +112,10 @@ public class ManageCachePage extends ActivityState implements
             mLayoutReady = false;
 
             mEyePosition.resetPosition();
-            Activity activity = (Activity) mActivity;
             int slotViewTop = mActivity.getGalleryActionBar().getHeight();
             int slotViewBottom = bottom - top;
 
-            View footer = activity.findViewById(R.id.footer);
+            View footer = mActivity.findViewById(R.id.footer);
             if (footer != null) {
                 int location[] = {0, 0};
                 footer.getLocationOnScreen(location);
@@ -284,7 +283,7 @@ public class ManageCachePage extends ActivityState implements
     }
 
     private void initializeViews() {
-        Activity activity = (Activity) mActivity;
+        Activity activity = mActivity;
 
         mSelectionManager = new SelectionManager(mActivity, true);
         mSelectionManager.setSelectionListener(this);
@@ -315,7 +314,7 @@ public class ManageCachePage extends ActivityState implements
     }
 
     private void initializeFooterViews() {
-        Activity activity = (Activity) mActivity;
+        Activity activity = mActivity;
 
         LayoutInflater inflater = activity.getLayoutInflater();
         mFooterContent = inflater.inflate(R.layout.manage_offline_bar, null);
@@ -347,7 +346,7 @@ public class ManageCachePage extends ActivityState implements
 
     private void showToast() {
         if (mAlbumCountToMakeAvailableOffline > 0) {
-            Activity activity = (Activity) mActivity;
+            Activity activity = mActivity;
             Toast.makeText(activity, activity.getResources().getQuantityString(
                     R.plurals.make_albums_available_offline,
                     mAlbumCountToMakeAvailableOffline),
@@ -356,7 +355,7 @@ public class ManageCachePage extends ActivityState implements
     }
 
     private void showToastForLocalAlbum() {
-        Activity activity = (Activity) mActivity;
+        Activity activity = mActivity;
         Toast.makeText(activity, activity.getResources().getString(
             R.string.try_to_set_local_album_available_offline),
             Toast.LENGTH_SHORT).show();
@@ -371,7 +370,7 @@ public class ManageCachePage extends ActivityState implements
         long expectedBytes = mCacheStorageInfo.getExpectedUsedBytes();
         long freeBytes = mCacheStorageInfo.getFreeBytes();
 
-        Activity activity = (Activity) mActivity;
+        Activity activity = mActivity;
         if (totalBytes == 0) {
             progressBar.setProgress(0);
             progressBar.setSecondaryProgress(0);
index f2dc9ad..0cf361c 100644 (file)
@@ -217,6 +217,7 @@ public class MoviePlayer implements
         });
     }
 
+    @SuppressWarnings("deprecation")
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     private void showSystemUi(boolean visible) {
         if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) return;
@@ -225,6 +226,7 @@ public class MoviePlayer implements
                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
         if (!visible) {
+            // We used the deprecated "STATUS_BAR_HIDDEN" for unbundling
             flag |= View.STATUS_BAR_HIDDEN | View.SYSTEM_UI_FLAG_FULLSCREEN
                     | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
         }
index d101aac..3354c91 100644 (file)
@@ -147,8 +147,6 @@ public class PhotoPage extends ActivityState implements
 
     private NfcAdapter mNfcAdapter;
 
-    private Menu mActionBarMenu;
-
     private final MyMenuVisibilityListener mMenuVisibilityListener =
             new MyMenuVisibilityListener();
 
index 5d01c06..d737ca8 100644 (file)
@@ -37,12 +37,13 @@ import java.util.Comparator;
 // The path should be "/local/image", "local/video" or "/local/all"
 public class LocalAlbumSet extends MediaSet
         implements FutureListener<ArrayList<MediaSet>> {
+    @SuppressWarnings("unused")
+    private static final String TAG = "LocalAlbumSet";
+
     public static final Path PATH_ALL = Path.fromString("/local/all");
     public static final Path PATH_IMAGE = Path.fromString("/local/image");
     public static final Path PATH_VIDEO = Path.fromString("/local/video");
 
-    private static final String TAG = "LocalAlbumSet";
-
     private static final Uri[] mWatchUris =
         {Images.Media.EXTERNAL_CONTENT_URI, Video.Media.EXTERNAL_CONTENT_URI};
 
index b233b3c..999f554 100644 (file)
@@ -37,7 +37,6 @@ public class MtpDevice extends MediaSet {
     private final GalleryApp mApplication;
     private final int mDeviceId;
     private final String mDeviceName;
-    private final DataManager mDataManager;
     private final MtpContext mMtpContext;
     private final String mName;
     private final ChangeNotifier mNotifier;
@@ -50,7 +49,6 @@ public class MtpDevice extends MediaSet {
         mApplication = application;
         mDeviceId = deviceId;
         mDeviceName = UsbDevice.getDeviceName(deviceId);
-        mDataManager = application.getDataManager();
         mMtpContext = mtpContext;
         mName = name;
         mNotifier = new ChangeNotifier(this, Uri.parse("mtp://"), application);
index 801a1d2..0abb198 100644 (file)
@@ -28,7 +28,6 @@ import android.util.Log;
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.provider.GalleryProvider;
-import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.ThreadPool.Job;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
@@ -44,7 +43,6 @@ public class MtpImage extends MediaItem {
     private int mObjectSize;
     private long mDateTaken;
     private String mFileName;
-    private final ThreadPool mThreadPool;
     private final MtpContext mMtpContext;
     private final MtpObjectInfo mObjInfo;
     private final int mImageWidth;
@@ -63,7 +61,6 @@ public class MtpImage extends MediaItem {
         mFileName = objInfo.getName();
         mImageWidth = objInfo.getImagePixWidth();
         mImageHeight = objInfo.getImagePixHeight();
-        mThreadPool = application.getThreadPool();
         mMtpContext = mtpContext;
     }
 
index 0bde55e..b809c84 100644 (file)
@@ -24,6 +24,7 @@ import com.android.gallery3d.R;
 import java.util.ArrayList;
 
 public class SizeClustering extends Clustering {
+    @SuppressWarnings("unused")
     private static final String TAG = "SizeClustering";
 
     private Context mContext;
@@ -48,10 +49,11 @@ public class SizeClustering extends Clustering {
         mContext = context;
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public void run(MediaSet baseSet) {
-        final ArrayList<Path>[] group =
-                (ArrayList<Path>[]) new ArrayList[SIZE_LEVELS.length];
+        @SuppressWarnings("unchecked")
+        final ArrayList<Path>[] group = new ArrayList[SIZE_LEVELS.length];
         baseSet.enumerateTotalMediaItems(new MediaSet.ItemConsumer() {
             @Override
             public void consume(int index, MediaItem item) {
@@ -80,7 +82,7 @@ public class SizeClustering extends Clustering {
             }
         }
 
-        mClusters = (ArrayList<Path>[]) new ArrayList[count];
+        mClusters = new ArrayList[count];
         mNames = new String[count];
         mMinSizes = new long[count];
 
index d37c51d..f66bacd 100644 (file)
@@ -22,6 +22,7 @@ import android.webkit.MimeTypeMap;
 
 import com.android.gallery3d.app.GalleryApp;
 
+import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
 
@@ -30,6 +31,7 @@ class UriSource extends MediaSource {
     private static final String TAG = "UriSource";
     private static final String IMAGE_TYPE_PREFIX = "image/";
     private static final String IMAGE_TYPE_ANY = "image/*";
+    private static final String CHARSET_UTF_8 = "utf-8";
 
     private GalleryApp mApplication;
 
@@ -44,9 +46,13 @@ class UriSource extends MediaSource {
         if (segment.length != 3) {
             throw new RuntimeException("bad path: " + path);
         }
-        String uri = URLDecoder.decode(segment[1]);
-        String type = URLDecoder.decode(segment[2]);
-        return new UriImage(mApplication, path, Uri.parse(uri), type);
+        try {
+            String uri = URLDecoder.decode(segment[1], CHARSET_UTF_8);
+            String type = URLDecoder.decode(segment[2], CHARSET_UTF_8);
+            return new UriImage(mApplication, path, Uri.parse(uri), type);
+        } catch (UnsupportedEncodingException e) {
+            throw new AssertionError(e);
+        }
     }
 
     private String getMimeType(Uri uri) {
@@ -75,8 +81,13 @@ class UriSource extends MediaSource {
         }
 
         if (type.startsWith(IMAGE_TYPE_PREFIX)) {
-            return Path.fromString("/uri/" + URLEncoder.encode(uri.toString())
-                    + "/" +URLEncoder.encode(type));
+            try {
+                return Path.fromString("/uri/"
+                        + URLEncoder.encode(uri.toString(), CHARSET_UTF_8)
+                        + "/" +URLEncoder.encode(type, CHARSET_UTF_8));
+            } catch (UnsupportedEncodingException e) {
+                throw new AssertionError(e);
+            }
         }
         // We have no clues that it is an image
         return null;
index 14d4822..776f77a 100644 (file)
@@ -18,6 +18,7 @@ package com.android.gallery3d.exif;
 
 import java.nio.ByteOrder;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  *  This class stores the EXIF header in IFDs according to the JPEG specification.
@@ -114,4 +115,31 @@ public class ExifData {
     public ByteOrder getByteOrder() {
         return mByteOrder;
     }
+
+    /**
+     * Returns true if this header contains compressed strip of thumbnail.
+     */
+    public boolean hasUncompressedStrip() {
+        return mStripBytes.size() != 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof ExifData) {
+            ExifData data = (ExifData) obj;
+            if (data.mByteOrder != mByteOrder
+                    || !Arrays.equals(data.mThumbnail, mThumbnail)
+                    || data.mStripBytes.size() != mStripBytes.size()) return false;
+
+            for (int i = 0; i < mStripBytes.size(); i++) {
+                if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) return false;
+            }
+
+            for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+                if (!Util.equals(data.getIfdData(i), getIfdData(i))) return false;
+            }
+            return true;
+        }
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/ExifOutputStream.java b/src/com/android/gallery3d/exif/ExifOutputStream.java
new file mode 100644 (file)
index 0000000..1c0baf2
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class ExifOutputStream extends FilterOutputStream {
+    private static final String TAG = "ExifOutputStream";
+
+    private static final int STATE_SOI = 0;
+    private static final int STATE_APP1 = 1;
+    private static final int STATE_JPEG_DATA = 2;
+
+    private static final short SOI = (short) 0xFFD8;
+    private static final short APP0 = (short) 0xFFE0;
+    private static final short APP1 = (short) 0xFFE1;
+    private static final int EXIF_HEADER = 0x45786966;
+    private static final short TIFF_HEADER = 0x002A;
+    private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+    private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+    private static final short TAG_SIZE = 12;
+    private static final short TIFF_HEADER_SIZE = 8;
+
+    private ExifData mExifData;
+    private int mState;
+    private int mByteToSkip;
+    private int mByteToCopy;
+    private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+
+    public ExifOutputStream(OutputStream ou) {
+        super(ou);
+    }
+
+    public void setExifData(ExifData exifData) {
+        mExifData = exifData;
+    }
+
+    public ExifData getExifData() {
+        return mExifData;
+    }
+
+    private int requestByteToBuffer(int requestByteCount, byte[] buffer
+            , int offset, int length) {
+        int byteNeeded = requestByteCount - mBuffer.position();
+        int byteToRead = length > byteNeeded ? byteNeeded : length;
+        mBuffer.put(buffer, offset, byteToRead);
+        return byteToRead;
+    }
+
+    @Override
+    public void write(byte[] buffer, int offset, int length) throws IOException {
+        while((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
+                && length > 0) {
+            if (mByteToSkip > 0) {
+                int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
+                length -= byteToProcess;
+                mByteToSkip -= byteToProcess;
+                offset += byteToProcess;
+            }
+            if (mByteToCopy > 0) {
+                int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
+                out.write(buffer, offset, byteToProcess);
+                length -= byteToProcess;
+                mByteToCopy -= byteToProcess;
+                offset += byteToProcess;
+            }
+            switch (mState) {
+                case STATE_SOI:
+                    int byteRead = requestByteToBuffer(2, buffer, offset, length);
+                    offset += byteRead;
+                    length -= byteRead;
+                    if (mBuffer.position() < 2) return;
+                    mBuffer.rewind();
+                    assert(mBuffer.getShort() == SOI);
+                    out.write(mBuffer.array(), 0 ,2);
+                    mState = STATE_APP1;
+                    mBuffer.rewind();
+                    break;
+                case STATE_APP1:
+                    byteRead = requestByteToBuffer(4, buffer, offset, length);
+                    offset += byteRead;
+                    length -= byteRead;
+                    if (mBuffer.position() < 4) return;
+                    mBuffer.rewind();
+                    if (mBuffer.getShort() == APP0) {
+                        out.write(mBuffer.array(), 0 ,4);
+                        mByteToCopy = (mBuffer.getShort() & 0xff) - 2;
+                    } else if (mBuffer.getShort() == APP1) {
+                        writeExifData();
+                        mByteToSkip = (mBuffer.getShort() & 0xff) - 2;
+                        mState = STATE_JPEG_DATA;
+                    } else {
+                        writeExifData();
+                        out.write(mBuffer.array(), 0, 4);
+                        mState = STATE_JPEG_DATA;
+                    }
+                    mBuffer.rewind();
+                    break;
+            }
+        }
+        if (length > 0) {
+            out.write(buffer, offset, length);
+        }
+    }
+
+    @Override
+    public void write(int oneByte) throws IOException {
+        byte[] buf = new byte[] {(byte) (0xff & oneByte)};
+        write(buf);
+    }
+
+    @Override
+    public void write(byte[] buffer) throws IOException {
+        write(buffer, 0, buffer.length);
+    }
+
+    private void writeExifData() throws IOException {
+        createRequiredIfdAndTag();
+        int exifSize = calculateAllOffset();
+        OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
+        dataOutputStream.writeShort(APP1);
+        dataOutputStream.writeShort((short) (exifSize + 8));
+        dataOutputStream.writeInt(EXIF_HEADER);
+        dataOutputStream.writeShort((short) 0x0000);
+        if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+            dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
+        } else {
+            dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
+        }
+        dataOutputStream.setByteOrder(mExifData.getByteOrder());
+        dataOutputStream.writeShort(TIFF_HEADER);
+        dataOutputStream.writeInt(8);
+        writeAllTag(dataOutputStream);
+        writeThumbnail(dataOutputStream);
+    }
+
+    private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
+        if (mExifData.hasCompressedThumbnail()) {
+            dataOutputStream.write(mExifData.getCompressedThumbnail());
+        } else if (mExifData.hasUncompressedStrip()) {
+            for (int i = 0; i < mExifData.getStripCount(); i++) {
+                dataOutputStream.write(mExifData.getStrip(i));
+            }
+        }
+    }
+
+    private void writeAllTag(OrderedDataOutputStream dataOutputStream) throws IOException {
+        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
+        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
+        IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+        if (interoperabilityIfd != null) {
+            writeIfd(interoperabilityIfd, dataOutputStream);
+        }
+        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+        if (gpsIfd != null) {
+            writeIfd(gpsIfd, dataOutputStream);
+        }
+        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+        if (ifd1 != null) {
+            writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
+        }
+    }
+
+    private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
+            throws IOException {
+        ExifTag[] tags = ifd.getAllTags(new ExifTag[] {});
+        dataOutputStream.writeShort((short) tags.length);
+        for (ExifTag tag: tags) {
+            dataOutputStream.writeShort(tag.getTagId());
+            dataOutputStream.writeShort(tag.getDataType());
+            dataOutputStream.writeInt(tag.getComponentCount());
+            if (tag.getDataSize() > 4) {
+                dataOutputStream.writeInt(tag.getOffset());
+            } else {
+                writeTagValue(tag, dataOutputStream);
+                for (int i = 0; i < 4 - tag.getDataSize(); i++) {
+                    dataOutputStream.write(0);
+                }
+            }
+        }
+        dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
+        for (ExifTag tag: tags) {
+            if (tag.getDataSize() > 4) {
+                writeTagValue(tag, dataOutputStream);
+            }
+        }
+    }
+
+    private void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+            throws IOException {
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_ASCII:
+                dataOutputStream.write(tag.getString().getBytes());
+                int remain = tag.getComponentCount() - tag.getString().getBytes().length;
+                for (int i = 0; i < remain; i++) {
+                    dataOutputStream.write(0);
+                }
+                break;
+            case ExifTag.TYPE_INT:
+                for (int i = 0; i < tag.getComponentCount(); i++) {
+                    dataOutputStream.writeInt(tag.getInt(i));
+                }
+                break;
+            case ExifTag.TYPE_RATIONAL:
+            case ExifTag.TYPE_UNSIGNED_RATIONAL:
+                for (int i = 0; i < tag.getComponentCount(); i++) {
+                    dataOutputStream.writeRational(tag.getRational(i));
+                }
+                break;
+            case ExifTag.TYPE_UNDEFINED:
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+                byte[] buf = new byte[tag.getComponentCount()];
+                tag.getBytes(buf);
+                dataOutputStream.write(buf);
+                break;
+            case ExifTag.TYPE_UNSIGNED_INT:
+                for (int i = 0; i < tag.getComponentCount(); i++) {
+                    dataOutputStream.writeInt((int) tag.getUnsignedInt(i));
+                }
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT:
+                for (int i = 0; i < tag.getComponentCount(); i++) {
+                    dataOutputStream.writeShort((short) tag.getUnsignedShort(i));
+                }
+                break;
+        }
+    }
+
+    private int calculateOffsetOfIfd(IfdData ifd, int offset) {
+        offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
+        ExifTag[] tags = ifd.getAllTags(new ExifTag[] {});
+        for(ExifTag tag: tags) {
+            if (tag.getDataSize() > 4) {
+                tag.setOffset(offset);
+                offset += tag.getDataSize();
+            }
+        }
+        return offset;
+    }
+
+    private void createRequiredIfdAndTag() {
+        // IFD0 is required for all file
+        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+        if (ifd0 == null) {
+            ifd0 = new IfdData(IfdId.TYPE_IFD_0);
+            mExifData.addIfdData(ifd0);
+        }
+        ExifTag exifOffsetTag = new ExifTag(ExifTag.TIFF_TAG.TAG_EXIF_IFD
+                , ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_0);
+        ifd0.setTag(exifOffsetTag);
+
+        // Exif IFD is required for all file.
+        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+        if (exifIfd == null) {
+            exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
+            mExifData.addIfdData(exifIfd);
+        }
+
+        // GPS IFD
+        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+        if (gpsIfd != null) {
+            ExifTag gpsOffsetTag = new ExifTag(ExifTag.TIFF_TAG.TAG_GPS_IFD,
+                    ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_0);
+            ifd0.setTag(gpsOffsetTag);
+        }
+
+        // Interoperability IFD
+        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+        if (interIfd != null) {
+            ExifTag interOffsetTag = new ExifTag(ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD,
+                    ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_EXIF);
+            exifIfd.setTag(interOffsetTag);
+        }
+
+        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+
+        // thumbnail
+        if (mExifData.hasCompressedThumbnail()) {
+            if (ifd1 == null) {
+                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+                mExifData.addIfdData(ifd1);
+            }
+            ExifTag offsetTag = new ExifTag(ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT,
+                    ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_1);
+            ifd1.setTag(offsetTag);
+            ExifTag lengthTag = new ExifTag(ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+                    ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_1);
+            lengthTag.setValue(mExifData.getCompressedThumbnail().length);
+            ifd1.setTag(lengthTag);
+        } else if (mExifData.hasUncompressedStrip()){
+            if (ifd1 == null) {
+                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+                mExifData.addIfdData(ifd1);
+            }
+            int stripCount = mExifData.getStripCount();
+            ExifTag offsetTag = new ExifTag(ExifTag.TIFF_TAG.TAG_STRIP_OFFSETS,
+                    ExifTag.TYPE_UNSIGNED_INT, stripCount, IfdId.TYPE_IFD_1);
+            ExifTag lengthTag = new ExifTag(ExifTag.TIFF_TAG.TAG_STRIP_BYTE_COUNTS,
+                    ExifTag.TYPE_UNSIGNED_INT, stripCount, IfdId.TYPE_IFD_1);
+            long[] lengths = new long[stripCount];
+            for (int i = 0; i < mExifData.getStripCount(); i++) {
+                lengths[i] = mExifData.getStrip(i).length;
+            }
+            lengthTag.setValue(lengths);
+            ifd1.setTag(offsetTag);
+            ifd1.setTag(lengthTag);
+        }
+    }
+
+    private int calculateAllOffset() {
+        int offset = TIFF_HEADER_SIZE;
+        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+        offset = calculateOffsetOfIfd(ifd0, offset);
+        ifd0.getTag(ExifTag.TIFF_TAG.TAG_EXIF_IFD).setValue(offset);
+
+        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+        offset = calculateOffsetOfIfd(exifIfd, offset);
+
+        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+        if (interIfd != null) {
+            exifIfd.getTag(ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD).setValue(offset);
+            offset = calculateOffsetOfIfd(interIfd, offset);
+        }
+
+        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+        if (gpsIfd != null) {
+            ifd0.getTag(ExifTag.TIFF_TAG.TAG_GPS_IFD).setValue(offset);
+            offset = calculateOffsetOfIfd(gpsIfd, offset);
+        }
+
+        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+        if (ifd1 != null) {
+            ifd0.setOffsetToNextIfd(offset);
+            offset = calculateOffsetOfIfd(ifd1, offset);
+        }
+
+        // thumbnail
+        if (mExifData.hasCompressedThumbnail()) {
+            ifd1.getTag(ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT).setValue(offset);
+            offset += mExifData.getCompressedThumbnail().length;
+        } else if (mExifData.hasUncompressedStrip()){
+            int stripCount = mExifData.getStripCount();
+            long[] offsets = new long[stripCount];
+            for (int i = 0; i < mExifData.getStripCount(); i++) {
+                offsets[i] = offset;
+                offset += mExifData.getStrip(i).length;
+            }
+            ifd1.getTag(ExifTag.TIFF_TAG.TAG_STRIP_OFFSETS).setValue(offsets);
+        }
+        return offset;
+    }
+}
\ No newline at end of file
index 268c989..f6ecd77 100644 (file)
@@ -405,7 +405,7 @@ public class ExifParser {
      */
     public int getCompressedImageSize() {
         if (mJpegSizeTag == null) return 0;
-        return (int) mJpegSizeTag.getUnsignedInt();
+        return (int) mJpegSizeTag.getUnsignedInt(0);
     }
 
     private void skipTo(int offset) throws IOException {
@@ -449,7 +449,8 @@ public class ExifParser {
                     "Number of component is larger then Integer.MAX_VALUE");
         }
         ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType);
-        if (tag.getDataSize() > 4) {
+        int dataSize = tag.getDataSize();
+        if (dataSize > 4) {
             long offset = mTiffStream.readUnsignedInt();
             if (offset > Integer.MAX_VALUE) {
                 throw new ExifInvalidFormatException(
@@ -458,7 +459,7 @@ public class ExifParser {
             tag.setOffset((int) offset);
         } else {
             readFullTagValue(tag);
-            mTiffStream.skip(4 - tag.getDataSize());
+            mTiffStream.skip(4 - dataSize);
         }
         return tag;
     }
@@ -472,22 +473,22 @@ public class ExifParser {
             case ExifTag.TIFF_TAG.TAG_EXIF_IFD:
                 if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
                         || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
-                    registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedInt());
+                    registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedInt(0));
                 }
                 break;
             case ExifTag.TIFF_TAG.TAG_GPS_IFD:
                 if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
-                    registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedInt());
+                    registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedInt(0));
                 }
                 break;
             case ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD:
                 if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
-                    registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedInt());
+                    registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedInt(0));
                 }
                 break;
             case ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT:
                 if (isThumbnailRequested()) {
-                    registerCompressedImage(tag.getUnsignedInt());
+                    registerCompressedImage(tag.getUnsignedInt(0));
                 }
                 break;
             case ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
index 7b5a2eb..fad47d6 100644 (file)
@@ -17,6 +17,7 @@
 package com.android.gallery3d.exif;
 
 import java.lang.reflect.Array;
+import java.util.Arrays;
 
 /**
  * This class stores information of an EXIF tag.
@@ -265,37 +266,37 @@ public class ExifTag {
     }
 
     public static interface GPS_TAG {
-        public static final short GPS_VERSION_ID = 0;
-        public static final short GPS_LATITUDE_REF = 1;
-        public static final short GPS_LATITUDE = 2;
-        public static final short GPS_LONGITUDE_REF = 3;
-        public static final short GPS_LONGITUDE = 4;
-        public static final short GPS_ALTITUDE_REF = 5;
-        public static final short GPS_ALTITUDE = 6;
-        public static final short GPS_TIME_STAMP = 7;
-        public static final short GPS_SATTELLITES = 8;
-        public static final short GPS_STATUS = 9;
-        public static final short GPS_MEASURE_MODE = 10;
-        public static final short GPS_DOP = 11;
-        public static final short GPS_SPEED_REF = 12;
-        public static final short GPS_SPEED = 13;
-        public static final short GPS_TRACK_REF = 14;
-        public static final short GPS_TRACK = 15;
-        public static final short GPS_IMG_DIRECTION_REF = 16;
-        public static final short GPS_IMG_DIRECTION = 17;
-        public static final short GPS_MAP_DATUM = 18;
-        public static final short GPS_DEST_LATITUDE_REF = 19;
-        public static final short GPS_DEST_LATITUDE = 20;
-        public static final short GPS_DEST_LONGITUDE_REF = 21;
-        public static final short GPS_DEST_LONGITUDE = 22;
-        public static final short GPS_DEST_BEARING_REF = 23;
-        public static final short GPS_DEST_BEARING = 24;
-        public static final short GPS_DEST_DISTANCE_REF = 25;
-        public static final short GPS_DEST_DISTANCE = 26;
-        public static final short GPS_PROCESSING_METHOD = 27;
-        public static final short GPS_AREA_INFORMATION = 28;
-        public static final short GPS_DATA_STAMP = 29;
-        public static final short GPS_DIFFERENTIAL = 30;
+        public static final short TAG_GPS_VERSION_ID = 0;
+        public static final short TAG_GPS_LATITUDE_REF = 1;
+        public static final short TAG_GPS_LATITUDE = 2;
+        public static final short TAG_GPS_LONGITUDE_REF = 3;
+        public static final short TAG_GPS_LONGITUDE = 4;
+        public static final short TAG_GPS_ALTITUDE_REF = 5;
+        public static final short TAG_GPS_ALTITUDE = 6;
+        public static final short TAG_GPS_TIME_STAMP = 7;
+        public static final short TAG_GPS_SATTELLITES = 8;
+        public static final short TAG_GPS_STATUS = 9;
+        public static final short TAG_GPS_MEASURE_MODE = 10;
+        public static final short TAG_GPS_DOP = 11;
+        public static final short TAG_GPS_SPEED_REF = 12;
+        public static final short TAG_GPS_SPEED = 13;
+        public static final short TAG_GPS_TRACK_REF = 14;
+        public static final short TAG_GPS_TRACK = 15;
+        public static final short TAG_GPS_IMG_DIRECTION_REF = 16;
+        public static final short TAG_GPS_IMG_DIRECTION = 17;
+        public static final short TAG_GPS_MAP_DATUM = 18;
+        public static final short TAG_GPS_DEST_LATITUDE_REF = 19;
+        public static final short TAG_GPS_DEST_LATITUDE = 20;
+        public static final short TAG_GPS_DEST_LONGITUDE_REF = 21;
+        public static final short TAG_GPS_DEST_LONGITUDE = 22;
+        public static final short TAG_GPS_DEST_BEARING_REF = 23;
+        public static final short TAG_GPS_DEST_BEARING = 24;
+        public static final short TAG_GPS_DEST_DISTANCE_REF = 25;
+        public static final short TAG_GPS_DEST_DISTANCE = 26;
+        public static final short TAG_GPS_PROCESSING_METHOD = 27;
+        public static final short TAG_GPS_AREA_INFORMATION = 28;
+        public static final short TAG_GPS_DATA_STAMP = 29;
+        public static final short TAG_GPS_DIFFERENTIAL = 30;
 
         public static final String GPS_REF_NORTH = "N";
         public static final String GPS_REF_SOUTH = "S";
@@ -363,8 +364,8 @@ public class ExifTag {
 
     private final short mTagId;
     private final short mDataType;
-    private final int mComponentCount;
     private final int mIfd;
+    private int mComponentCount;
     private Object mValue;
     private int mOffset;
 
@@ -451,83 +452,175 @@ public class ExifTag {
     }
 
     /**
-     * Sets the value of this tag. This is useful when we want to modify the tags and write it back
-     * to the JPEG file
+     * Sets integer values into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to the length.
      */
-    public void setValue(Object object) {
-        if (object.getClass().isArray()) {
-            assert(mComponentCount == Array.getLength(object));
-            mValue = object;
-        } else if (object instanceof String) {
-            assert(mComponentCount == ((String) object).length() + 1);
-            mValue = object;
-        } else {
-            // Wrap object with an array because user may try to get object by get method with
-            // index 0 when size = 1
-            // e.g. getShort(0)
-            assert(mComponentCount == 1);
-            Object array = Array.newInstance(object.getClass(), 1);
-            Array.set(array, 0, object);
-            mValue = array;
+    public void setValue(int[] value) {
+        long[] data = new long[value.length];
+        for (int i = 0; i < value.length; i++) {
+            data[i] = value[i];
         }
+        mValue = data;
+        mComponentCount = value.length;
     }
 
-    public short getShort(int index) {
-        return (Short) Array.get(mValue, index);
+    /**
+     * Sets integer value into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to 1.
+     */
+    public void setValue(int value) {
+        mValue = new long[] {value};
+        mComponentCount = 1;
     }
 
-    public short getShort() {
-        return (Short) Array.get(mValue, 0);
+    /**
+     * Sets short values into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to the length.
+     */
+    public void setValue(short[] value) {
+        long[] data = new long[value.length];
+        for (int i = 0; i < value.length; i++) {
+            data[i] = value[i];
+        }
+        mValue = data;
+        mComponentCount = value.length;
     }
 
-    public int getUnsignedShort(int index) {
-        return (Integer) Array.get(mValue, index);
+    /**
+     * Sets short value into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to 1.
+     */
+    public void setValue(short value) {
+        mValue = new long[] {value};
+        mComponentCount = 1;
     }
 
-    public int getUnsignedShort() {
-        return (Integer) Array.get(mValue, 0);
+    /**
+     * Sets long values into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to the length.
+     */
+    public void setValue(long[] value) {
+        long[] data = new long[value.length];
+        System.arraycopy(value, 0, data, 0, value.length);
+        mValue = data;
+        mComponentCount = value.length;
     }
 
-    public int getInt(int index) {
-        return (Integer) Array.get(mValue, index);
+    /**
+     * Sets long value into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to 1.
+     */
+    public void setValue(long value) {
+        mValue = new long[] {value};
+        mComponentCount = 1;
     }
 
-    public int getInt() {
-        return (Integer) Array.get(mValue, 0);
+    /**
+     * Sets String value into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to
+     * value.length() + 1.
+     */
+    public void setValue(String value) {
+        mComponentCount = value.length() + 1;
+        mValue = value;
     }
 
-    public long getUnsignedInt(int index) {
-        return (Long) Array.get(mValue, index);
+    /**
+     * Sets Rational values into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to the length.
+     */
+    public void setValue(Rational[] value) {
+        mValue = new Rational[value.length];
+        System.arraycopy(value, 0, mValue, 0, value.length);
+        mComponentCount = value.length;
     }
 
-    public long getUnsignedInt() {
-        return (Long) Array.get(mValue, 0);
+    /**
+     * Sets Rational value into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to 1.
+     */
+    public void setValue(Rational value) {
+        mValue = new Rational[] {value};
+        mComponentCount = 1;
     }
 
-    public String getString() {
-        return (String) mValue;
+    /**
+     * Sets byte values into this tag. This is useful when we want to modify the tags
+     * and write it back to the JPEG file. The component count will be set to the length.
+     */
+    public void setValue(byte[] value, int offset, int length) {
+        long[] data = new long[length];
+        for (int i = 0; i < length; i++) {
+            data[i] = value[i + offset];
+        }
+        mValue = data;
+        mComponentCount = length;
     }
 
-    public Rational getRational(int index) {
-        return ((Rational[]) mValue)[index];
+    /**
+     * Sets the byte array as the value of this tag.
+     * This is equivalent to setValue(value, 0, value.length).
+     */
+    public void setValue(byte[] value) {
+        setValue(value, 0, value.length);
     }
 
-    public Rational getRational() {
-        return ((Rational[]) mValue)[0];
+    public short getShort(int index) {
+        if (mValue instanceof long[]) {
+            return (short) (((long[]) mValue) [index]);
+        } else {
+            throw new RuntimeException("There is no numerical value in this tag");
+        }
     }
 
-    public int getBytes(byte[] buf) {
-        return getBytes(buf, 0, buf.length);
+    public int getUnsignedShort(int index) {
+        if (mValue instanceof long[]) {
+            return (int) (((long[]) mValue) [index]);
+        } else {
+            throw new RuntimeException("There is no numerical value in this tag");
+        }
+    }
+
+    public int getInt(int index) {
+        if (mValue instanceof long[]) {
+            return (int) (((long[]) mValue) [index]);
+        } else {
+            throw new RuntimeException("There is no numerical value in this tag");
+        }
+    }
+
+    public long getUnsignedInt(int index) {
+        if (mValue instanceof long[]) {
+            return ((long[]) mValue) [index];
+        } else {
+            throw new RuntimeException("There is no numerical value in this tag");
+        }
+    }
+
+    public String getString() {
+        return (String) mValue;
     }
 
-    public int getBytes(byte[] buf, int offset, int length) {
-        byte[] data = (byte[]) mValue;
-        if (data.length < length + offset) {
-            System.arraycopy(data, offset, buf, 0, data.length - offset);
-            return data.length - offset;
+    public Rational getRational(int index) {
+        Object value = Array.get(mValue, index);
+        if (value instanceof Rational) {
+            return (Rational) value;
         } else {
-            System.arraycopy(data, offset, buf, 0, length);
-            return length;
+            throw new RuntimeException("There is no Rational value in this tag");
+        }
+    }
+
+    public void getBytes(byte[] buf) {
+        getBytes(buf, 0, buf.length);
+    }
+
+    public void getBytes(byte[] buf, int offset, int length) {
+        if (!(mValue instanceof long[])) {
+            throw new RuntimeException("There is no byte value in this tag");
+        }
+        long[] data = (long[]) mValue;
+        for(int i = 0; i < length; i++) {
+            buf[offset + i] = (byte) data[i];
         }
     }
 
@@ -541,7 +634,7 @@ public class ExifTag {
             case ExifTag.TYPE_UNSIGNED_BYTE:
                 byte buf[] = new byte[getComponentCount()];
                 getBytes(buf);
-                for(int i = 0; i < getComponentCount(); i++) {
+                for(int i = 0, n = getComponentCount(); i < n; i++) {
                     if(i != 0) sbuilder.append(" ");
                     sbuilder.append(String.format("%02x", buf[i]));
                 }
@@ -551,27 +644,27 @@ public class ExifTag {
                 sbuilder.append(getString().trim());
                 break;
             case ExifTag.TYPE_UNSIGNED_INT:
-                for(int i = 0; i < getComponentCount(); i++) {
+                for(int i = 0, n = getComponentCount(); i < n; i++) {
                     if(i != 0) sbuilder.append(" ");
                     sbuilder.append(getUnsignedInt(i));
                 }
                 break;
             case ExifTag.TYPE_RATIONAL:
             case ExifTag.TYPE_UNSIGNED_RATIONAL:
-                for(int i = 0; i < getComponentCount(); i++) {
+                for(int i = 0, n = getComponentCount(); i < n; i++) {
                     Rational r = getRational(i);
                     if(i != 0) sbuilder.append(" ");
                     sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
                 }
                 break;
             case ExifTag.TYPE_UNSIGNED_SHORT:
-                for(int i = 0; i < getComponentCount(); i++) {
+                for(int i = 0, n = getComponentCount(); i < n; i++) {
                     if(i != 0) sbuilder.append(" ");
                     sbuilder.append(getUnsignedShort(i));
                 }
                 break;
             case ExifTag.TYPE_INT:
-                for(int i = 0; i < getComponentCount(); i++) {
+                for(int i = 0, n = getComponentCount(); i < n; i++) {
                     if(i != 0) sbuilder.append(" ");
                     sbuilder.append(getInt(i));
                 }
@@ -579,4 +672,39 @@ public class ExifTag {
         }
         return sbuilder.toString();
     }
+
+    /**
+     * Returns true if the ID is one of the following: {@link TIFF_TAG#TAG_EXIF_IFD},
+     * {@link TIFF_TAG#TAG_GPS_IFD}, {@link TIFF_TAG#TAG_JPEG_INTERCHANGE_FORMAT},
+     * {@link TIFF_TAG#TAG_STRIP_OFFSETS}, {@link EXIF_TAG#TAG_INTEROPERABILITY_IFD}
+     */
+    public static boolean isOffsetTag(short tagId) {
+        return tagId == ExifTag.TIFF_TAG.TAG_EXIF_IFD
+                || tagId == ExifTag.TIFF_TAG.TAG_GPS_IFD
+                || tagId == ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT
+                || tagId == ExifTag.TIFF_TAG.TAG_STRIP_OFFSETS
+                || tagId == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof ExifTag) {
+            ExifTag tag = (ExifTag) obj;
+            boolean isArray = mValue != null && mValue.getClass().isArray();
+            if (mValue != null) {
+                if (mValue instanceof long[]) {
+                    if (!(tag.mValue instanceof long[])) return false;
+                    return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
+                } else if (mValue instanceof Rational[]) {
+                    if (!(tag.mValue instanceof Rational[])) return false;
+                    return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
+                } else {
+                    return mValue.equals(tag.mValue);
+                }
+            } else {
+                return tag.mValue == null;
+            }
+        }
+        return false;
+    }
 }
\ No newline at end of file
index 4109361..079613b 100644 (file)
@@ -77,6 +77,14 @@ public class IfdData {
     public void setTag(ExifTag tag) {
         mExifTags.put(tag.getTagId(), tag);
     }
+
+    /**
+     * Gets the tags count in the IFD.
+     */
+    public int getTagCount() {
+        return mExifTags.size();
+    }
+
     /**
      * Sets the offset of next IFD.
      */
@@ -90,4 +98,25 @@ public class IfdData {
     int getOffsetToNextIfd() {
         return mOffsetToNextIfd;
     }
+
+    /**
+     * Returns true if all tags in this two IFDs are equal. Note that tags of IFDs offset or
+     * thumbnail offset will be ignored.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof IfdData) {
+            IfdData data = (IfdData) obj;
+            if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
+                ExifTag[] tags = data.getAllTags(new ExifTag[0]);
+                for (ExifTag tag: tags) {
+                    if (ExifTag.isOffsetTag(tag.getTagId())) continue;
+                    ExifTag tag2 = mExifTags.get(tag.getTagId());
+                    if (!tag.equals(tag2)) return false;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
new file mode 100644 (file)
index 0000000..9244be3
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class OrderedDataOutputStream extends FilterOutputStream {
+    private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
+
+    public OrderedDataOutputStream(OutputStream out) {
+        super(out);
+    }
+
+    public void setByteOrder(ByteOrder order) {
+        mByteBuffer.order(order);
+    }
+
+    public void writeShort(short value) throws IOException {
+        mByteBuffer.rewind();
+        mByteBuffer.putShort(value);
+        out.write(mByteBuffer.array(), 0, 2);
+     }
+
+    public void writeInt(int value) throws IOException {
+        mByteBuffer.rewind();
+        mByteBuffer.putInt(value);
+        out.write(mByteBuffer.array());
+    }
+
+    public void writeRational(Rational rational) throws IOException {
+        writeInt((int) rational.getNominator());
+        writeInt((int) rational.getDenominator());
+    }
+}
index 4c3c77f..7d90262 100644 (file)
@@ -33,4 +33,13 @@ public class Rational {
     public long getDenominator() {
         return mDenominator;
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Rational) {
+            Rational data = (Rational) obj;
+            return mNominator == data.mNominator && mDenominator == data.mDenominator;
+        }
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/Util.java b/src/com/android/gallery3d/exif/Util.java
new file mode 100644 (file)
index 0000000..594d6fc
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import java.io.Closeable;
+
+class Util {
+    public static boolean equals(Object a, Object b) {
+        return (a == b) || (a == null ? false : a.equals(b));
+    }
+
+    public static void closeSilently(Closeable c) {
+        if (c == null) return;
+        try {
+            c.close();
+        } catch (Throwable t) {
+            // do nothing
+        }
+    }
+}
index 6910562..5e76fe6 100644 (file)
@@ -61,7 +61,6 @@ public class MenuExecutor {
     private Future<?> mTask;
     // wait the operation to finish when we want to stop it.
     private boolean mWaitOnStop;
-    private Intent mShareIntent;
 
     private final AbstractGalleryActivity mActivity;
     private final SelectionManager mSelectionManager;
@@ -230,7 +229,7 @@ public class MenuExecutor {
                 Intent intent = getIntentBySingleSelectedPath(Intent.ACTION_ATTACH_DATA)
                         .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                 intent.putExtra("mimeType", intent.getType());
-                Activity activity = (Activity) mActivity;
+                Activity activity = mActivity;
                 activity.startActivity(Intent.createChooser(
                         intent, activity.getString(R.string.set_as)));
                 return;
@@ -314,7 +313,7 @@ public class MenuExecutor {
         ArrayList<Path> ids = mSelectionManager.getSelected(false);
         stopTaskAndDismissDialog();
 
-        Activity activity = (Activity) mActivity;
+        Activity activity = mActivity;
         mDialog = createProgressDialog(activity, title, ids.size());
         if (showDialog) {
             mDialog.show();
@@ -366,7 +365,7 @@ public class MenuExecutor {
                 double latlng[] = new double[2];
                 item.getLatLong(latlng);
                 if (GalleryUtils.isValidLocation(latlng[0], latlng[1])) {
-                    GalleryUtils.showOnMap((Context) mActivity, latlng[0], latlng[1]);
+                    GalleryUtils.showOnMap(mActivity, latlng[0], latlng[1]);
                 }
                 break;
             }
index 3bd0a21..b36f5c3 100644 (file)
@@ -30,7 +30,7 @@ class Paper {
     private static final int ROTATE_FACTOR = 4;
     private EdgeAnimation mAnimationLeft = new EdgeAnimation();
     private EdgeAnimation mAnimationRight = new EdgeAnimation();
-    private int mWidth, mHeight;
+    private int mWidth;
     private float[] mMatrix = new float[16];
 
     public void overScroll(float distance) {
@@ -63,7 +63,6 @@ class Paper {
 
     public void setSize(int width, int height) {
         mWidth = width;
-        mHeight = height;
     }
 
     public float[] getTransform(Rect rect, float scrollX) {
index 5c4c1e9..9a0fe9d 100644 (file)
@@ -19,7 +19,6 @@ package com.android.gallery3d.ui;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Matrix;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Message;
@@ -189,7 +188,6 @@ public class PhotoView extends GLView {
 
     private Listener mListener;
     private Model mModel;
-    private StringTexture mLoadingText;
     private StringTexture mNoThumbnailText;
     private TileImageView mTileView;
     private EdgeView mEdgeView;
@@ -198,7 +196,6 @@ public class PhotoView extends GLView {
 
     private SynchronizedHandler mHandler;
 
-    private Point mImageCenter = new Point();
     private boolean mCancelExtraScalingPending;
     private boolean mFilmMode = false;
     private int mDisplayRotation = 0;
@@ -252,9 +249,6 @@ public class PhotoView extends GLView {
                     hideUndoBar();
                 }
             });
-        mLoadingText = StringTexture.newInstance(
-                context.getString(R.string.loading),
-                DEFAULT_TEXT_SIZE, Color.WHITE);
         mNoThumbnailText = StringTexture.newInstance(
                 context.getString(R.string.no_thumbnail),
                 DEFAULT_TEXT_SIZE, Color.WHITE);
@@ -266,25 +260,37 @@ public class PhotoView extends GLView {
 
         mPositionController = new PositionController(context,
                 new PositionController.Listener() {
-                    public void invalidate() {
-                        PhotoView.this.invalidate();
-                    }
-                    public boolean isHoldingDown() {
-                        return (mHolding & HOLD_TOUCH_DOWN) != 0;
-                    }
-                    public boolean isHoldingDelete() {
-                        return (mHolding & HOLD_DELETE) != 0;
-                    }
-                    public void onPull(int offset, int direction) {
-                        mEdgeView.onPull(offset, direction);
-                    }
-                    public void onRelease() {
-                        mEdgeView.onRelease();
-                    }
-                    public void onAbsorb(int velocity, int direction) {
-                        mEdgeView.onAbsorb(velocity, direction);
-                    }
-                });
+
+            @Override
+            public void invalidate() {
+                PhotoView.this.invalidate();
+            }
+
+            @Override
+            public boolean isHoldingDown() {
+                return (mHolding & HOLD_TOUCH_DOWN) != 0;
+            }
+
+            @Override
+            public boolean isHoldingDelete() {
+                return (mHolding & HOLD_DELETE) != 0;
+            }
+
+            @Override
+            public void onPull(int offset, int direction) {
+                mEdgeView.onPull(offset, direction);
+            }
+
+            @Override
+            public void onRelease() {
+                mEdgeView.onRelease();
+            }
+
+            @Override
+            public void onAbsorb(int velocity, int direction) {
+                mEdgeView.onAbsorb(velocity, direction);
+            }
+        });
         mVideoPlayIcon = new ResourceTexture(context, R.drawable.ic_control_play);
         for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
             if (i == 0) {
@@ -369,7 +375,7 @@ public class PhotoView extends GLView {
                 default: throw new AssertionError(message.what);
             }
         }
-    };
+    }
 
     ////////////////////////////////////////////////////////////////////////////
     //  Data/Image change notifications
@@ -559,9 +565,6 @@ public class PhotoView extends GLView {
         private int mLoadingState = Model.LOADING_INIT;
         private Size mSize = new Size();
         private boolean mWasCameraCenter;
-        public void FullPicture(TileImageView tileView) {
-            mTileView = tileView;
-        }
 
         @Override
         public void reload() {
index 16e9f7b..434054d 100644 (file)
@@ -23,10 +23,10 @@ import android.util.Log;
 import com.android.gallery3d.app.PhotoPage;
 import com.android.gallery3d.common.OverScroller;
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.ui.PhotoView.Size;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.RangeArray;
 import com.android.gallery3d.util.RangeIntArray;
-import com.android.gallery3d.ui.PhotoView.Size;
 
 class PositionController {
     private static final String TAG = "PositionController";
@@ -857,6 +857,7 @@ class PositionController {
         //dumpState();
     }
 
+    @SuppressWarnings("unused")
     private void dumpState() {
         for (int i = -BOX_MAX; i < BOX_MAX; i++) {
             Log.d(TAG, "Gap " + i + ": " + mGaps.get(i).mCurrentGap);
@@ -981,6 +982,7 @@ class PositionController {
         g.mAnimationStartTime = NO_ANIMATION;
     }
 
+    @SuppressWarnings("unused")
     private void debugMoveBox(int fromIndex[]) {
         StringBuilder s = new StringBuilder("moveBox:");
         for (int i = 0; i < fromIndex.length; i++) {
index e750e27..f04a5cc 100644 (file)
@@ -86,10 +86,10 @@ public class GalleryUtils {
 
     public static float[] intColorToFloatARGBArray(int from) {
         return new float[] {
-            (float) Color.alpha(from) / 255f,
-            (float) Color.red(from) / 255f,
-            (float) Color.green(from) / 255f,
-            (float) Color.blue(from) / 255f
+            Color.alpha(from) / 255f,
+            Color.red(from) / 255f,
+            Color.green(from) / 255f,
+            Color.blue(from) / 255f
         };
     }
 
diff --git a/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java
new file mode 100644 (file)
index 0000000..fb85b2c
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 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.exif;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.test.InstrumentationTestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ExifOutputStreamTest extends InstrumentationTestCase {
+
+    private final int mImageResourceId;
+
+    public ExifOutputStreamTest(int imageResourceId, int xmlReourceId) {
+        mImageResourceId = imageResourceId;
+    }
+
+    public void testExifOutputStream() throws IOException, ExifInvalidFormatException {
+        File file = File.createTempFile("exif_test", ".jpg");
+        InputStream imageInputStream = null;
+        InputStream exifInputStream = null;
+        FileInputStream reDecodeInputStream = null;
+        FileInputStream reParseInputStream = null;
+        try {
+            // Read the image
+            imageInputStream = getInstrumentation()
+                    .getContext().getResources().openRawResource(mImageResourceId);
+            Bitmap bmp = BitmapFactory.decodeStream(imageInputStream);
+
+            // Read exif data
+            exifInputStream = getInstrumentation()
+                    .getContext().getResources().openRawResource(mImageResourceId);
+            ExifData exifData = new ExifReader().read(exifInputStream);
+
+            // Encode the image with the exif data
+            FileOutputStream outputStream = new FileOutputStream(file);
+            ExifOutputStream exifOutputStream = new ExifOutputStream(outputStream);
+            exifOutputStream.setExifData(exifData);
+            bmp.compress(Bitmap.CompressFormat.JPEG, 100, exifOutputStream);
+            exifOutputStream.close();
+
+            // Re-decode the temp file and check the data.
+            reDecodeInputStream = new FileInputStream(file);
+            Bitmap decodedBmp = BitmapFactory.decodeStream(reDecodeInputStream);
+            assertNotNull(decodedBmp);
+
+            // Re-parse the temp file the check EXIF tag
+            reParseInputStream = new FileInputStream(file);
+            ExifData reExifData = new ExifReader().read(reParseInputStream);
+            assertEquals(exifData, reExifData);
+        } finally {
+            Util.closeSilently(imageInputStream);
+            Util.closeSilently(exifInputStream);
+            Util.closeSilently(reDecodeInputStream);
+            Util.closeSilently(reParseInputStream);
+        }
+    }
+}
\ No newline at end of file
index 241aaf5..e967e3d 100644 (file)
@@ -210,7 +210,7 @@ public class ExifParserTest extends InstrumentationTestCase {
                 case ExifParser.EVENT_NEW_TAG:
                     ExifTag tag = parser.getTag();
                     if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_COMPRESSION) {
-                        if (tag.getUnsignedShort() == ExifTag.TIFF_TAG.COMPRESSION_JPEG) {
+                        if (tag.getUnsignedShort(0) == ExifTag.TIFF_TAG.COMPRESSION_JPEG) {
                             mIsContainCompressedImage = true;
                         }
                     }
index 67e6956..e5b51f1 100644 (file)
@@ -69,7 +69,7 @@ public class ExifReaderTest extends InstrumentationTestCase {
     private void checkThumbnail(ExifData exifData) {
         IfdData ifd1 = exifData.getIfdData(IfdId.TYPE_IFD_1);
         if (ifd1 != null) {
-            if (ifd1.getTag(ExifTag.TIFF_TAG.TAG_COMPRESSION).getUnsignedShort() ==
+            if (ifd1.getTag(ExifTag.TIFF_TAG.TAG_COMPRESSION).getUnsignedShort(0) ==
                     ExifTag.TIFF_TAG.COMPRESSION_JPEG) {
                 assertTrue(exifData.hasCompressedThumbnail());
                 byte[] thumbnail = exifData.getCompressedThumbnail();
@@ -79,7 +79,7 @@ public class ExifReaderTest extends InstrumentationTestCase {
                 int planarType = ExifTag.TIFF_TAG.PLANAR_CONFIGURATION_CHUNKY;
                 ExifTag planarTag = ifd1.getTag(ExifTag.TIFF_TAG.TAG_PLANAR_CONFIGURATION);
                 if (planarTag != null) {
-                    planarType = planarTag.getUnsignedShort();
+                    planarType = planarTag.getUnsignedShort(0);
                 }
 
                 ExifTag heightTag = ifd1.getTag(ExifTag.TIFF_TAG.TAG_IMAGE_HEIGHT);
@@ -94,7 +94,7 @@ public class ExifReaderTest extends InstrumentationTestCase {
                     assertTrue(stripCount == (imageLength + rowsPerStrip - 1) / rowsPerStrip);
                 } else {
                     ExifTag samplePerPixelTag = ifd1.getTag(ExifTag.TIFF_TAG.TAG_SAMPLES_PER_PIXEL);
-                    int samplePerPixel = samplePerPixelTag.getUnsignedShort();
+                    int samplePerPixel = samplePerPixelTag.getUnsignedShort(0);
                     assertTrue(stripCount ==
                             (imageLength + rowsPerStrip - 1) / rowsPerStrip * samplePerPixel);
                 }
@@ -114,9 +114,9 @@ public class ExifReaderTest extends InstrumentationTestCase {
 
     private int getUnsignedIntOrShort(ExifTag tag) {
         if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
-            return tag.getUnsignedShort();
+            return tag.getUnsignedShort(0);
         } else {
-            return (int) tag.getUnsignedInt();
+            return (int) tag.getUnsignedInt(0);
         }
     }
 
index 022597d..bcbc9f5 100644 (file)
@@ -44,6 +44,7 @@ public class ExifTestRunner extends InstrumentationTestRunner {
         TestSuite suite = new InstrumentationTestSuite(this);
         getAllTestFromTestCase(ExifParserTest.class, suite);
         getAllTestFromTestCase(ExifReaderTest.class, suite);
+        getAllTestFromTestCase(ExifOutputStreamTest.class, suite);
         return suite;
     }