blob: d0bc9ce311409f5dc56c21e155f07a12076735ac [file] [log] [blame]
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -08001/*
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
17package com.android.camera.session;
18
19import android.content.ContentResolver;
Seth Raphael455ba5a2014-02-13 15:10:06 -080020import android.graphics.BitmapFactory;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080021import android.location.Location;
22import android.net.Uri;
Seth Raphael455ba5a2014-02-13 15:10:06 -080023import android.os.AsyncTask;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080024import android.os.Handler;
25import android.os.Looper;
26
27import com.android.camera.app.MediaSaver;
28import com.android.camera.app.MediaSaver.OnMediaSavedListener;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080029import com.android.camera.data.LocalData;
Angus Kong2bca2102014-03-11 16:27:30 -070030import com.android.camera.debug.Log;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080031import com.android.camera.exif.ExifInterface;
Seth Raphael455ba5a2014-02-13 15:10:06 -080032import com.android.camera.util.FileUtil;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080033
Sascha Haeberlinga86b0482014-01-22 18:45:43 -080034import java.io.File;
35import java.io.IOException;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080036import java.util.HashMap;
Sascha Haeberling4a400d72014-03-21 10:41:04 -070037import java.util.HashSet;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080038import java.util.LinkedList;
39import java.util.Map;
40
41/**
42 * Implementation for the {@link CaptureSessionManager}.
43 */
44public class CaptureSessionManagerImpl implements CaptureSessionManager {
45
Angus Kong2bca2102014-03-11 16:27:30 -070046 private static final Log.Tag TAG = new Log.Tag("CaptureSessMgrImpl");
Seth Raphael455ba5a2014-02-13 15:10:06 -080047 public static final String TEMP_SESSIONS = "TEMP_SESSIONS";
48
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080049 private class CaptureSessionImpl implements CaptureSession {
50 /** A URI of the item being processed. */
51 private Uri mUri;
52 /** The title of the item being processed. */
53 private final String mTitle;
Sascha Haeberling93be42a2014-03-05 16:03:36 -080054 /** The location this session was created at. Used for media store.*/
55 private Location mLocation;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080056 /** The current progress of this session in percent. */
57 private int mProgressPercent = 0;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080058 /** A message ID for the current progress state. */
59 private CharSequence mProgressMessage;
60 /** A place holder for this capture session. */
61 private PlaceholderManager.Session mPlaceHolderSession;
Seth Raphael93c6b612014-03-13 11:01:26 -070062 private Uri mContentUri;
Sascha Haeberling4a400d72014-03-21 10:41:04 -070063 /** These listeners get informed about progress updates. */
64 private final HashSet<ProgressListener> mProgressListeners =
65 new HashSet<ProgressListener>();
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080066
Sascha Haeberling93be42a2014-03-05 16:03:36 -080067 /**
68 * Creates a new {@link CaptureSession}.
69 *
70 * @param title the title of this session.
71 * @param location the location of this session, used for media store.
72 */
73 private CaptureSessionImpl(String title, Location location) {
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080074 mTitle = title;
Sascha Haeberling93be42a2014-03-05 16:03:36 -080075 mLocation = location;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080076 }
77
78 @Override
Sascha Haeberling298c0662013-12-20 16:45:53 -080079 public String getTitle() {
80 return mTitle;
81 }
82
83 @Override
Sascha Haeberling93be42a2014-03-05 16:03:36 -080084 public Location getLocation() {
85 return mLocation;
86 }
87
88 @Override
89 public void setLocation(Location location) {
90 mLocation = location;
91 }
92
93 @Override
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -080094 public synchronized void setProgress(int percent) {
95 mProgressPercent = percent;
96 notifyTaskProgress(mUri, mProgressPercent);
Sascha Haeberling4a400d72014-03-21 10:41:04 -070097 for (ProgressListener listener : mProgressListeners) {
98 listener.onProgressChanged(percent);
99 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800100 }
101
102 @Override
103 public synchronized int getProgress() {
104 return mProgressPercent;
105 }
106
107 @Override
108 public synchronized CharSequence getProgressMessage() {
109 return mProgressMessage;
110 }
111
112 @Override
113 public synchronized void setProgressMessage(CharSequence message) {
114 mProgressMessage = message;
Carlos Hernandezcbd058a2014-03-25 12:26:19 -0700115 notifyTaskProgressText(mUri, message);
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700116 for (ProgressListener listener : mProgressListeners) {
117 listener.onStatusMessageChanged(message);
118 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800119 }
120
121 @Override
122 public synchronized void startSession(byte[] placeholder, CharSequence progressMessage) {
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800123 mProgressMessage = progressMessage;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800124
125 final long now = System.currentTimeMillis();
126 // TODO: This needs to happen outside the UI thread.
127 mPlaceHolderSession = mPlaceholderManager.insertPlaceholder(mTitle, placeholder, now);
128 mUri = mPlaceHolderSession.outputUri;
129 mSessions.put(mUri.toString(), this);
130 notifyTaskQueued(mUri);
131 }
132
133 @Override
134 public synchronized void startSession(Uri uri, CharSequence progressMessage) {
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800135 mUri = uri;
136 mProgressMessage = progressMessage;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800137 mPlaceHolderSession = mPlaceholderManager.convertToPlaceholder(uri);
138
139 mSessions.put(mUri.toString(), this);
140 notifyTaskQueued(mUri);
141 }
142
143 @Override
144 public synchronized void cancel() {
145 if (mUri != null) {
146 removeSession(mUri.toString());
147 }
148 }
149
150 @Override
Sascha Haeberling93be42a2014-03-05 16:03:36 -0800151 public synchronized void saveAndFinish(byte[] data, int width, int height, int orientation,
152 ExifInterface exif, OnMediaSavedListener listener) {
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800153 if (mPlaceHolderSession == null) {
154 throw new IllegalStateException(
155 "Cannot call saveAndFinish without calling startSession first.");
156 }
157
158 // TODO: This needs to happen outside the UI thread.
Seth Raphael93c6b612014-03-13 11:01:26 -0700159 mContentUri = mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation,
160 orientation, exif, data, width, height, LocalData.MIME_TYPE_JPEG);
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800161
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800162 removeSession(mUri.toString());
163 notifyTaskDone(mPlaceHolderSession.outputUri);
164 }
165
166 @Override
167 public void finish() {
168 if (mPlaceHolderSession == null) {
169 throw new IllegalStateException(
170 "Cannot call finish without calling startSession first.");
171 }
172
Seth Raphael455ba5a2014-02-13 15:10:06 -0800173 final String path = this.getPath();
174
175 AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
176 @Override
177 public void run() {
178 byte[] jpegDataTemp;
179 try {
180 jpegDataTemp = FileUtil.readFileToByteArray(new File(path));
181 } catch (IOException e) {
182 return;
183 }
184 final byte[] jpegData = jpegDataTemp;
185
Seth Raphael455ba5a2014-02-13 15:10:06 -0800186 BitmapFactory.Options options = new BitmapFactory.Options();
187 options.inJustDecodeBounds = true;
188 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options);
189 int width = options.outWidth;
190 int height = options.outHeight;
191 int rotation = 0;
192 ExifInterface exif = null;
193 try {
194 exif = new ExifInterface();
195 exif.readExif(jpegData);
196 } catch (IOException e) {
197 Log.w(TAG, "Could not read exif", e);
198 exif = null;
199 }
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700200 CaptureSessionImpl.this.saveAndFinish(jpegData, width, height, rotation, exif,
201 null);
Seth Raphael455ba5a2014-02-13 15:10:06 -0800202 }
203 });
204
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800205 }
206
207 @Override
208 public String getPath() {
209 if (mUri == null) {
210 throw new IllegalStateException("Cannot retrieve URI of not started session.");
211 }
Seth Raphael455ba5a2014-02-13 15:10:06 -0800212
213 File tempDirectory = null;
214 try {
215 tempDirectory = new File(
216 getSessionDirectory(TEMP_SESSIONS), mTitle);
217 } catch (IOException e) {
218 Log.e(TAG, "Could not get temp session directory", e);
219 throw new RuntimeException("Could not get temp session directory", e);
220 }
221 tempDirectory.mkdirs();
222 File tempFile = new File(tempDirectory, mTitle + ".jpg");
223 try {
224 if (!tempFile.exists()) {
225 tempFile.createNewFile();
226 }
227 } catch (IOException e) {
228 Log.e(TAG, "Could not create temp session file", e);
229 throw new RuntimeException("Could not create temp session file", e);
230 }
231 return tempFile.getPath();
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800232 }
233
234 @Override
Sascha Haeberling298c0662013-12-20 16:45:53 -0800235 public Uri getUri() {
236 return mUri;
237 }
238
239 @Override
Seth Raphael93c6b612014-03-13 11:01:26 -0700240 public Uri getContentUri() {
241 return mContentUri;
242 }
243
244 @Override
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800245 public boolean hasPath() {
246 return mUri != null;
247 }
248
249 @Override
Angus Kong571a8c32014-03-13 12:53:03 -0700250 public void onPreviewAvailable() {
251 notifySessionPreviewAvailable(mPlaceHolderSession.outputUri);
252 }
253
254 @Override
255 public void updatePreview(String previewPath) {
Seth Raphael455ba5a2014-02-13 15:10:06 -0800256
Seth Raphael455ba5a2014-02-13 15:10:06 -0800257 final String path = this.getPath();
258
259 AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
260 @Override
261 public void run() {
262 byte[] jpegDataTemp;
263 try {
264 jpegDataTemp = FileUtil.readFileToByteArray(new File(path));
265 } catch (IOException e) {
266 return;
267 }
268 final byte[] jpegData = jpegDataTemp;
269
270 BitmapFactory.Options options = new BitmapFactory.Options();
271 options.inJustDecodeBounds = true;
272 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options);
273 int width = options.outWidth;
274 int height = options.outHeight;
275
276 mPlaceholderManager.replacePlaceholder(mPlaceHolderSession, jpegData, width, height);
Angus Kong571a8c32014-03-13 12:53:03 -0700277 onPreviewAvailable();
Seth Raphael455ba5a2014-02-13 15:10:06 -0800278 }
279 });
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800280 }
Sascha Haeberling597c1a02014-03-06 11:46:06 -0800281
282 @Override
283 public void finishWithFailure(CharSequence reason) {
284 if (mPlaceHolderSession == null) {
285 throw new IllegalStateException(
286 "Cannot call finish without calling startSession first.");
287 }
288 mProgressMessage = reason;
289
Sascha Haeberling597c1a02014-03-06 11:46:06 -0800290 removeSession(mUri.toString());
291 mFailedSessionMessages.put(mPlaceHolderSession.outputUri, reason);
292 notifyTaskFailed(mPlaceHolderSession.outputUri, reason);
293 }
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700294
295 @Override
296 public void addProgressListener(ProgressListener listener) {
297 listener.onStatusMessageChanged(mProgressMessage);
298 listener.onProgressChanged(mProgressPercent);
299 mProgressListeners.add(listener);
300 }
301
302 @Override
303 public void removeProgressListener(ProgressListener listener) {
304 mProgressListeners.remove(listener);
305 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800306 }
307
308 private final MediaSaver mMediaSaver;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800309 private final PlaceholderManager mPlaceholderManager;
Sascha Haeberlinga86b0482014-01-22 18:45:43 -0800310 private final SessionStorageManager mSessionStorageManager;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800311 private final ContentResolver mContentResolver;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800312
Sascha Haeberling597c1a02014-03-06 11:46:06 -0800313 /** Failed session messages. Uri -> message. */
314 private final HashMap<Uri, CharSequence> mFailedSessionMessages =
315 new HashMap<Uri, CharSequence>();
316
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800317 /**
318 * We use this to fire events to the session listeners from the main thread.
319 */
320 private final Handler mMainHandler = new Handler(Looper.getMainLooper());
321
322 /** Sessions in progress, keyed by URI. */
323 private final Map<String, CaptureSession> mSessions;
324
325 /** Listeners interested in task update events. */
326 private final LinkedList<SessionListener> mTaskListeners = new LinkedList<SessionListener>();
327
328 /**
329 * Initializes a new {@link CaptureSessionManager} implementation.
330 *
331 * @param mediaSaver used to store the resulting media item
332 * @param contentResolver required by the media saver
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800333 * @param placeholderManager used to manage placeholders in the filmstrip
334 * before the final result is ready
Sascha Haeberlinga86b0482014-01-22 18:45:43 -0800335 * @param sessionStorageManager used to tell modules where to store
336 * temporary session data
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800337 */
Sascha Haeberling4a400d72014-03-21 10:41:04 -0700338 public CaptureSessionManagerImpl(MediaSaver mediaSaver, ContentResolver contentResolver,
Sascha Haeberlinga86b0482014-01-22 18:45:43 -0800339 PlaceholderManager placeholderManager, SessionStorageManager sessionStorageManager) {
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800340 mSessions = new HashMap<String, CaptureSession>();
341 mMediaSaver = mediaSaver;
342 mContentResolver = contentResolver;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800343 mPlaceholderManager = placeholderManager;
Sascha Haeberlinga86b0482014-01-22 18:45:43 -0800344 mSessionStorageManager = sessionStorageManager;
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800345 }
346
347 @Override
Sascha Haeberling93be42a2014-03-05 16:03:36 -0800348 public CaptureSession createNewSession(String title, Location location) {
349 return new CaptureSessionImpl(title, location);
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800350 }
351
352 @Override
353 public CaptureSession createSession() {
Sascha Haeberling93be42a2014-03-05 16:03:36 -0800354 return new CaptureSessionImpl(null, null);
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800355 }
356
357 @Override
Seth Raphaelcc79da22014-03-20 16:47:53 -0700358 public CaptureSession getSession(Uri sessionUri) {
Seth Raphael0708bb42014-03-21 11:28:02 -0700359 return mSessions.get(sessionUri.toString());
Seth Raphaelcc79da22014-03-20 16:47:53 -0700360 }
361
362 @Override
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800363 public void saveImage(byte[] data, String title, long date, Location loc,
364 int width, int height, int orientation, ExifInterface exif,
365 OnMediaSavedListener listener) {
366 mMediaSaver.addImage(data, title, date, loc, width, height, orientation, exif,
367 listener, mContentResolver);
368 }
369
370 @Override
371 public void addSessionListener(SessionListener listener) {
372 synchronized (mTaskListeners) {
373 mTaskListeners.add(listener);
374 }
375 }
376
377 @Override
378 public void removeSessionListener(SessionListener listener) {
379 synchronized (mTaskListeners) {
380 mTaskListeners.remove(listener);
381 }
382 }
383
384 @Override
Sascha Haeberlinga86b0482014-01-22 18:45:43 -0800385 public File getSessionDirectory(String subDirectory) throws IOException {
386 return mSessionStorageManager.getSessionDirectory(subDirectory);
387 }
388
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800389 private void removeSession(String sessionUri) {
390 mSessions.remove(sessionUri);
391 }
392
393 /**
394 * Notifies all task listeners that the task with the given URI has been
395 * queued.
396 */
397 private void notifyTaskQueued(final Uri uri) {
398 mMainHandler.post(new Runnable() {
399 @Override
400 public void run() {
401 synchronized (mTaskListeners) {
402 for (SessionListener listener : mTaskListeners) {
403 listener.onSessionQueued(uri);
404 }
405 }
406 }
407 });
408 }
409
410 /**
411 * Notifies all task listeners that the task with the given URI has been
412 * finished.
413 */
414 private void notifyTaskDone(final Uri uri) {
415 mMainHandler.post(new Runnable() {
416 @Override
417 public void run() {
418 synchronized (mTaskListeners) {
419 for (SessionListener listener : mTaskListeners) {
420 listener.onSessionDone(uri);
421 }
422 }
423 }
424 });
425 }
426
427 /**
Sascha Haeberling597c1a02014-03-06 11:46:06 -0800428 * Notifies all task listeners that the task with the given URI has been
429 * failed to process.
430 */
431 private void notifyTaskFailed(final Uri uri, final CharSequence reason) {
432 mMainHandler.post(new Runnable() {
433 @Override
434 public void run() {
435 synchronized (mTaskListeners) {
436 for (SessionListener listener : mTaskListeners) {
437 listener.onSessionFailed(uri, reason);
438 }
439 }
440 }
441 });
442 }
443
444 /**
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800445 * Notifies all task listeners that the task with the given URI has
446 * progressed to the given state.
447 */
448 private void notifyTaskProgress(final Uri uri, final int progressPercent) {
449 mMainHandler.post(new Runnable() {
450 @Override
451 public void run() {
452 synchronized (mTaskListeners) {
453 for (SessionListener listener : mTaskListeners) {
454 listener.onSessionProgress(uri, progressPercent);
455 }
456 }
457 }
458 });
459 }
Sascha Haeberling14ff6c82013-12-13 13:29:58 -0800460
461 /**
Carlos Hernandezcbd058a2014-03-25 12:26:19 -0700462 * Notifies all task listeners that the task with the given URI has
463 * changed its progress message.
464 */
465 private void notifyTaskProgressText(final Uri uri, final CharSequence message) {
466 mMainHandler.post(new Runnable() {
467 @Override
468 public void run() {
469 synchronized (mTaskListeners) {
470 for (SessionListener listener : mTaskListeners) {
471 listener.onSessionProgressText(uri, message);
472 }
473 }
474 }
475 });
476 }
477
478 /**
Sascha Haeberling14ff6c82013-12-13 13:29:58 -0800479 * Notifies all task listeners that the task with the given URI has updated
480 * its media.
481 */
Angus Kong571a8c32014-03-13 12:53:03 -0700482 private void notifySessionPreviewAvailable(final Uri uri) {
Sascha Haeberling14ff6c82013-12-13 13:29:58 -0800483 mMainHandler.post(new Runnable() {
484 @Override
485 public void run() {
486 synchronized (mTaskListeners) {
487 for (SessionListener listener : mTaskListeners) {
Angus Kong571a8c32014-03-13 12:53:03 -0700488 listener.onSessionPreviewAvailable(uri);
Sascha Haeberling14ff6c82013-12-13 13:29:58 -0800489 }
490 }
491 }
492 });
493 }
Sascha Haeberling597c1a02014-03-06 11:46:06 -0800494
495 @Override
496 public boolean hasErrorMessage(Uri uri) {
497 return mFailedSessionMessages.containsKey(uri);
498 }
499
500 @Override
501 public CharSequence getErrorMesage(Uri uri) {
502 return mFailedSessionMessages.get(uri);
503 }
504
505 @Override
506 public void removeErrorMessage(Uri uri) {
507 mFailedSessionMessages.remove(uri);
508 }
Sascha Haeberlinga63dbb62013-11-22 11:55:32 -0800509}