1/*
2 * Copyright (C) 2010 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.gallery3d.photoeditor;
18
19import android.content.ContentResolver;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.graphics.Bitmap;
24import android.net.Uri;
25import android.os.AsyncTask;
26import android.os.Environment;
27import android.provider.MediaStore.Images;
28import android.provider.MediaStore.Images.ImageColumns;
29import android.view.Gravity;
30import android.widget.Toast;
31
32import com.android.gallery3d.R;
33
34import java.io.File;
35import java.sql.Date;
36import java.text.SimpleDateFormat;
37
38/**
39 * Asynchronous task for saving edited photo as a new copy.
40 */
41public class SaveCopyTask extends AsyncTask<Bitmap, Void, Uri> {
42
43    /**
44     * Callback for the completed asynchronous task.
45     */
46    public interface Callback {
47
48        void onComplete(Uri result);
49    }
50
51    private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
52    private static final int INDEX_DATE_TAKEN = 0;
53    private static final int INDEX_LATITUDE = 1;
54    private static final int INDEX_LONGITUDE = 2;
55
56    private static final String[] IMAGE_PROJECTION = new String[] {
57        ImageColumns.DATE_TAKEN,
58        ImageColumns.LATITUDE,
59        ImageColumns.LONGITUDE,
60    };
61
62    private final Context context;
63    private final Uri sourceUri;
64    private final Callback callback;
65    private final String albumName;
66    private final String saveFileName;
67
68    public SaveCopyTask(Context context, Uri sourceUri, Callback callback) {
69        this.context = context;
70        this.sourceUri = sourceUri;
71        this.callback = callback;
72
73        albumName = context.getString(R.string.edited_photo_bucket_name);
74        saveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(
75                new Date(System.currentTimeMillis()));
76    }
77
78    /**
79     * The task should be executed with one given bitmap to be saved.
80     */
81    @Override
82    protected Uri doInBackground(Bitmap... params) {
83        // TODO: Support larger dimensions for photo saving.
84        if (params[0] == null) {
85            return null;
86        }
87        Bitmap bitmap = params[0];
88        File file = save(bitmap);
89        Uri uri = (file != null) ? insertContent(file) : null;
90        bitmap.recycle();
91        return uri;
92    }
93
94    @Override
95    protected void onPostExecute(Uri result) {
96        String message = (result == null) ? context.getString(R.string.saving_failure)
97                : context.getString(R.string.photo_saved, albumName);
98        Toast toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
99        toast.setGravity(Gravity.CENTER, 0, 0);
100        toast.show();
101
102        callback.onComplete(result);
103    }
104
105    private File save(Bitmap bitmap) {
106        String directory = Environment.getExternalStorageDirectory().toString() + "/" + albumName;
107        return new BitmapUtils(context).saveBitmap(
108                bitmap, directory, saveFileName, Bitmap.CompressFormat.JPEG);
109    }
110
111    /**
112     * Insert the content (saved file) with proper source photo properties.
113     */
114    private Uri insertContent(File file) {
115        long now = System.currentTimeMillis() / 1000;
116        long dateTaken = now;
117        double latitude = 0f;
118        double longitude = 0f;
119
120        ContentResolver contentResolver = context.getContentResolver();
121        Cursor cursor = null;
122        try {
123            cursor = contentResolver.query(sourceUri, IMAGE_PROJECTION, null, null, null);
124            if ((cursor != null) && cursor.moveToNext()) {
125                dateTaken = cursor.getLong(INDEX_DATE_TAKEN);
126                latitude = cursor.getDouble(INDEX_LATITUDE);
127                longitude = cursor.getDouble(INDEX_LONGITUDE);
128            }
129        } catch (Exception e) {
130            // Ignore error for lacking property columns from the source.
131        } finally {
132            if (cursor != null) {
133                cursor.close();
134            }
135        }
136
137        ContentValues values = new ContentValues();
138        values.put(Images.Media.TITLE, saveFileName);
139        values.put(Images.Media.DISPLAY_NAME, file.getName());
140        values.put(Images.Media.MIME_TYPE, "image/jpeg");
141        values.put(Images.Media.DATE_TAKEN, dateTaken);
142        values.put(Images.Media.DATE_MODIFIED, now);
143        values.put(Images.Media.DATE_ADDED, now);
144        values.put(Images.Media.ORIENTATION, 0);
145        values.put(Images.Media.DATA, file.getAbsolutePath());
146        values.put(Images.Media.SIZE, file.length());
147
148        // TODO: Change || to && after the default location issue is fixed.
149        if ((latitude != 0f) || (longitude != 0f)) {
150            values.put(Images.Media.LATITUDE, latitude);
151            values.put(Images.Media.LONGITUDE, longitude);
152        }
153        return contentResolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);
154    }
155}
156