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.app.ProgressDialog;
20import android.content.ContentValues;
21import android.content.Context;
22import android.os.AsyncTask;
23import android.provider.MediaStore.Images;
24
25import com.android.camera.data.LocalMediaData.PhotoData;
26import com.android.camera.debug.Log;
27import com.android.camera.exif.ExifInterface;
28import com.android.camera.exif.ExifTag;
29import com.android.camera2.R;
30
31import java.io.File;
32import java.io.FileNotFoundException;
33import java.io.IOException;
34
35/**
36 * RotationTask can be used to rotate a {@link LocalData} by updating the exif
37 * data from jpeg file. Note that only {@link PhotoData}  can be rotated.
38 */
39public class RotationTask extends AsyncTask<LocalData, Void, LocalData> {
40    private static final Log.Tag TAG = new Log.Tag("RotationTask");
41    private final Context mContext;
42    private final LocalDataAdapter mAdapter;
43    private final int mCurrentDataId;
44    private final boolean mClockwise;
45    private ProgressDialog mProgress;
46
47    public RotationTask(Context context, LocalDataAdapter adapter,
48            int currentDataId, boolean clockwise) {
49        mContext = context;
50        mAdapter = adapter;
51        mCurrentDataId = currentDataId;
52        mClockwise = clockwise;
53    }
54
55    @Override
56    protected void onPreExecute() {
57        // Show a progress bar since the rotation could take long.
58        mProgress = new ProgressDialog(mContext);
59        int titleStringId = mClockwise ? R.string.rotate_right : R.string.rotate_left;
60        mProgress.setTitle(mContext.getString(titleStringId));
61        mProgress.setMessage(mContext.getString(R.string.please_wait));
62        mProgress.setCancelable(false);
63        mProgress.show();
64    }
65
66    @Override
67    protected LocalData doInBackground(LocalData... data) {
68        return rotateInJpegExif(data[0]);
69    }
70
71    /**
72     * Rotates the image by updating the exif. Done in background thread.
73     * The worst case is the whole file needed to be re-written with
74     * modified exif data.
75     *
76     * @return A new {@link LocalData} object which containing the new info.
77     */
78    private LocalData rotateInJpegExif(LocalData data) {
79        if (!(data instanceof PhotoData)) {
80            Log.w(TAG, "Rotation can only happen on PhotoData.");
81            return null;
82        }
83
84        PhotoData imageData = (PhotoData) data;
85        int originRotation = imageData.getRotation();
86        int finalRotationDegrees;
87        if (mClockwise) {
88            finalRotationDegrees = (originRotation + 90) % 360;
89        } else {
90            finalRotationDegrees = (originRotation + 270) % 360;
91        }
92
93        String filePath = imageData.getPath();
94        ContentValues values = new ContentValues();
95        boolean success = false;
96        int newOrientation = 0;
97        if (imageData.getMimeType().equalsIgnoreCase(LocalData.MIME_TYPE_JPEG)) {
98            ExifInterface exifInterface = new ExifInterface();
99            ExifTag tag = exifInterface.buildTag(
100                    ExifInterface.TAG_ORIENTATION,
101                    ExifInterface.getOrientationValueForRotation(
102                            finalRotationDegrees));
103            if (tag != null) {
104                exifInterface.setTag(tag);
105                try {
106                    // Note: This only works if the file already has some EXIF.
107                    exifInterface.forceRewriteExif(filePath);
108                    long fileSize = new File(filePath).length();
109                    values.put(Images.Media.SIZE, fileSize);
110                    newOrientation = finalRotationDegrees;
111                    success = true;
112                } catch (FileNotFoundException e) {
113                    Log.w(TAG, "Cannot find file to set exif: " + filePath);
114                } catch (IOException e) {
115                    Log.w(TAG, "Cannot set exif data: " + filePath);
116                }
117            } else {
118                Log.w(TAG, "Cannot build tag: " + ExifInterface.TAG_ORIENTATION);
119            }
120        }
121
122        PhotoData result = null;
123        if (success) {
124            // MediaStore using SQLite is thread safe.
125            values.put(Images.Media.ORIENTATION, finalRotationDegrees);
126            mContext.getContentResolver().update(imageData.getUri(),
127                    values, null, null);
128            double[] latLong = data.getLatLong();
129            double latitude = 0;
130            double longitude = 0;
131            if (latLong != null) {
132                latitude = latLong[0];
133                longitude = latLong[1];
134            }
135
136            result = new PhotoData(data.getContentId(), data.getTitle(),
137                    data.getMimeType(), data.getDateTaken(), data.getDateModified(),
138                    data.getPath(), newOrientation, imageData.getWidth(),
139                    imageData.getHeight(), data.getSizeInBytes(), latitude, longitude);
140        }
141
142        return result;
143    }
144
145    @Override
146    protected void onPostExecute(LocalData result) {
147        mProgress.dismiss();
148        if (result != null) {
149            mAdapter.updateData(mCurrentDataId, result);
150        }
151    }
152}
153