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