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.app.Service; 20 import android.content.ContentResolver; 21 import android.content.ContentValues; 22 import android.content.Intent; 23 import android.location.Location; 24 import android.net.Uri; 25 import android.os.AsyncTask; 26 import android.os.Binder; 27 import android.os.IBinder; 28 import android.provider.MediaStore.Video; 29 import android.util.Log; 30 31 import com.android.gallery3d.exif.ExifInterface; 32 33 import java.io.File; 34 35 /* 36 * Service for saving images in the background thread. 37 */ 38 public class MediaSaveService extends Service { 39 private static final int SAVE_TASK_LIMIT = 3; 40 private static final String TAG = MediaSaveService.class.getSimpleName(); 41 42 private final IBinder mBinder = new LocalBinder(); 43 private int mTaskNumber; 44 private Listener mListener; 45 46 interface Listener { 47 48 public void onQueueStatus(boolean full); 49 } 50 51 interface OnMediaSavedListener { 52 public void onMediaSaved(Uri uri); 53 } 54 55 class LocalBinder extends Binder { 56 public MediaSaveService getService() { 57 return MediaSaveService.this; 58 } 59 } 60 61 @Override 62 public IBinder onBind(Intent intent) { 63 return mBinder; 64 } 65 66 @Override 67 public int onStartCommand(Intent intent, int flag, int startId) { 68 return START_STICKY; 69 } 70 71 @Override 72 public void onDestroy() { 73 } 74 75 @Override 76 public void onCreate() { 77 mTaskNumber = 0; 78 } 79 80 public boolean isQueueFull() { 81 return (mTaskNumber >= SAVE_TASK_LIMIT); 82 } 83 84 // Runs in main thread 85 public void addImage(final byte[] data, String title, long date, Location loc, 86 int width, int height, int orientation, ExifInterface exif, 87 OnMediaSavedListener l, ContentResolver resolver) { 88 if (isQueueFull()) { 89 Log.e(TAG, "Cannot add image when the queue is full"); 90 return; 91 } 92 ImageSaveTask t = new ImageSaveTask(data, title, date, 93 (loc == null) ? null : new Location(loc), 94 width, height, orientation, exif, resolver, l); 95 96 mTaskNumber++; 97 if (isQueueFull()) { 98 onQueueFull(); 99 } 100 t.execute(); 101 } 102 103 public void addVideo(String path, long duration, ContentValues values, 104 OnMediaSavedListener l, ContentResolver resolver) { 105 // We don't set a queue limit for video saving because the file 106 // is already in the storage. Only updating the database. 107 new VideoSaveTask(path, duration, values, l, resolver).execute(); 108 } 109 110 public void setListener(Listener l) { 111 mListener = l; 112 if (l == null) return; 113 l.onQueueStatus(isQueueFull()); 114 } 115 116 private void onQueueFull() { 117 if (mListener != null) mListener.onQueueStatus(true); 118 } 119 120 private void onQueueAvailable() { 121 if (mListener != null) mListener.onQueueStatus(false); 122 } 123 124 private class ImageSaveTask extends AsyncTask <Void, Void, Uri> { 125 private byte[] data; 126 private String title; 127 private long date; 128 private Location loc; 129 private int width, height; 130 private int orientation; 131 private ExifInterface exif; 132 private ContentResolver resolver; 133 private OnMediaSavedListener listener; 134 135 public ImageSaveTask(byte[] data, String title, long date, Location loc, 136 int width, int height, int orientation, ExifInterface exif, 137 ContentResolver resolver, OnMediaSavedListener listener) { 138 this.data = data; 139 this.title = title; 140 this.date = date; 141 this.loc = loc; 142 this.width = width; 143 this.height = height; 144 this.orientation = orientation; 145 this.exif = exif; 146 this.resolver = resolver; 147 this.listener = listener; 148 } 149 150 @Override 151 protected void onPreExecute() { 152 // do nothing. 153 } 154 155 @Override 156 protected Uri doInBackground(Void... v) { 157 return Storage.addImage( 158 resolver, title, date, loc, orientation, exif, data, width, height); 159 } 160 161 @Override 162 protected void onPostExecute(Uri uri) { 163 if (listener != null) listener.onMediaSaved(uri); 164 mTaskNumber--; 165 if (mTaskNumber == SAVE_TASK_LIMIT - 1) onQueueAvailable(); 166 } 167 } 168 169 private class VideoSaveTask extends AsyncTask <Void, Void, Uri> { 170 private String path; 171 private long duration; 172 private ContentValues values; 173 private OnMediaSavedListener listener; 174 private ContentResolver resolver; 175 176 public VideoSaveTask(String path, long duration, ContentValues values, 177 OnMediaSavedListener l, ContentResolver r) { 178 this.path = path; 179 this.duration = duration; 180 this.values = new ContentValues(values); 181 this.listener = l; 182 this.resolver = r; 183 } 184 185 @Override 186 protected void onPreExecute() { 187 // do nothing. 188 } 189 190 @Override 191 protected Uri doInBackground(Void... v) { 192 values.put(Video.Media.SIZE, new File(path).length()); 193 values.put(Video.Media.DURATION, duration); 194 Uri uri = null; 195 try { 196 Uri videoTable = Uri.parse("content://media/external/video/media"); 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( 203 Video.Media.DATA); 204 if (new File(path).renameTo(new File(finalName))) { 205 path = finalName; 206 } 207 208 resolver.update(uri, values, null, null); 209 } catch (Exception e) { 210 // We failed to insert into the database. This can happen if 211 // the SD card is unmounted. 212 Log.e(TAG, "failed to add video to media store", e); 213 uri = null; 214 } finally { 215 Log.v(TAG, "Current video URI: " + uri); 216 } 217 return uri; 218 } 219 220 @Override 221 protected void onPostExecute(Uri uri) { 222 if (listener != null) listener.onMediaSaved(uri); 223 } 224 } 225 } 226