import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.provider.MediaStore;
// Make the PhotoItem aware of the session placeholder, to
// allow it to make a smooth transition to its content.
newData.setSessionPlaceholderBitmap(
- Storage.getPlacerHolderForSession(sessionUri));
+ Storage.getPlaceholderForSession(sessionUri));
final int pos = mDataAdapter.findByContentUri(sessionUri);
if (pos == -1) {
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.MediaColumns;
+import android.util.LruCache;
import com.android.camera.data.FilmstripItemData;
import com.android.camera.debug.Log;
import com.android.camera.exif.ExifInterface;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.Size;
+import com.google.common.base.Optional;
import java.io.File;
import java.io.FileOutputStream;
private static final String GOOGLE_COM = "google.com";
private static HashMap<Uri, Uri> sSessionsToContentUris = new HashMap<>();
private static HashMap<Uri, Uri> sContentUrisToSessions = new HashMap<>();
- private static HashMap<Uri, Bitmap> sSessionsToPlaceholderBitmap = new HashMap<>();
+ private static LruCache<Uri, Bitmap> sSessionsToPlaceholderBitmap =
+ // 20MB cache as an upper bound for session bitmap storage
+ new LruCache<Uri, Bitmap>(20 * 1024 * 1024) {
+ @Override
+ protected int sizeOf(Uri key, Bitmap value) {
+ return value.getByteCount();
+ }
+ };
private static HashMap<Uri, Point> sSessionsToSizes = new HashMap<>();
private static HashMap<Uri, Integer> sSessionsToPlaceholderVersions = new HashMap<>();
}
/**
+ * Remove a placeholder from in memory storage.
+ */
+ public static void removePlaceholder(Uri uri) {
+ sSessionsToSizes.remove(uri);
+ sSessionsToPlaceholderBitmap.remove(uri);
+ sSessionsToPlaceholderVersions.remove(uri);
+ }
+
+ /**
* Add or replace placeholder for a new image that does not exist yet.
*
* @param uri the uri of the placeholder to replace, or null if this is a
* @return A URI used to reference this placeholder
*/
public static void replacePlaceholder(Uri uri, Bitmap placeholder) {
+ Log.v(TAG, "session bitmap cache size: " + sSessionsToPlaceholderBitmap.size());
Point size = new Point(placeholder.getWidth(), placeholder.getHeight());
sSessionsToSizes.put(uri, size);
sSessionsToPlaceholderBitmap.put(uri, placeholder);
public static Uri addEmptyPlaceholder(@Nonnull Size size) {
Uri uri = generateUniquePlaceholderUri();
sSessionsToSizes.put(uri, new Point(size.getWidth(), size.getHeight()));
- sSessionsToPlaceholderBitmap.put(uri, null /* Bitmap */);
+ sSessionsToPlaceholderBitmap.remove(uri);
Integer currentVersion = sSessionsToPlaceholderVersions.get(uri);
sSessionsToPlaceholderVersions.put(uri, currentVersion == null ? 0 : currentVersion + 1);
return uri;
* Returns the jpeg bytes for a placeholder session
*
* @param uri the session uri to look up
- * @return The jpeg bytes or null
+ * @return The bitmap or null
*/
- public static Bitmap getPlacerHolderForSession(Uri uri) {
- return sSessionsToPlaceholderBitmap.get(uri);
+ public static Optional<Bitmap> getPlaceholderForSession(Uri uri) {
+ return Optional.fromNullable(sSessionsToPlaceholderBitmap.get(uri));
}
/**
* @param uri the session uri to look up.
* @return the current version int.
*/
- public static int getPlacerHolderVersionForSession(Uri uri) {
+ public static int getPlaceholderVersionForSession(Uri uri) {
return sSessionsToPlaceholderVersions.get(uri);
}
private final PhotoItemFactory mPhotoItemFactory;
- private Bitmap mSessionPlaceholderBitmap;
+ private Optional<Bitmap> mSessionPlaceholderBitmap = Optional.absent();
public PhotoItem(Context context, GlideFilmstripManager manager, FilmstripItemData data,
PhotoItemFactory photoItemFactory) {
*
* @param sessionPlaceholderBitmap a Bitmap to set as a placeholder
*/
- public void setSessionPlaceholderBitmap(Bitmap sessionPlaceholderBitmap) {
+ public void setSessionPlaceholderBitmap(Optional<Bitmap> sessionPlaceholderBitmap) {
mSessionPlaceholderBitmap = sessionPlaceholderBitmap;
}
@Override
public void recycle(@Nonnull View view) {
Glide.clear(view);
- mSessionPlaceholderBitmap = null;
+ mSessionPlaceholderBitmap = Optional.absent();
}
@Override
// If we have a non-null placeholder, use that and do NOT ever render a
// tiny thumbnail to prevent un-intended "flash of low resolution image"
- if (mSessionPlaceholderBitmap != null) {
+ if (mSessionPlaceholderBitmap.isPresent()) {
return request.placeholder(new BitmapDrawable(mContext.getResources(),
- mSessionPlaceholderBitmap));
+ mSessionPlaceholderBitmap.get()));
}
// If we do not have a placeholder bitmap, render a thumbnail with
final Bitmap bitmap;
if (getAttributes().isRendering()) {
- bitmap = Storage.getPlacerHolderForSession(data.getUri());
+ return Storage.getPlaceholderForSession(data.getUri());
} else {
FileInputStream stream;
data.getDimensions().getHeight(),
(int) (dim.x * 0.7f), (int) (dim.y * 0.7),
data.getOrientation(), MAX_PEEK_BITMAP_PIXELS);
- }
- return Optional.fromNullable(bitmap);
+ return Optional.fromNullable(bitmap);
+ }
}
}
imageView.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
}
- Bitmap placeholder = Storage.getPlacerHolderForSession(mData.getUri());
- if (placeholder != null) {
- imageView.setImageBitmap(placeholder);
+ Optional<Bitmap> placeholder = Storage.getPlaceholderForSession(mData.getUri());
+ if (placeholder.isPresent()) {
+ imageView.setImageBitmap(placeholder.get());
} else {
imageView.setImageResource(GlideFilmstripManager.DEFAULT_PLACEHOLDER_RESOURCE);
}
@Override
public Optional<Bitmap> generateThumbnail(int boundingWidthPx, int boundingHeightPx) {
- return Optional.fromNullable(Storage.getPlacerHolderForSession(mUri));
+ return Storage.getPlaceholderForSession(mUri);
}
@Override
public StackSaver getStackSaver();
/**
- * Finishes the session.
+ * Finishes the session. Resources may be held during notification of
+ * finished state, {@link #finalize()} must be called to fully complete
+ * the session.
*/
public void finish();
/**
- * Finish the session and indicate it failed.
+ * Finish the session and indicate it failed. Resources may be held during
+ * notification of finished state, {@link #finalize()} must be called to
+ * fully complete the session.
*/
public void finishWithFailure(CharSequence reason);
/**
+ * All processing complete, finalize the session and remove any resources.
+ */
+ public void finalize();
+
+ /**
* Returns the file to where the final output of this session should be
* stored. This is only available after startSession has been called and
* will become unavailable after finish() was called.
import android.net.Uri;
import android.os.AsyncTask;
-import com.android.camera.Storage;
import com.android.camera.app.MediaSaver;
import com.android.camera.data.FilmstripItemData;
import com.android.camera.debug.Log;
import com.android.camera.exif.ExifInterface;
import com.android.camera.util.FileUtil;
import com.android.camera.util.Size;
+import com.google.common.base.Optional;
import java.io.File;
import java.io.IOException;
mUri = mPlaceHolderSession.outputUri;
mSessionManager.putSession(mUri, this);
mSessionNotifier.notifyTaskQueued(mUri);
- Bitmap placeholderBitmap = Storage.getPlacerHolderForSession(mUri);
- onCaptureIndicatorUpdate(placeholderBitmap, 0);
+ Optional<Bitmap> placeholderBitmap =
+ mPlaceholderManager.getPlaceholder(mPlaceHolderSession);
+ if (placeholderBitmap.isPresent()) {
+ onCaptureIndicatorUpdate(placeholderBitmap.get(), 0);
+ }
}
@Override
@Override
public synchronized void cancel() {
if (isStarted()) {
- mSessionNotifier.removeSession(mUri.toString());
+ mSessionManager.removeSession(mUri);
}
}
mContentUri = mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation,
orientation, exif, data, width, height, FilmstripItemData.MIME_TYPE_JPEG);
- mSessionNotifier.removeSession(mUri.toString());
- mSessionNotifier.notifyTaskDone(mPlaceHolderSession.outputUri);
+ mSessionNotifier.notifyTaskDone(mUri);
}
@Override
"Cannot call finish without calling startSession first.");
}
mProgressMessage = reason;
- mSessionNotifier.removeSession(mUri.toString());
- mSessionManager.putErrorMessage(mPlaceHolderSession.outputUri, reason);
- mSessionNotifier.notifyTaskFailed(mPlaceHolderSession.outputUri, reason);
+ mSessionManager.putErrorMessage(mUri, reason);
+ mSessionNotifier.notifyTaskFailed(mUri, reason);
}
@Override
mProgressListeners.remove(listener);
}
+ @Override
+ public void finalize() {
+ mPlaceholderManager.removePlaceholder(mPlaceHolderSession);
+ }
private void onCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
mSessionNotifier.notifySessionCaptureIndicatorAvailable(indicator, rotationDegrees);
public void putSession(Uri sessionUri, CaptureSession session);
/**
+ * Removes the session with the given uri from the manager. This may not
+ * remove temporary in memory resources from the session itself, see
+ * {@link CaptureSession#finalize()} to complete session removal.
+ */
+ public CaptureSession removeSession(Uri sessionUri);
+
+ /**
* Removes a previously added listener from receiving further capture
* session updates.
*/
public class CaptureSessionManagerImpl implements CaptureSessionManager {
private final class SessionNotifierImpl implements SessionNotifier {
- @Override
- public void removeSession(String sessionUri) {
- synchronized (mSessions) {
- mSessions.remove(sessionUri);
- }
- }
-
/**
* Notifies all task listeners that the task with the given URI has been
* queued.
listener.onSessionDone(uri);
}
}
+ finalizeSession(uri);
}
});
}
listener.onSessionFailed(uri, reason);
}
}
+ finalizeSession(uri);
}
});
}
mMainHandler = mainHandler;
}
+ @Override
public CaptureSession createNewSession(String title, long sessionStartMillis, Location location) {
return mSessionFactory.createNewSession(this, mSessionNotifier, title, sessionStartMillis,
location);
}
@Override
+ public CaptureSession removeSession(Uri sessionUri) {
+ synchronized (mSessions) {
+ return mSessions.remove(sessionUri.toString());
+ }
+ }
+
+ @Override
public void addSessionListener(SessionListener listener) {
synchronized (mTaskListeners) {
mTaskListeners.add(listener);
}
});
}
+
+ /**
+ * When done with a session, remove it from internal map and finalize it.
+ *
+ * @param uri Uri of the session to remove and finalize
+ */
+ private void finalizeSession(Uri uri) {
+ CaptureSession session;
+ synchronized (mSessions) {
+ session = removeSession(uri);
+ }
+ if (session != null) {
+ session.finalize();
+ }
+ }
}
import com.android.camera.exif.ExifInterface;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.Size;
+import com.google.common.base.Optional;
/**
* Handles placeholders in filmstrip that show up temporarily while a final
public Uri finishPlaceholder(Session session, Location location, int orientation,
ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) {
Uri resultUri = Storage.updateImage(session.outputUri, mContext.getContentResolver(),
- session.outputTitle,
- session.time, location, orientation, exif, jpeg, width, height, mimeType);
+ session.outputTitle, session.time, location, orientation, exif, jpeg, width,
+ height, mimeType);
CameraUtil.broadcastNewPicture(mContext, resultUri);
return resultUri;
}
}
/**
+ * Retrieve the placeholder for a given session.
+ *
+ * @param session the session for which to retrieve bitmap placeholder
+ */
+ public Optional<Bitmap> getPlaceholder(Session session) {
+ return Storage.getPlaceholderForSession(session.outputUri);
+ }
+
+
+ /**
+ * Remove the placeholder for a given session.
+ *
+ * @param session the session for which to remove the bitmap placeholder.
+ */
+ public void removePlaceholder(Session session) {
+ Storage.removePlaceholder(session.outputUri);
+ }
+
+ /**
* Create a new session instance from the given URI by querying the media
* store.
* <p>
* status of session.
*/
interface SessionNotifier {
- /** Removes the session since it's either done or cancelled. */
- void removeSession(String sessionUri);
-
/** A new task has been queued. */
void notifyTaskQueued(final Uri uri);