blob: e8ec08d57211ba52e006bb643a971515b57fea11 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera;
import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
import android.provider.MediaStore.Video;
import android.util.Log;
import com.android.camera.exif.ExifInterface;
import java.io.File;
/*
* Service for saving images in the background thread.
*/
public class MediaSaveService extends Service {
public static final String VIDEO_BASE_URI = "content://media/external/video/media";
// The memory limit for unsaved image is 20MB.
private static final int SAVE_TASK_MEMORY_LIMIT = 20 * 1024 * 1024;
private static final String TAG = "CAM_" + MediaSaveService.class.getSimpleName();
private final IBinder mBinder = new LocalBinder();
private Listener mListener;
// Memory used by the total queued save request, in bytes.
private long mMemoryUse;
public interface Listener {
public void onQueueStatus(boolean full);
}
public interface OnMediaSavedListener {
public void onMediaSaved(Uri uri);
}
class LocalBinder extends Binder {
public MediaSaveService getService() {
return MediaSaveService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public int onStartCommand(Intent intent, int flag, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
}
@Override
public void onCreate() {
mMemoryUse = 0;
}
public boolean isQueueFull() {
return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
}
public void addImage(final byte[] data, String title, long date, Location loc,
int width, int height, int orientation, ExifInterface exif,
OnMediaSavedListener l, ContentResolver resolver) {
if (isQueueFull()) {
Log.e(TAG, "Cannot add image when the queue is full");
return;
}
ImageSaveTask t = new ImageSaveTask(data, title, date,
(loc == null) ? null : new Location(loc),
width, height, orientation, exif, resolver, l);
mMemoryUse += data.length;
if (isQueueFull()) {
onQueueFull();
}
t.execute();
}
public void addImage(final byte[] data, String title, long date, Location loc,
int orientation, ExifInterface exif,
OnMediaSavedListener l, ContentResolver resolver) {
// When dimensions are unknown, pass 0 as width and height,
// and decode image for width and height later in a background thread
addImage(data, title, date, loc, 0, 0, orientation, exif, l, resolver);
}
public void addImage(final byte[] data, String title, Location loc,
int width, int height, int orientation, ExifInterface exif,
OnMediaSavedListener l, ContentResolver resolver) {
addImage(data, title, System.currentTimeMillis(), loc, width, height,
orientation, exif, l, resolver);
}
public void addVideo(String path, long duration, ContentValues values,
OnMediaSavedListener l, ContentResolver resolver) {
// We don't set a queue limit for video saving because the file
// is already in the storage. Only updating the database.
new VideoSaveTask(path, duration, values, l, resolver).execute();
}
public void setListener(Listener l) {
mListener = l;
if (l == null) return;
l.onQueueStatus(isQueueFull());
}
private void onQueueFull() {
if (mListener != null) mListener.onQueueStatus(true);
}
private void onQueueAvailable() {
if (mListener != null) mListener.onQueueStatus(false);
}
private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
private byte[] data;
private String title;
private long date;
private Location loc;
private int width, height;
private int orientation;
private ExifInterface exif;
private ContentResolver resolver;
private OnMediaSavedListener listener;
public ImageSaveTask(byte[] data, String title, long date, Location loc,
int width, int height, int orientation, ExifInterface exif,
ContentResolver resolver, OnMediaSavedListener listener) {
this.data = data;
this.title = title;
this.date = date;
this.loc = loc;
this.width = width;
this.height = height;
this.orientation = orientation;
this.exif = exif;
this.resolver = resolver;
this.listener = listener;
}
@Override
protected void onPreExecute() {
// do nothing.
}
@Override
protected Uri doInBackground(Void... v) {
if (width == 0 || height == 0) {
// Decode bounds
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
width = options.outWidth;
height = options.outHeight;
}
return Storage.addImage(
resolver, title, date, loc, orientation, exif, data, width, height);
}
@Override
protected void onPostExecute(Uri uri) {
if (listener != null) listener.onMediaSaved(uri);
boolean previouslyFull = isQueueFull();
mMemoryUse -= data.length;
if (isQueueFull() != previouslyFull) onQueueAvailable();
}
}
private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {
private String path;
private long duration;
private ContentValues values;
private OnMediaSavedListener listener;
private ContentResolver resolver;
public VideoSaveTask(String path, long duration, ContentValues values,
OnMediaSavedListener l, ContentResolver r) {
this.path = path;
this.duration = duration;
this.values = new ContentValues(values);
this.listener = l;
this.resolver = r;
}
@Override
protected Uri doInBackground(Void... v) {
values.put(Video.Media.SIZE, new File(path).length());
values.put(Video.Media.DURATION, duration);
Uri uri = null;
try {
Uri videoTable = Uri.parse(VIDEO_BASE_URI);
uri = resolver.insert(videoTable, values);
// Rename the video file to the final name. This avoids other
// apps reading incomplete data. We need to do it after we are
// certain that the previous insert to MediaProvider is completed.
String finalName = values.getAsString(
Video.Media.DATA);
if (new File(path).renameTo(new File(finalName))) {
path = finalName;
}
resolver.update(uri, values, null, null);
} catch (Exception e) {
// We failed to insert into the database. This can happen if
// the SD card is unmounted.
Log.e(TAG, "failed to add video to media store", e);
uri = null;
} finally {
Log.v(TAG, "Current video URI: " + uri);
}
return uri;
}
@Override
protected void onPostExecute(Uri uri) {
if (listener != null) listener.onMediaSaved(uri);
}
}
}