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