1/*
2 * Copyright (C) 2013 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.camera.data;
18
19import android.content.Context;
20import android.util.SparseIntArray;
21
22import com.android.camera.debug.Log;
23import com.android.camera.exif.ExifInterface;
24import com.android.camera.exif.ExifTag;
25import com.android.camera2.R;
26
27import java.io.FileNotFoundException;
28import java.io.IOException;
29import java.util.Iterator;
30import java.util.Map.Entry;
31import java.util.TreeMap;
32
33public class MediaDetails implements Iterable<Entry<Integer, Object>> {
34    @SuppressWarnings("unused")
35    private static final Log.Tag TAG = new Log.Tag("MediaDetails");
36
37    private TreeMap<Integer, Object> mDetails = new TreeMap<Integer, Object>();
38    private SparseIntArray mUnits = new SparseIntArray();
39
40    public static final int INDEX_TITLE = 1;
41    public static final int INDEX_DESCRIPTION = 2;
42    public static final int INDEX_DATETIME = 3;
43    public static final int INDEX_LOCATION = 4;
44    public static final int INDEX_WIDTH = 5;
45    public static final int INDEX_HEIGHT = 6;
46    public static final int INDEX_ORIENTATION = 7;
47    public static final int INDEX_DURATION = 8;
48    public static final int INDEX_MIMETYPE = 9;
49    public static final int INDEX_SIZE = 10;
50
51    // for EXIF
52    public static final int INDEX_MAKE = 100;
53    public static final int INDEX_MODEL = 101;
54    public static final int INDEX_FLASH = 102;
55    public static final int INDEX_FOCAL_LENGTH = 103;
56    public static final int INDEX_WHITE_BALANCE = 104;
57    public static final int INDEX_APERTURE = 105;
58    public static final int INDEX_SHUTTER_SPEED = 106;
59    public static final int INDEX_EXPOSURE_TIME = 107;
60    public static final int INDEX_ISO = 108;
61
62    // Put this last because it may be long.
63    public static final int INDEX_PATH = 200;
64
65    public static class FlashState {
66        private static int FLASH_FIRED_MASK = 1;
67        private static int FLASH_RETURN_MASK = 2 | 4;
68        private static int FLASH_MODE_MASK = 8 | 16;
69        private static int FLASH_FUNCTION_MASK = 32;
70        private static int FLASH_RED_EYE_MASK = 64;
71        private int mState;
72
73        public FlashState(int state) {
74            mState = state;
75        }
76
77        public boolean isFlashFired() {
78            return (mState & FLASH_FIRED_MASK) != 0;
79        }
80    }
81
82    public void addDetail(int index, Object value) {
83        mDetails.put(index, value);
84    }
85
86    public Object getDetail(int index) {
87        return mDetails.get(index);
88    }
89
90    public int size() {
91        return mDetails.size();
92    }
93
94    @Override
95    public Iterator<Entry<Integer, Object>> iterator() {
96        return mDetails.entrySet().iterator();
97    }
98
99    public void setUnit(int index, int unit) {
100        mUnits.put(index, unit);
101    }
102
103    public boolean hasUnit(int index) {
104        return mUnits.indexOfKey(index) >= 0;
105    }
106
107    public int getUnit(int index) {
108        return mUnits.get(index);
109    }
110
111    private static void setExifData(MediaDetails details, ExifTag tag,
112            int key) {
113        if (tag != null) {
114            String value = null;
115            int type = tag.getDataType();
116            if (type == ExifTag.TYPE_UNSIGNED_RATIONAL || type == ExifTag.TYPE_RATIONAL) {
117                value = String.valueOf(tag.getValueAsRational(0).toDouble());
118            } else if (type == ExifTag.TYPE_ASCII) {
119                value = tag.getValueAsString();
120            } else {
121                value = String.valueOf(tag.forceGetValueAsLong(0));
122            }
123            if (key == MediaDetails.INDEX_FLASH) {
124                MediaDetails.FlashState state = new MediaDetails.FlashState(
125                        Integer.valueOf(value));
126                details.addDetail(key, state);
127            } else {
128                details.addDetail(key, value);
129            }
130        }
131    }
132
133    /**
134     * Extracts data from the EXIF of the given file and stores it in the
135     * MediaDetails instance.
136     */
137    public static void extractExifInfo(MediaDetails details, String filePath) {
138        ExifInterface exif = new ExifInterface();
139        try {
140            exif.readExif(filePath);
141        } catch (FileNotFoundException e) {
142            Log.w(TAG, "Could not find file to read exif: " + filePath, e);
143        } catch (IOException e) {
144            Log.w(TAG, "Could not read exif from file: " + filePath, e);
145        }
146
147        setExifData(details, exif.getTag(ExifInterface.TAG_FLASH),
148                MediaDetails.INDEX_FLASH);
149        setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_WIDTH),
150                MediaDetails.INDEX_WIDTH);
151        setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_LENGTH),
152                MediaDetails.INDEX_HEIGHT);
153        setExifData(details, exif.getTag(ExifInterface.TAG_MAKE),
154                MediaDetails.INDEX_MAKE);
155        setExifData(details, exif.getTag(ExifInterface.TAG_MODEL),
156                MediaDetails.INDEX_MODEL);
157        setExifData(details, exif.getTag(ExifInterface.TAG_APERTURE_VALUE),
158                MediaDetails.INDEX_APERTURE);
159        setExifData(details, exif.getTag(ExifInterface.TAG_ISO_SPEED_RATINGS),
160                MediaDetails.INDEX_ISO);
161        setExifData(details, exif.getTag(ExifInterface.TAG_WHITE_BALANCE),
162                MediaDetails.INDEX_WHITE_BALANCE);
163        setExifData(details, exif.getTag(ExifInterface.TAG_EXPOSURE_TIME),
164                MediaDetails.INDEX_EXPOSURE_TIME);
165        ExifTag focalTag = exif.getTag(ExifInterface.TAG_FOCAL_LENGTH);
166        if (focalTag != null) {
167            details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH,
168                    focalTag.getValueAsRational(0).toDouble());
169            details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm);
170        }
171    }
172
173    /**
174     * Returns a (localized) string for the given duration (in seconds).
175     */
176    public static String formatDuration(final Context context, long seconds) {
177        long h = seconds / 3600;
178        long m = (seconds - h * 3600) / 60;
179        long s = seconds - (h * 3600 + m * 60);
180        String durationValue;
181        if (h == 0) {
182            durationValue = String.format(context.getString(R.string.details_ms), m, s);
183        } else {
184            durationValue = String.format(context.getString(R.string.details_hms), h, m, s);
185        }
186        return durationValue;
187    }
188}
189