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 17 package com.android.camera; 18 19 import android.content.ContentResolver; 20 import android.content.ContentValues; 21 import android.graphics.BitmapFactory; 22 import android.location.Location; 23 import android.net.Uri; 24 import android.os.AsyncTask; 25 import android.provider.MediaStore.Video; 26 27 import com.android.camera.app.MediaSaver; 28 import com.android.camera.debug.Log; 29 import com.android.camera.exif.ExifInterface; 30 31 import java.io.File; 32 33 /** 34 * A class implementing {@link com.android.camera.app.MediaSaver}. 35 */ 36 public class MediaSaverImpl implements MediaSaver { 37 private static final Log.Tag TAG = new Log.Tag("MediaSaverImpl"); 38 private static final String VIDEO_BASE_URI = "content://media/external/video/media"; 39 40 /** The memory limit for unsaved image is 20MB. */ 41 private static final int SAVE_TASK_MEMORY_LIMIT = 20 * 1024 * 1024; 42 43 private QueueListener mQueueListener; 44 45 /** Memory used by the total queued save request, in bytes. */ 46 private long mMemoryUse; 47 MediaSaverImpl()48 public MediaSaverImpl() { 49 mMemoryUse = 0; 50 } 51 52 @Override isQueueFull()53 public boolean isQueueFull() { 54 return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT); 55 } 56 57 @Override addImage(final byte[] data, String title, long date, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver)58 public void addImage(final byte[] data, String title, long date, Location loc, int width, 59 int height, int orientation, ExifInterface exif, OnMediaSavedListener l, 60 ContentResolver resolver) { 61 if (isQueueFull()) { 62 Log.e(TAG, "Cannot add image when the queue is full"); 63 return; 64 } 65 ImageSaveTask t = new ImageSaveTask(data, title, date, 66 (loc == null) ? null : new Location(loc), 67 width, height, orientation, exif, resolver, l); 68 69 mMemoryUse += data.length; 70 if (isQueueFull()) { 71 onQueueFull(); 72 } 73 t.execute(); 74 } 75 76 @Override addImage(final byte[] data, String title, long date, Location loc, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver)77 public void addImage(final byte[] data, String title, long date, Location loc, int orientation, 78 ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver) { 79 // When dimensions are unknown, pass 0 as width and height, 80 // and decode image for width and height later in a background thread 81 addImage(data, title, date, loc, 0, 0, orientation, exif, l, resolver); 82 } 83 @Override addImage(final byte[] data, String title, Location loc, int width, int height, int orientation, ExifInterface exif, OnMediaSavedListener l, ContentResolver resolver)84 public void addImage(final byte[] data, String title, Location loc, int width, int height, 85 int orientation, ExifInterface exif, OnMediaSavedListener l, 86 ContentResolver resolver) { 87 addImage(data, title, System.currentTimeMillis(), loc, width, height, 88 orientation, exif, l, resolver); 89 } 90 91 @Override addVideo(String path, ContentValues values, OnMediaSavedListener l, ContentResolver resolver)92 public void addVideo(String path, ContentValues values, OnMediaSavedListener l, 93 ContentResolver resolver) { 94 // We don't set a queue limit for video saving because the file 95 // is already in the storage. Only updating the database. 96 new VideoSaveTask(path, values, l, resolver).execute(); 97 } 98 99 @Override setQueueListener(QueueListener l)100 public void setQueueListener(QueueListener l) { 101 mQueueListener = l; 102 if (l == null) { 103 return; 104 } 105 l.onQueueStatus(isQueueFull()); 106 } 107 onQueueFull()108 private void onQueueFull() { 109 if (mQueueListener != null) { 110 mQueueListener.onQueueStatus(true); 111 } 112 } 113 onQueueAvailable()114 private void onQueueAvailable() { 115 if (mQueueListener != null) { 116 mQueueListener.onQueueStatus(false); 117 } 118 } 119 120 private class ImageSaveTask extends AsyncTask <Void, Void, Uri> { 121 private final byte[] data; 122 private final String title; 123 private final long date; 124 private final Location loc; 125 private int width, height; 126 private final int orientation; 127 private final ExifInterface exif; 128 private final ContentResolver resolver; 129 private final OnMediaSavedListener listener; 130 ImageSaveTask(byte[] data, String title, long date, Location loc, int width, int height, int orientation, ExifInterface exif, ContentResolver resolver, OnMediaSavedListener listener)131 public ImageSaveTask(byte[] data, String title, long date, Location loc, 132 int width, int height, int orientation, ExifInterface exif, 133 ContentResolver resolver, OnMediaSavedListener listener) { 134 this.data = data; 135 this.title = title; 136 this.date = date; 137 this.loc = loc; 138 this.width = width; 139 this.height = height; 140 this.orientation = orientation; 141 this.exif = exif; 142 this.resolver = resolver; 143 this.listener = listener; 144 } 145 146 @Override onPreExecute()147 protected void onPreExecute() { 148 // do nothing. 149 } 150 151 @Override doInBackground(Void... v)152 protected Uri doInBackground(Void... v) { 153 if (width == 0 || height == 0) { 154 // Decode bounds 155 BitmapFactory.Options options = new BitmapFactory.Options(); 156 options.inJustDecodeBounds = true; 157 BitmapFactory.decodeByteArray(data, 0, data.length, options); 158 width = options.outWidth; 159 height = options.outHeight; 160 } 161 return Storage.addImage( 162 resolver, title, date, loc, orientation, exif, data, width, height); 163 } 164 165 @Override onPostExecute(Uri uri)166 protected void onPostExecute(Uri uri) { 167 if (listener != null && uri != null) { 168 listener.onMediaSaved(uri); 169 } 170 boolean previouslyFull = isQueueFull(); 171 mMemoryUse -= data.length; 172 if (isQueueFull() != previouslyFull) { 173 onQueueAvailable(); 174 } 175 } 176 } 177 178 private class VideoSaveTask extends AsyncTask <Void, Void, Uri> { 179 private String path; 180 private final ContentValues values; 181 private final OnMediaSavedListener listener; 182 private final ContentResolver resolver; 183 VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l, ContentResolver r)184 public VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l, 185 ContentResolver r) { 186 this.path = path; 187 this.values = new ContentValues(values); 188 this.listener = l; 189 this.resolver = r; 190 } 191 192 @Override doInBackground(Void... v)193 protected Uri doInBackground(Void... v) { 194 Uri uri = null; 195 try { 196 Uri videoTable = Uri.parse(VIDEO_BASE_URI); 197 uri = resolver.insert(videoTable, values); 198 199 // Rename the video file to the final name. This avoids other 200 // apps reading incomplete data. We need to do it after we are 201 // certain that the previous insert to MediaProvider is completed. 202 String finalName = values.getAsString(Video.Media.DATA); 203 File finalFile = new File(finalName); 204 if (new File(path).renameTo(finalFile)) { 205 path = finalName; 206 } 207 resolver.update(uri, values, null, null); 208 } catch (Exception e) { 209 // We failed to insert into the database. This can happen if 210 // the SD card is unmounted. 211 Log.e(TAG, "failed to add video to media store", e); 212 uri = null; 213 } finally { 214 Log.v(TAG, "Current video URI: " + uri); 215 } 216 return uri; 217 } 218 219 @Override onPostExecute(Uri uri)220 protected void onPostExecute(Uri uri) { 221 if (listener != null) { 222 listener.onMediaSaved(uri); 223 } 224 } 225 } 226 } 227