SaveCopyTask.java revision 0addfc7f6342184a67cdd8b5cc3872c6a5c87e55
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.filtershow.tools;
18
19import android.content.ContentResolver;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.graphics.Bitmap;
24import android.graphics.Bitmap.CompressFormat;
25import android.net.Uri;
26import android.os.AsyncTask;
27import android.os.Environment;
28import android.provider.MediaStore.Images;
29import android.provider.MediaStore.Images.ImageColumns;
30import android.view.Gravity;
31import android.widget.Toast;
32
33import com.android.camera.R;
34import com.android.gallery3d.filtershow.presets.ImagePreset;
35
36//import com.android.gallery3d.R;
37//import com.android.gallery3d.util.BucketNames;
38
39import java.io.Closeable;
40import java.io.File;
41import java.io.FileNotFoundException;
42import java.io.FileOutputStream;
43import java.io.IOException;
44import java.io.OutputStream;
45import java.sql.Date;
46import java.text.SimpleDateFormat;
47
48/**
49 * Asynchronous task for saving edited photo as a new copy.
50 */
51public class SaveCopyTask extends AsyncTask<ProcessedBitmap, Void, Uri> {
52
53    private static final int DEFAULT_COMPRESS_QUALITY = 95;
54    private static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
55
56    /**
57     * Saves the bitmap in the final destination
58     */
59    public static void saveBitmap(Bitmap bitmap, File destination) {
60        OutputStream os = null;
61        try {
62            os = new FileOutputStream(destination);
63            bitmap.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, os);
64        } catch (FileNotFoundException e) {
65            e.printStackTrace();
66        } finally {
67            closeStream(os);
68        }
69    }
70
71    private static void closeStream(Closeable stream) {
72        if (stream != null) {
73            try {
74                stream.close();
75            } catch (IOException e) {
76                e.printStackTrace();
77            }
78        }
79    }
80
81    /**
82     * Callback for the completed asynchronous task.
83     */
84    public interface Callback {
85
86        void onComplete(Uri result);
87    }
88
89    private interface ContentResolverQueryCallback {
90
91        void onCursorResult(Cursor cursor);
92    }
93
94    private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
95
96    private final Context context;
97    private final Uri sourceUri;
98    private final Callback callback;
99    private final String saveFileName;
100    private final File destinationFile;
101
102    public SaveCopyTask(Context context, Uri sourceUri, File destination, Callback callback) {
103        this.context = context;
104        this.sourceUri = sourceUri;
105        this.callback = callback;
106
107        if (destination == null) {
108            this.destinationFile = getNewFile(context, sourceUri);
109        } else {
110            this.destinationFile = destination;
111        }
112
113        saveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(
114                System.currentTimeMillis()));
115    }
116
117    public static File getFinalSaveDirectory(Context context, Uri sourceUri) {
118        File saveDirectory = getSaveDirectory(context, sourceUri);
119        if ((saveDirectory == null) || !saveDirectory.canWrite()) {
120            saveDirectory = new File(Environment.getExternalStorageDirectory(),
121                    DEFAULT_SAVE_DIRECTORY);
122        }
123        return saveDirectory;
124    }
125
126    public static File getNewFile(Context context, Uri sourceUri) {
127        File saveDirectory = getFinalSaveDirectory(context, sourceUri);
128        String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(
129                System.currentTimeMillis()));
130        return new File(saveDirectory, filename + ".JPG");
131    }
132
133    /**
134     * The task should be executed with one given bitmap to be saved.
135     */
136    @Override
137    protected Uri doInBackground(ProcessedBitmap... params) {
138        // TODO: Support larger dimensions for photo saving.
139        if (params[0] == null) {
140            return null;
141        }
142
143        ProcessedBitmap processedBitmap = params[0];
144
145        Bitmap bitmap = processedBitmap.apply();
146        saveBitmap(bitmap, this.destinationFile);
147
148        Uri uri = insertContent(context, sourceUri, this.destinationFile, saveFileName);
149        bitmap.recycle();
150        return uri;
151    }
152
153    @Override
154    protected void onPostExecute(Uri result) {
155        if (callback != null) {
156            callback.onComplete(result);
157        }
158    }
159
160    private static void querySource(Context context, Uri sourceUri, String[] projection,
161            ContentResolverQueryCallback callback) {
162        ContentResolver contentResolver = context.getContentResolver();
163        Cursor cursor = null;
164        try {
165            cursor = contentResolver.query(sourceUri, projection, null, null,
166                    null);
167            if ((cursor != null) && cursor.moveToNext()) {
168                callback.onCursorResult(cursor);
169            }
170        } catch (Exception e) {
171            // Ignore error for lacking the data column from the source.
172        } finally {
173            if (cursor != null) {
174                cursor.close();
175            }
176        }
177    }
178
179    private static File getSaveDirectory(Context context, Uri sourceUri) {
180        final File[] dir = new File[1];
181        querySource(context, sourceUri, new String[] {
182                ImageColumns.DATA
183        },
184                new ContentResolverQueryCallback() {
185
186                    @Override
187                    public void onCursorResult(Cursor cursor) {
188                        dir[0] = new File(cursor.getString(0)).getParentFile();
189                    }
190                });
191        return dir[0];
192    }
193
194    /**
195     * Insert the content (saved file) with proper source photo properties.
196     */
197    public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName) {
198        long now = System.currentTimeMillis() / 1000;
199
200        final ContentValues values = new ContentValues();
201        values.put(Images.Media.TITLE, saveFileName);
202        values.put(Images.Media.DISPLAY_NAME, file.getName());
203        values.put(Images.Media.MIME_TYPE, "image/jpeg");
204        values.put(Images.Media.DATE_TAKEN, now);
205        values.put(Images.Media.DATE_MODIFIED, now);
206        values.put(Images.Media.DATE_ADDED, now);
207        values.put(Images.Media.ORIENTATION, 0);
208        values.put(Images.Media.DATA, file.getAbsolutePath());
209        values.put(Images.Media.SIZE, file.length());
210
211        String[] projection = new String[] {
212                ImageColumns.DATE_TAKEN,
213                ImageColumns.LATITUDE, ImageColumns.LONGITUDE,
214        };
215        querySource(context, sourceUri, projection, new ContentResolverQueryCallback() {
216
217            @Override
218            public void onCursorResult(Cursor cursor) {
219                values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
220
221                double latitude = cursor.getDouble(1);
222                double longitude = cursor.getDouble(2);
223                // TODO: Change || to && after the default location issue is
224                // fixed.
225                if ((latitude != 0f) || (longitude != 0f)) {
226                    values.put(Images.Media.LATITUDE, latitude);
227                    values.put(Images.Media.LONGITUDE, longitude);
228                }
229            }
230        });
231
232        return context.getContentResolver().insert(
233                Images.Media.EXTERNAL_CONTENT_URI, values);
234    }
235
236}
237