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