1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.mms.exif;
18
19import com.android.mms.LogTag;
20
21import android.util.Log;
22
23import java.io.IOException;
24import java.io.InputStream;
25import java.nio.ByteBuffer;
26import java.nio.ByteOrder;
27import java.util.ArrayList;
28import java.util.List;
29
30class ExifModifier {
31    public static final String TAG = LogTag.TAG;
32    public static final boolean DEBUG = false;
33    private final ByteBuffer mByteBuffer;
34    private final ExifData mTagToModified;
35    private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
36    private final ExifInterface mInterface;
37    private int mOffsetBase;
38
39    private static class TagOffset {
40        final int mOffset;
41        final ExifTag mTag;
42
43        TagOffset(ExifTag tag, int offset) {
44            mTag = tag;
45            mOffset = offset;
46        }
47    }
48
49    protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
50            ExifInvalidFormatException {
51        mByteBuffer = byteBuffer;
52        mOffsetBase = byteBuffer.position();
53        mInterface = iRef;
54        InputStream is = null;
55        try {
56            is = new ByteBufferInputStream(byteBuffer);
57            // Do not require any IFD;
58            ExifParser parser = ExifParser.parse(is, mInterface);
59            mTagToModified = new ExifData(parser.getByteOrder());
60            mOffsetBase += parser.getTiffStartPosition();
61            mByteBuffer.position(0);
62        } finally {
63            ExifInterface.closeSilently(is);
64        }
65    }
66
67    protected ByteOrder getByteOrder() {
68        return mTagToModified.getByteOrder();
69    }
70
71    protected boolean commit() throws IOException, ExifInvalidFormatException {
72        InputStream is = null;
73        try {
74            is = new ByteBufferInputStream(mByteBuffer);
75            int flag = 0;
76            IfdData[] ifdDatas = new IfdData[] {
77                    mTagToModified.getIfdData(IfdId.TYPE_IFD_0),
78                    mTagToModified.getIfdData(IfdId.TYPE_IFD_1),
79                    mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF),
80                    mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
81                    mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
82            };
83
84            if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
85                flag |= ExifParser.OPTION_IFD_0;
86            }
87            if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
88                flag |= ExifParser.OPTION_IFD_1;
89            }
90            if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
91                flag |= ExifParser.OPTION_IFD_EXIF;
92            }
93            if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
94                flag |= ExifParser.OPTION_IFD_GPS;
95            }
96            if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
97                flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
98            }
99
100            ExifParser parser = ExifParser.parse(is, flag, mInterface);
101            int event = parser.next();
102            IfdData currIfd = null;
103            while (event != ExifParser.EVENT_END) {
104                switch (event) {
105                    case ExifParser.EVENT_START_OF_IFD:
106                        currIfd = ifdDatas[parser.getCurrentIfd()];
107                        if (currIfd == null) {
108                            parser.skipRemainingTagsInCurrentIfd();
109                        }
110                        break;
111                    case ExifParser.EVENT_NEW_TAG:
112                        ExifTag oldTag = parser.getTag();
113                        ExifTag newTag = currIfd.getTag(oldTag.getTagId());
114                        if (newTag != null) {
115                            if (newTag.getComponentCount() != oldTag.getComponentCount()
116                                    || newTag.getDataType() != oldTag.getDataType()) {
117                                return false;
118                            } else {
119                                mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset()));
120                                currIfd.removeTag(oldTag.getTagId());
121                                if (currIfd.getTagCount() == 0) {
122                                    parser.skipRemainingTagsInCurrentIfd();
123                                }
124                            }
125                        }
126                        break;
127                }
128                event = parser.next();
129            }
130            for (IfdData ifd : ifdDatas) {
131                if (ifd != null && ifd.getTagCount() > 0) {
132                    return false;
133                }
134            }
135            modify();
136        } finally {
137            ExifInterface.closeSilently(is);
138        }
139        return true;
140    }
141
142    private void modify() {
143        mByteBuffer.order(getByteOrder());
144        for (TagOffset tagOffset : mTagOffsets) {
145            writeTagValue(tagOffset.mTag, tagOffset.mOffset);
146        }
147    }
148
149    private void writeTagValue(ExifTag tag, int offset) {
150        if (DEBUG) {
151            Log.v(TAG, "modifying tag to: \n" + tag.toString());
152            Log.v(TAG, "at offset: " + offset);
153        }
154        mByteBuffer.position(offset + mOffsetBase);
155        switch (tag.getDataType()) {
156            case ExifTag.TYPE_ASCII:
157                byte buf[] = tag.getStringByte();
158                if (buf.length == tag.getComponentCount()) {
159                    buf[buf.length - 1] = 0;
160                    mByteBuffer.put(buf);
161                } else {
162                    mByteBuffer.put(buf);
163                    mByteBuffer.put((byte) 0);
164                }
165                break;
166            case ExifTag.TYPE_LONG:
167            case ExifTag.TYPE_UNSIGNED_LONG:
168                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
169                    mByteBuffer.putInt((int) tag.getValueAt(i));
170                }
171                break;
172            case ExifTag.TYPE_RATIONAL:
173            case ExifTag.TYPE_UNSIGNED_RATIONAL:
174                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
175                    Rational v = tag.getRational(i);
176                    mByteBuffer.putInt((int) v.getNumerator());
177                    mByteBuffer.putInt((int) v.getDenominator());
178                }
179                break;
180            case ExifTag.TYPE_UNDEFINED:
181            case ExifTag.TYPE_UNSIGNED_BYTE:
182                buf = new byte[tag.getComponentCount()];
183                tag.getBytes(buf);
184                mByteBuffer.put(buf);
185                break;
186            case ExifTag.TYPE_UNSIGNED_SHORT:
187                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
188                    mByteBuffer.putShort((short) tag.getValueAt(i));
189                }
190                break;
191        }
192    }
193
194    public void modifyTag(ExifTag tag) {
195        mTagToModified.addTag(tag);
196    }
197}
198