CaptureSessionImpl.java revision e7c53cc907f5bb40c8d112830d12ac494e4c68c5
1/* 2 * Copyright (C) 2015 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.graphics.Bitmap; 20import android.graphics.BitmapFactory; 21import android.location.Location; 22import android.net.Uri; 23import android.os.AsyncTask; 24 25import com.android.camera.app.MediaSaver; 26import com.android.camera.data.FilmstripItemData; 27import com.android.camera.debug.Log; 28import com.android.camera.exif.ExifInterface; 29import com.android.camera.stats.CaptureSessionStatsCollector; 30import com.android.camera.util.FileUtil; 31import com.android.camera.util.Size; 32import com.google.common.base.Optional; 33 34import java.io.File; 35import java.io.IOException; 36import java.util.HashSet; 37 38/** 39 * The default implementation of the CaptureSession interface. This is the 40 * implementation we use for normal Camera use. 41 */ 42public class CaptureSessionImpl implements CaptureSession { 43 private static final Log.Tag TAG = new Log.Tag("CaptureSessionImpl"); 44 45 /** The capture session manager responsible for this session. */ 46 private final CaptureSessionManager mSessionManager; 47 /** Used to inform about session status updates. */ 48 private final SessionNotifier mSessionNotifier; 49 /** Used for adding/removing/updating placeholders for in-progress sessions. */ 50 private final PlaceholderManager mPlaceholderManager; 51 /** Used to store images on disk and to add them to the media store. */ 52 private final MediaSaver mMediaSaver; 53 /** The title of the item being processed. */ 54 private final String mTitle; 55 /** These listeners get informed about progress updates. */ 56 private final HashSet<ProgressListener> mProgressListeners = new HashSet<>(); 57 private final long mSessionStartMillis; 58 /** 59 * The file that can be used to write the final JPEG output temporarily, 60 * before it is copied to the final location. 61 */ 62 private final TemporarySessionFile mTempOutputFile; 63 /** Saver that is used to store a stack of images. */ 64 private final StackSaver mStackSaver; 65 /** A URI of the item being processed. */ 66 private Uri mUri; 67 /** The location this session was created at. Used for media store. */ 68 private Location mLocation; 69 /** The current progress of this session in percent. */ 70 private int mProgressPercent = 0; 71 /** A message ID for the current progress state. */ 72 private int mProgressMessageId; 73 /** A place holder for this capture session. */ 74 private PlaceholderManager.Session mPlaceHolderSession; 75 private Uri mContentUri; 76 /** Whether this image was finished. */ 77 private volatile boolean mIsFinished; 78 /** Object that collects logging information through the capture session lifecycle */ 79 private final CaptureSessionStatsCollector mCaptureSessionStatsCollector = new CaptureSessionStatsCollector(); 80 81 /** 82 * Creates a new {@link CaptureSession}. 83 * 84 * @param title the title of this session. 85 * @param sessionStartMillis the timestamp of this capture session (since 86 * epoch). 87 * @param location the location of this session, used for media store. 88 * @param temporarySessionFile used to create a temporary session file if 89 * necessary. 90 * @param captureSessionManager the capture session manager responsible for 91 * this session. 92 * @param placeholderManager used to add/update/remove session placeholders. 93 * @param mediaSaver used to store images on disk and add them to the media 94 * store. 95 * @param stackSaver used to save stacks of images that belong to this 96 * session. 97 */ 98 /* package */CaptureSessionImpl(String title, 99 long sessionStartMillis, Location location, TemporarySessionFile temporarySessionFile, 100 CaptureSessionManager captureSessionManager, SessionNotifier sessionNotifier, 101 PlaceholderManager placeholderManager, MediaSaver mediaSaver, StackSaver stackSaver) { 102 mTitle = title; 103 mSessionStartMillis = sessionStartMillis; 104 mLocation = location; 105 mTempOutputFile = temporarySessionFile; 106 mSessionManager = captureSessionManager; 107 mSessionNotifier = sessionNotifier; 108 mPlaceholderManager = placeholderManager; 109 mMediaSaver = mediaSaver; 110 mStackSaver = stackSaver; 111 mIsFinished = false; 112 } 113 114 @Override 115 public CaptureSessionStatsCollector getCollector() { 116 return mCaptureSessionStatsCollector; 117 } 118 119 @Override 120 public String getTitle() { 121 return mTitle; 122 } 123 124 @Override 125 public Location getLocation() { 126 return mLocation; 127 } 128 129 @Override 130 public void setLocation(Location location) { 131 mLocation = location; 132 } 133 134 @Override 135 public synchronized int getProgress() { 136 return mProgressPercent; 137 } 138 139 @Override 140 public synchronized void setProgress(int percent) { 141 mProgressPercent = percent; 142 mSessionNotifier.notifyTaskProgress(mUri, mProgressPercent); 143 for (ProgressListener listener : mProgressListeners) { 144 listener.onProgressChanged(percent); 145 } 146 } 147 148 @Override 149 public synchronized int getProgressMessageId() { 150 return mProgressMessageId; 151 } 152 153 @Override 154 public synchronized void setProgressMessage(int messageId) { 155 mProgressMessageId = messageId; 156 mSessionNotifier.notifyTaskProgressText(mUri, messageId); 157 for (ProgressListener listener : mProgressListeners) { 158 listener.onStatusMessageChanged(messageId); 159 } 160 } 161 162 @Override 163 public void updateThumbnail(Bitmap bitmap) { 164 mPlaceholderManager.replacePlaceholder(mPlaceHolderSession, bitmap); 165 mSessionNotifier.notifySessionUpdated(mUri); 166 } 167 168 @Override 169 public void updateCaptureIndicatorThumbnail(Bitmap indicator, int rotationDegrees) { 170 onCaptureIndicatorUpdate(indicator, rotationDegrees); 171 } 172 173 @Override 174 public synchronized void startEmpty(Size pictureSize) { 175 if (mIsFinished) { 176 return; 177 } 178 179 mProgressMessageId = -1; 180 mPlaceHolderSession = mPlaceholderManager.insertEmptyPlaceholder(mTitle, pictureSize, 181 mSessionStartMillis); 182 mUri = mPlaceHolderSession.outputUri; 183 mSessionManager.putSession(mUri, this); 184 mSessionNotifier.notifyTaskQueued(mUri); 185 } 186 187 @Override 188 public synchronized void startSession(Bitmap placeholder, int progressMessageId) { 189 if (mIsFinished) { 190 return; 191 } 192 193 mProgressMessageId = progressMessageId; 194 mPlaceHolderSession = mPlaceholderManager.insertPlaceholder(mTitle, placeholder, 195 mSessionStartMillis); 196 mUri = mPlaceHolderSession.outputUri; 197 mSessionManager.putSession(mUri, this); 198 mSessionNotifier.notifyTaskQueued(mUri); 199 onCaptureIndicatorUpdate(placeholder, 0); 200 } 201 202 @Override 203 public synchronized void startSession(byte[] placeholder, int progressMessageId) { 204 if (mIsFinished) { 205 return; 206 } 207 208 mProgressMessageId = progressMessageId; 209 mPlaceHolderSession = mPlaceholderManager.insertPlaceholder(mTitle, placeholder, 210 mSessionStartMillis); 211 mUri = mPlaceHolderSession.outputUri; 212 mSessionManager.putSession(mUri, this); 213 mSessionNotifier.notifyTaskQueued(mUri); 214 Optional<Bitmap> placeholderBitmap = 215 mPlaceholderManager.getPlaceholder(mPlaceHolderSession); 216 if (placeholderBitmap.isPresent()) { 217 onCaptureIndicatorUpdate(placeholderBitmap.get(), 0); 218 } 219 } 220 221 @Override 222 public synchronized void startSession(Uri uri, int progressMessageId) { 223 mUri = uri; 224 mProgressMessageId = progressMessageId; 225 mPlaceHolderSession = mPlaceholderManager.convertToPlaceholder(uri); 226 227 mSessionManager.putSession(mUri, this); 228 mSessionNotifier.notifyTaskQueued(mUri); 229 } 230 231 @Override 232 public synchronized void cancel() { 233 if (isStarted()) { 234 mSessionManager.removeSession(mUri); 235 } 236 } 237 238 @Override 239 public synchronized void saveAndFinish(byte[] data, int width, int height, int orientation, 240 ExifInterface exif, final MediaSaver.OnMediaSavedListener listener) { 241 mIsFinished = true; 242 if (mPlaceHolderSession == null) { 243 mMediaSaver.addImage( 244 data, mTitle, mSessionStartMillis, mLocation, width, height, 245 orientation, exif, listener); 246 return; 247 } 248 try { 249 mContentUri = mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation, 250 orientation, exif, data, width, height, FilmstripItemData.MIME_TYPE_JPEG); 251 mSessionNotifier.notifyTaskDone(mUri); 252 } catch (IOException e) { 253 Log.e(TAG, "IOException", e); 254 // TODO: Replace with a sensisble description 255 // Placeholder string R.string.reason_storage_failure 256 finishWithFailure(-1, true); 257 } 258 } 259 260 @Override 261 public StackSaver getStackSaver() { 262 return mStackSaver; 263 } 264 265 @Override 266 public void finish() { 267 if (mPlaceHolderSession == null) { 268 throw new IllegalStateException( 269 "Cannot call finish without calling startSession first."); 270 } 271 272 mIsFinished = true; 273 AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { 274 @Override 275 public void run() { 276 byte[] jpegDataTemp; 277 if (mTempOutputFile.isUsable()) { 278 try { 279 jpegDataTemp = FileUtil.readFileToByteArray(mTempOutputFile.getFile()); 280 } catch (IOException e) { 281 return; 282 } 283 } else { 284 return; 285 } 286 final byte[] jpegData = jpegDataTemp; 287 288 BitmapFactory.Options options = new BitmapFactory.Options(); 289 options.inJustDecodeBounds = true; 290 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options); 291 int width = options.outWidth; 292 int height = options.outHeight; 293 int rotation = 0; 294 ExifInterface exif = null; 295 try { 296 exif = new ExifInterface(); 297 exif.readExif(jpegData); 298 } catch (IOException e) { 299 Log.w(TAG, "Could not read exif", e); 300 exif = null; 301 } 302 CaptureSessionImpl.this.saveAndFinish(jpegData, width, height, rotation, exif, 303 null); 304 } 305 }); 306 307 } 308 309 @Override 310 public TemporarySessionFile getTempOutputFile() { 311 return mTempOutputFile; 312 } 313 314 @Override 315 public Uri getUri() { 316 return mUri; 317 } 318 319 @Override 320 public void updatePreview() { 321 final File path; 322 if (mTempOutputFile.isUsable()) { 323 path = mTempOutputFile.getFile(); 324 } else { 325 Log.e(TAG, "Cannot update preview"); 326 return; 327 } 328 AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { 329 @Override 330 public void run() { 331 byte[] jpegDataTemp; 332 try { 333 jpegDataTemp = FileUtil.readFileToByteArray(path); 334 } catch (IOException e) { 335 return; 336 } 337 final byte[] jpegData = jpegDataTemp; 338 339 BitmapFactory.Options options = new BitmapFactory.Options(); 340 Bitmap placeholder = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, 341 options); 342 mPlaceholderManager.replacePlaceholder(mPlaceHolderSession, placeholder); 343 mSessionNotifier.notifySessionUpdated(mUri); 344 } 345 }); 346 } 347 348 @Override 349 public void finishWithFailure(int failureMessageId, boolean removeFromFilmstrip) { 350 if (mPlaceHolderSession == null) { 351 throw new IllegalStateException( 352 "Cannot call finish without calling startSession first."); 353 } 354 mProgressMessageId = failureMessageId; 355 mSessionManager.putErrorMessage(mUri, failureMessageId); 356 mSessionNotifier.notifyTaskFailed(mUri, failureMessageId, removeFromFilmstrip); 357 } 358 359 @Override 360 public void addProgressListener(ProgressListener listener) { 361 if (mProgressMessageId > 0) { 362 listener.onStatusMessageChanged(mProgressMessageId); 363 } 364 listener.onProgressChanged(mProgressPercent); 365 mProgressListeners.add(listener); 366 } 367 368 @Override 369 public void removeProgressListener(ProgressListener listener) { 370 mProgressListeners.remove(listener); 371 } 372 373 @Override 374 public void finalize() { 375 mPlaceholderManager.removePlaceholder(mPlaceHolderSession); 376 } 377 378 private void onCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) { 379 mSessionNotifier.notifySessionCaptureIndicatorAvailable(indicator, rotationDegrees); 380 } 381 382 private boolean isStarted() { 383 return mUri != null; 384 } 385} 386