Merge changes from topic "api29" am: 55d843fcf1 am: d96b5af9c4 am: b0382e4d82

Original change: https://android-review.googlesource.com/c/platform/packages/apps/Camera2/+/1347923

Change-Id: Id5489bab88a226880128d6d5cc173e626d388538
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 002bf5f..751d08a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -6,7 +6,7 @@
 
     <uses-sdk
         android:minSdkVersion="19"
-        android:targetSdkVersion="28" />
+        android:targetSdkVersion="1000" />
 
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
@@ -26,7 +26,6 @@
     <uses-permission android:name="android.permission.USE_CREDENTIALS" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
 
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 6b81641..9de0037 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -889,7 +889,7 @@
                 @Override
                 public void onSessionQueued(final Uri uri) {
                     Log.v(TAG, "onSessionQueued: " + uri);
-                    if (!Storage.isSessionUri(uri)) {
+                    if (!Storage.instance().isSessionUri(uri)) {
                         return;
                     }
                     Optional<SessionItem> newData = SessionItem.create(getApplicationContext(), uri);
@@ -907,7 +907,7 @@
                 @Override
                 public void onSessionDone(final Uri sessionUri) {
                     Log.v(TAG, "onSessionDone:" + sessionUri);
-                    Uri contentUri = Storage.getContentUriForSessionUri(sessionUri);
+                    Uri contentUri = Storage.instance().getContentUriForSessionUri(sessionUri);
                     if (contentUri == null) {
                         mDataAdapter.refresh(sessionUri);
                         return;
@@ -936,7 +936,7 @@
                                 && mFilmstripController.isVisible(oldSessionData)) {
                             Log.v(TAG, "session item visible, setting transition placeholder");
                             newData.setSessionPlaceholderBitmap(
-                                    Storage.getPlaceholderForSession(sessionUri));
+                                    Storage.instance().getPlaceholderForSession(sessionUri));
                         }
                         mDataAdapter.updateItemAt(pos, newData);
                     }
@@ -1932,8 +1932,7 @@
         }
 
         if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
-                checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED &&
-                checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+                checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
             mHasCriticalPermissions = true;
         } else {
             mHasCriticalPermissions = false;
@@ -2381,7 +2380,7 @@
             @Override
             protected Long doInBackground(Void ... arg) {
                 synchronized (mStorageSpaceLock) {
-                    mStorageSpaceBytes = Storage.getAvailableSpace();
+                    mStorageSpaceBytes = Storage.instance().getAvailableSpace();
                     return mStorageSpaceBytes;
                 }
             }
diff --git a/src/com/android/camera/MediaSaverImpl.java b/src/com/android/camera/MediaSaverImpl.java
index 3107bb5..a8e96ec 100644
--- a/src/com/android/camera/MediaSaverImpl.java
+++ b/src/com/android/camera/MediaSaverImpl.java
@@ -179,7 +179,7 @@
                 height = options.outHeight;
             }
             try {
-                return Storage.addImage(
+                return Storage.instance().addImage(
                         resolver, title, date, loc, orientation, exif, data, width, height,
                         mimeType);
             } catch (IOException e) {
diff --git a/src/com/android/camera/PermissionsActivity.java b/src/com/android/camera/PermissionsActivity.java
index aca4778..895d316 100644
--- a/src/com/android/camera/PermissionsActivity.java
+++ b/src/com/android/camera/PermissionsActivity.java
@@ -43,13 +43,9 @@
     private boolean mShouldRequestCameraPermission;
     private boolean mShouldRequestMicrophonePermission;
     private boolean mShouldRequestLocationPermission;
-    private boolean mShouldRequestStoragePermission;
-    private boolean mShouldRequestWriteStoragePermission;
     private int mNumPermissionsToRequest;
     private boolean mFlagHasCameraPermission;
     private boolean mFlagHasMicrophonePermission;
-    private boolean mFlagHasStoragePermission;
-    private boolean mFlagHasWriteStoragePermission;
     private SettingsManager mSettingsManager;
 
     /**
@@ -119,22 +115,6 @@
             mFlagHasMicrophonePermission = true;
         }
 
-        if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
-                != PackageManager.PERMISSION_GRANTED) {
-            mNumPermissionsToRequest++;
-            mShouldRequestStoragePermission = true;
-        } else {
-            mFlagHasStoragePermission = true;
-        }
-
-        if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
-                != PackageManager.PERMISSION_GRANTED) {
-            mNumPermissionsToRequest++;
-            mShouldRequestWriteStoragePermission = true;
-        } else {
-            mFlagHasWriteStoragePermission = true;
-        }
-
         if (mSettingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
             Keys.KEY_RECORD_LOCATION)
                 && (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
@@ -171,16 +151,6 @@
             mIndexPermissionRequestMicrophone = permissionsRequestIndex;
             permissionsRequestIndex++;
         }
-        if (mShouldRequestStoragePermission) {
-            permissionsToRequest[permissionsRequestIndex] = Manifest.permission.READ_EXTERNAL_STORAGE;
-            mIndexPermissionRequestStorage = permissionsRequestIndex;
-            permissionsRequestIndex++;
-        }
-        if (mShouldRequestWriteStoragePermission) {
-            permissionsToRequest[permissionsRequestIndex] = Manifest.permission.WRITE_EXTERNAL_STORAGE;
-            mIndexPermissionRequestWriteStorage = permissionsRequestIndex;
-            permissionsRequestIndex++;
-        }
         if (mShouldRequestLocationPermission) {
             permissionsToRequest[permissionsRequestIndex] = Manifest.permission.ACCESS_COARSE_LOCATION;
             mIndexPermissionRequestLocation = permissionsRequestIndex;
@@ -215,23 +185,6 @@
                 handlePermissionsFailure();
             }
         }
-        if (mShouldRequestStoragePermission) {
-            if (grantResults.length > 0 && grantResults[mIndexPermissionRequestStorage] ==
-                    PackageManager.PERMISSION_GRANTED) {
-                mFlagHasStoragePermission = true;
-            } else {
-                handlePermissionsFailure();
-            }
-        }
-        if (mShouldRequestWriteStoragePermission) {
-            if (grantResults.length > 0 && grantResults[mIndexPermissionRequestWriteStorage] ==
-                    PackageManager.PERMISSION_GRANTED) {
-                mFlagHasWriteStoragePermission = true;
-            } else {
-                handlePermissionsFailure();
-            }
-        }
-
         if (mShouldRequestLocationPermission) {
             if (grantResults.length > 0 && grantResults[mIndexPermissionRequestLocation] ==
                     PackageManager.PERMISSION_GRANTED) {
@@ -241,8 +194,7 @@
             }
         }
 
-        if (mFlagHasCameraPermission && mFlagHasMicrophonePermission &&
-                mFlagHasStoragePermission && mFlagHasWriteStoragePermission) {
+        if (mFlagHasCameraPermission && mFlagHasMicrophonePermission) {
             handlePermissionsSuccess();
         }
     }
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index f29b08e..6fb8243 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -727,7 +727,7 @@
         queue.addIdleHandler(new MessageQueue.IdleHandler() {
             @Override
             public boolean queueIdle() {
-                Storage.ensureOSXCompatible();
+                Storage.instance().ensureOSXCompatible();
                 return false;
             }
         });
diff --git a/src/com/android/camera/Storage.java b/src/com/android/camera/Storage.java
index 842f9a4..385e465 100644
--- a/src/com/android/camera/Storage.java
+++ b/src/com/android/camera/Storage.java
@@ -18,38 +18,35 @@
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Point;
 import android.location.Location;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.StatFs;
-import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Images.ImageColumns;
-import android.provider.MediaStore.MediaColumns;
+import android.provider.MediaStore.Images.Media;
 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.AndroidContext;
 import com.android.camera.util.Size;
 import com.google.common.base.Optional;
 
 import java.io.File;
-import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.HashMap;
 import java.util.UUID;
-import java.util.concurrent.TimeUnit;
 
 import javax.annotation.Nonnull;
 
 public class Storage {
-    public static final String DCIM =
-            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString();
-    public static final String DIRECTORY = DCIM + "/Camera";
-    public static final File DIRECTORY_FILE = new File(DIRECTORY);
+    public final String DIRECTORY;
     public static final String JPEG_POSTFIX = ".jpg";
     public static final String GIF_POSTFIX = ".gif";
     public static final long UNAVAILABLE = -1L;
@@ -60,9 +57,9 @@
     public static final String CAMERA_SESSION_SCHEME = "camera_session";
     private static final Log.Tag TAG = new Log.Tag("Storage");
     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 LruCache<Uri, Bitmap> sSessionsToPlaceholderBitmap =
+    private HashMap<Uri, Uri> sSessionsToContentUris = new HashMap<>();
+    private HashMap<Uri, Uri> sContentUrisToSessions = new HashMap<>();
+    private LruCache<Uri, Bitmap> sSessionsToPlaceholderBitmap =
             // 20MB cache as an upper bound for session bitmap storage
             new LruCache<Uri, Bitmap>(20 * 1024 * 1024) {
                 @Override
@@ -70,8 +67,20 @@
                     return value.getByteCount();
                 }
             };
-    private static HashMap<Uri, Point> sSessionsToSizes = new HashMap<>();
-    private static HashMap<Uri, Integer> sSessionsToPlaceholderVersions = new HashMap<>();
+    private HashMap<Uri, Point> sSessionsToSizes = new HashMap<>();
+    private HashMap<Uri, Integer> sSessionsToPlaceholderVersions = new HashMap<>();
+
+    private static class Singleton {
+        private static final Storage INSTANCE = new Storage(AndroidContext.instance().get());
+    }
+
+    public static Storage instance() {
+        return Singleton.INSTANCE;
+    }
+
+    private Storage(Context context) {
+        DIRECTORY = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath();
+    }
 
     /**
      * Save the image with default JPEG MIME type and add it to the MediaStore.
@@ -88,7 +97,7 @@
      * @param height The height of the media file after the orientation is
      *               applied.
      */
-    public static Uri addImage(ContentResolver resolver, String title, long date,
+    public Uri addImage(ContentResolver resolver, String title, long date,
             Location location, int orientation, ExifInterface exif, byte[] jpeg, int width,
             int height) throws IOException {
 
@@ -117,15 +126,14 @@
      * @return The URI of the added image, or null if the image could not be
      *         added.
      */
-    public static Uri addImage(ContentResolver resolver, String title, long date,
+    public Uri addImage(ContentResolver resolver, String title, long date,
             Location location, int orientation, ExifInterface exif, byte[] data, int width,
             int height, String mimeType) throws IOException {
 
-        String path = generateFilepath(title, mimeType);
-        long fileLength = writeFile(path, data, exif);
-        if (fileLength >= 0) {
-            return addImageToMediaStore(resolver, title, date, location, orientation, fileLength,
-                    path, width, height, mimeType);
+        if (data.length >= 0) {
+            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+            return addImageToMediaStore(resolver, title, date, location, orientation, data.length,
+                    bitmap, width, height, mimeType, exif);
         }
         return null;
     }
@@ -138,25 +146,26 @@
      * @param date The date for the media file.
      * @param location The location of the media file.
      * @param orientation The orientation of the media file.
+     * @param bitmap The bitmap representation of the media to store.
      * @param width The width of the media file after the orientation is
      *            applied.
      * @param height The height of the media file after the orientation is
      *            applied.
      * @param mimeType The MIME type of the data.
+     * @param exif The exif of the image.
      * @return The content URI of the inserted media file or null, if the image
      *         could not be added.
      */
-    public static Uri addImageToMediaStore(ContentResolver resolver, String title, long date,
-            Location location, int orientation, long jpegLength, String path, int width, int height,
-            String mimeType) {
+    public Uri addImageToMediaStore(ContentResolver resolver, String title, long date,
+            Location location, int orientation, long jpegLength, Bitmap bitmap, int width,
+            int height, String mimeType, ExifInterface exif) {
         // Insert into MediaStore.
-        ContentValues values =
-                getContentValuesForData(title, date, location, orientation, jpegLength, path, width,
-                        height, mimeType);
+        ContentValues values = getContentValuesForData(title, date, location, mimeType, true);
 
         Uri uri = null;
         try {
-            uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);
+            uri = resolver.insert(Media.EXTERNAL_CONTENT_URI, values);
+            writeBitmap(uri, exif, bitmap, resolver);
         } catch (Throwable th)  {
             // This can happen when the external volume is already mounted, but
             // MediaScanner has not notify MediaProvider to add that volume.
@@ -164,34 +173,44 @@
             // insert it into MediaProvider. The only problem is that the user
             // cannot click the thumbnail to review the picture.
             Log.e(TAG, "Failed to write MediaStore" + th);
+            if (uri != null) {
+                resolver.delete(uri, null, null);
+            }
         }
         return uri;
     }
 
-    // Get a ContentValues object for the given photo data
-    public static ContentValues getContentValuesForData(String title,
-            long date, Location location, int orientation, long jpegLength,
-            String path, int width, int height, String mimeType) {
+    private void writeBitmap(Uri uri, ExifInterface exif, Bitmap bitmap, ContentResolver resolver)
+            throws FileNotFoundException, IOException {
+        OutputStream os = resolver.openOutputStream(uri);
+        if (exif != null) {
+            exif.writeExif(bitmap, os);
+        } else {
+            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os);
+        }
 
-        File file = new File(path);
-        long dateModifiedSeconds = TimeUnit.MILLISECONDS.toSeconds(file.lastModified());
+        ContentValues publishValues = new ContentValues();
+        publishValues.put(Media.IS_PENDING, 0);
+        resolver.update(uri, publishValues, null, null);
+    }
+
+    // Get a ContentValues object for the given photo data
+    public ContentValues getContentValuesForData(String title, long date, Location location,
+                                                 String mimeType, boolean isPending) {
 
         ContentValues values = new ContentValues(11);
-        values.put(ImageColumns.TITLE, title);
-        values.put(ImageColumns.DISPLAY_NAME, title + JPEG_POSTFIX);
-        values.put(ImageColumns.DATE_TAKEN, date);
-        values.put(ImageColumns.MIME_TYPE, mimeType);
-        values.put(ImageColumns.DATE_MODIFIED, dateModifiedSeconds);
-        // Clockwise rotation in degrees. 0, 90, 180, or 270.
-        values.put(ImageColumns.ORIENTATION, orientation);
-        values.put(ImageColumns.DATA, path);
-        values.put(ImageColumns.SIZE, jpegLength);
+        values.put(Media.TITLE, title);
+        values.put(Media.DISPLAY_NAME, title + JPEG_POSTFIX);
+        values.put(Media.DATE_TAKEN, date);
+        values.put(Media.MIME_TYPE, mimeType);
 
-        setImageSize(values, width, height);
+        if (isPending) {
+            values.put(Media.IS_PENDING, 1);
+        }
 
         if (location != null) {
-            values.put(ImageColumns.LATITUDE, location.getLatitude());
-            values.put(ImageColumns.LONGITUDE, location.getLongitude());
+            values.put(Media.LATITUDE, location.getLatitude());
+            values.put(Media.LONGITUDE, location.getLongitude());
         }
         return values;
     }
@@ -202,7 +221,7 @@
      * @param placeholder the placeholder image
      * @return A new URI used to reference this placeholder
      */
-    public static Uri addPlaceholder(Bitmap placeholder) {
+    public Uri addPlaceholder(Bitmap placeholder) {
         Uri uri = generateUniquePlaceholderUri();
         replacePlaceholder(uri, placeholder);
         return uri;
@@ -211,7 +230,7 @@
     /**
      * Remove a placeholder from in memory storage.
      */
-    public static void removePlaceholder(Uri uri) {
+    public void removePlaceholder(Uri uri) {
         sSessionsToSizes.remove(uri);
         sSessionsToPlaceholderBitmap.remove(uri);
         sSessionsToPlaceholderVersions.remove(uri);
@@ -225,7 +244,7 @@
      * @param placeholder the placeholder image
      * @return A URI used to reference this placeholder
      */
-    public static void replacePlaceholder(Uri uri, Bitmap placeholder) {
+    public 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);
@@ -241,7 +260,7 @@
      * @return A new URI used to reference this placeholder
      */
     @Nonnull
-    public static Uri addEmptyPlaceholder(@Nonnull Size size) {
+    public Uri addEmptyPlaceholder(@Nonnull Size size) {
         Uri uri = generateUniquePlaceholderUri();
         sSessionsToSizes.put(uri, new Point(size.getWidth(), size.getHeight()));
         sSessionsToPlaceholderBitmap.remove(uri);
@@ -266,55 +285,21 @@
      * @param mimeType of the image
      * @return The content uri of the newly inserted or replaced item.
      */
-    public static Uri updateImage(Uri imageUri, ContentResolver resolver, String title, long date,
+    public Uri updateImage(Uri imageUri, ContentResolver resolver, String title, long date,
            Location location, int orientation, ExifInterface exif,
            byte[] jpeg, int width, int height, String mimeType) throws IOException {
-        String path = generateFilepath(title, mimeType);
-        writeFile(path, jpeg, exif);
-        return updateImage(imageUri, resolver, title, date, location, orientation, jpeg.length, path,
-                width, height, mimeType);
+        Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length);
+        return updateImage(imageUri, resolver, title, date, location, orientation, jpeg.length,
+                bitmap, width, height, mimeType, exif);
     }
 
-    private static Uri generateUniquePlaceholderUri() {
+    private Uri generateUniquePlaceholderUri() {
         Uri.Builder builder = new Uri.Builder();
         String uuid = UUID.randomUUID().toString();
         builder.scheme(CAMERA_SESSION_SCHEME).authority(GOOGLE_COM).appendPath(uuid);
         return builder.build();
     }
 
-    private static void setImageSize(ContentValues values, int width, int height) {
-        // The two fields are available since ICS but got published in JB
-        if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) {
-            values.put(MediaColumns.WIDTH, width);
-            values.put(MediaColumns.HEIGHT, height);
-        }
-    }
-
-    /**
-     * Writes the JPEG data to a file. If there's EXIF info, the EXIF header
-     * will be added.
-     *
-     * @param path The path to the target file.
-     * @param jpeg The JPEG data.
-     * @param exif The EXIF info. Can be {@code null}.
-     *
-     * @return The size of the file. -1 if failed.
-     */
-    public static long writeFile(String path, byte[] jpeg, ExifInterface exif) throws IOException {
-        if (!createDirectoryIfNeeded(path)) {
-            Log.e(TAG, "Failed to create parent directory for file: " + path);
-            return -1;
-        }
-        if (exif != null) {
-                exif.writeExif(jpeg, path);
-                File f = new File(path);
-                return f.length();
-        } else {
-            return writeFile(path, jpeg);
-        }
-//        return -1;
-    }
-
     /**
      * Renames a file.
      *
@@ -325,7 +310,7 @@
      * @param newFilePath the new path of the file
      * @return false if rename was not successful
      */
-    public static boolean renameFile(File inputPath, File newFilePath) {
+    public boolean renameFile(File inputPath, File newFilePath) {
         if (newFilePath.exists()) {
             Log.e(TAG, "File path already exists: " + newFilePath.getAbsolutePath());
             return false;
@@ -343,32 +328,6 @@
     }
 
     /**
-     * Writes the data to a file.
-     *
-     * @param path The path to the target file.
-     * @param data The data to save.
-     *
-     * @return The size of the file. -1 if failed.
-     */
-    private static long writeFile(String path, byte[] data) {
-        FileOutputStream out = null;
-        try {
-            out = new FileOutputStream(path);
-            out.write(data);
-            return data.length;
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to write data", e);
-        } finally {
-            try {
-                out.close();
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to close file after write", e);
-            }
-        }
-        return -1;
-    }
-
-    /**
      * Given a file path, makes sure the directory it's in exists, and if not
      * that it is created.
      *
@@ -377,7 +336,7 @@
      *         cannot be written to since the parent directory could not be
      *         created.
      */
-    private static boolean createDirectoryIfNeeded(String filePath) {
+    private boolean createDirectoryIfNeeded(String filePath) {
         File parentFile = new File(filePath).getParentFile();
 
         // If the parent exists, return 'true' if it is a directory. If it's a
@@ -392,34 +351,30 @@
     }
 
     /** Updates the image values in MediaStore. */
-    private static Uri updateImage(Uri imageUri, ContentResolver resolver, String title,
+    private Uri updateImage(Uri imageUri, ContentResolver resolver, String title,
             long date, Location location, int orientation, int jpegLength,
-            String path, int width, int height, String mimeType) {
-
-        ContentValues values =
-                getContentValuesForData(title, date, location, orientation, jpegLength, path,
-                        width, height, mimeType);
-
+            Bitmap bitmap, int width, int height, String mimeType, ExifInterface exif) {
 
         Uri resultUri = imageUri;
-        if (Storage.isSessionUri(imageUri)) {
+        if (isSessionUri(imageUri)) {
             // If this is a session uri, then we need to add the image
             resultUri = addImageToMediaStore(resolver, title, date, location, orientation,
-                    jpegLength, path, width, height, mimeType);
+                    jpegLength, bitmap, width, height, mimeType, exif);
             sSessionsToContentUris.put(imageUri, resultUri);
             sContentUrisToSessions.put(resultUri, imageUri);
         } else {
             // Update the MediaStore
+            ContentValues values = getContentValuesForData(title, date, location, mimeType, false);
             resolver.update(imageUri, values, null, null);
         }
         return resultUri;
     }
 
-    private static String generateFilepath(String title, String mimeType) {
+    private String generateFilepath(String title, String mimeType) {
         return generateFilepath(DIRECTORY, title, mimeType);
     }
 
-    public static String generateFilepath(String directory, String title, String mimeType) {
+    public String generateFilepath(String directory, String title, String mimeType) {
         String extension = null;
         if (FilmstripItemData.MIME_TYPE_JPEG.equals(mimeType)) {
             extension = JPEG_POSTFIX;
@@ -437,7 +392,7 @@
      * @param uri the session uri to look up
      * @return The bitmap or null
      */
-    public static Optional<Bitmap> getPlaceholderForSession(Uri uri) {
+    public Optional<Bitmap> getPlaceholderForSession(Uri uri) {
         return Optional.fromNullable(sSessionsToPlaceholderBitmap.get(uri));
     }
 
@@ -445,7 +400,7 @@
      * @return Whether a placeholder size for the session with the given URI
      *         exists.
      */
-    public static boolean containsPlaceholderSize(Uri uri) {
+    public boolean containsPlaceholderSize(Uri uri) {
         return sSessionsToSizes.containsKey(uri);
     }
 
@@ -455,7 +410,7 @@
      * @param uri the session uri to look up
      * @return The size
      */
-    public static Point getSizeForSession(Uri uri) {
+    public Point getSizeForSession(Uri uri) {
         return sSessionsToSizes.get(uri);
     }
 
@@ -465,7 +420,7 @@
      * @param uri the uri of the session that was replaced
      * @return The uri of the new media item, if it exists, or null.
      */
-    public static Uri getContentUriForSessionUri(Uri uri) {
+    public Uri getContentUriForSessionUri(Uri uri) {
         return sSessionsToContentUris.get(uri);
     }
 
@@ -475,7 +430,7 @@
      * @param contentUri the uri of the media store content
      * @return The session uri of the original session, if it exists, or null.
      */
-    public static Uri getSessionUriFromContentUri(Uri contentUri) {
+    public Uri getSessionUriFromContentUri(Uri contentUri) {
         return sContentUrisToSessions.get(contentUri);
     }
 
@@ -485,11 +440,11 @@
      * @param uri the uri to check
      * @return true if it is a session uri.
      */
-    public static boolean isSessionUri(Uri uri) {
+    public boolean isSessionUri(Uri uri) {
         return uri.getScheme().equals(CAMERA_SESSION_SCHEME);
     }
 
-    public static long getAvailableSpace() {
+    public long getAvailableSpace() {
         String state = Environment.getExternalStorageState();
         Log.d(TAG, "External storage state=" + state);
         if (Environment.MEDIA_CHECKING.equals(state)) {
@@ -502,6 +457,7 @@
         File dir = new File(DIRECTORY);
         dir.mkdirs();
         if (!dir.isDirectory() || !dir.canWrite()) {
+            Log.d(TAG, DIRECTORY + " mounted, but isn't directory or cannot write");
             return UNAVAILABLE;
         }
 
@@ -518,8 +474,8 @@
      * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be
      * imported. This is a temporary fix for bug#1655552.
      */
-    public static void ensureOSXCompatible() {
-        File nnnAAAAA = new File(DCIM, "100ANDRO");
+    public void ensureOSXCompatible() {
+        File nnnAAAAA = new File(DIRECTORY, "100ANDRO");
         if (!(nnnAAAAA.exists() || nnnAAAAA.mkdirs())) {
             Log.e(TAG, "Failed to create " + nnnAAAAA.getPath());
         }
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index e8222f6..856781b 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -1223,7 +1223,7 @@
         // Used when emailing.
         String filename = title + convertOutputFormatToFileExt(outputFileFormat);
         String mime = convertOutputFormatToMimeType(outputFileFormat);
-        String path = Storage.DIRECTORY + '/' + filename;
+        String path = Storage.instance().DIRECTORY + '/' + filename;
         String tmpPath = path + ".tmp";
         mCurrentVideoValues = new ContentValues(9);
         mCurrentVideoValues.put(Video.Media.TITLE, title);
diff --git a/src/com/android/camera/app/CameraServicesImpl.java b/src/com/android/camera/app/CameraServicesImpl.java
index a1a2e86..804ba15 100644
--- a/src/com/android/camera/app/CameraServicesImpl.java
+++ b/src/com/android/camera/app/CameraServicesImpl.java
@@ -65,7 +65,7 @@
         PlaceholderManager mPlaceHolderManager = new PlaceholderManager(context);
         SessionStorageManager mSessionStorageManager = SessionStorageManagerImpl.create(context);
 
-        StackSaverFactory mStackSaverFactory = new StackSaverFactory(Storage.DIRECTORY,
+        StackSaverFactory mStackSaverFactory = new StackSaverFactory(Storage.instance().DIRECTORY,
               context.getContentResolver());
         CaptureSessionFactory captureSessionFactory = new CaptureSessionFactoryImpl(
                 mMediaSaver, mPlaceHolderManager, mSessionStorageManager, mStackSaverFactory);
diff --git a/src/com/android/camera/data/CameraFilmstripDataAdapter.java b/src/com/android/camera/data/CameraFilmstripDataAdapter.java
index e4cf978..a42f6c5 100644
--- a/src/com/android/camera/data/CameraFilmstripDataAdapter.java
+++ b/src/com/android/camera/data/CameraFilmstripDataAdapter.java
@@ -350,7 +350,7 @@
             // We may add data that is already present, but if we do, it will be deduped in addOrUpdate.
             // addOrUpdate does not dedupe session items, so we ignore them here
             for (FilmstripItem filmstripItem : newPhotoData) {
-                Uri sessionUri = Storage.getSessionUriFromContentUri(
+                Uri sessionUri = Storage.instance().getSessionUriFromContentUri(
                       filmstripItem.getData().getUri());
                 if (sessionUri == null) {
                     addOrUpdate(filmstripItem);
diff --git a/src/com/android/camera/data/FilmstripContentQueries.java b/src/com/android/camera/data/FilmstripContentQueries.java
index a3c273c..2aba065 100644
--- a/src/com/android/camera/data/FilmstripContentQueries.java
+++ b/src/com/android/camera/data/FilmstripContentQueries.java
@@ -32,7 +32,6 @@
  */
 public class FilmstripContentQueries {
     private static final Log.Tag TAG = new Log.Tag("LocalDataQuery");
-    private static final String CAMERA_PATH = Storage.DIRECTORY + "%";
     private static final String SELECT_BY_PATH = MediaStore.MediaColumns.DATA + " LIKE ?";
 
     public interface CursorToFilmstripItemFactory<I extends FilmstripItem> {
@@ -62,7 +61,8 @@
           Uri contentUri, String[] projection, long minimumId, String orderBy,
           CursorToFilmstripItemFactory<I> factory) {
         String selection = SELECT_BY_PATH + " AND " + MediaStore.MediaColumns._ID + " > ?";
-        String[] selectionArgs = new String[] { CAMERA_PATH, Long.toString(minimumId) };
+        String cameraPath = Storage.instance().DIRECTORY + "%";
+        String[] selectionArgs = new String[] { cameraPath, Long.toString(minimumId) };
 
         Cursor cursor = contentResolver.query(contentUri, projection,
               selection, selectionArgs, orderBy);
@@ -82,4 +82,4 @@
         }
         return result;
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/camera/data/FilmstripItemBase.java b/src/com/android/camera/data/FilmstripItemBase.java
index f225a07..e3c589f 100644
--- a/src/com/android/camera/data/FilmstripItemBase.java
+++ b/src/com/android/camera/data/FilmstripItemBase.java
@@ -146,7 +146,7 @@
         }
 
         // Check if this is a 'Camera' sub-directory.
-        String cameraPathStr = Storage.DIRECTORY_FILE.getAbsolutePath();
+        String cameraPathStr = Storage.instance().DIRECTORY;
         String fileParentPathStr = directory.getParentFile().getAbsolutePath();
         Log.d(TAG, "CameraPathStr: " + cameraPathStr + "  fileParentPathStr: " + fileParentPathStr);
 
diff --git a/src/com/android/camera/data/PhotoItem.java b/src/com/android/camera/data/PhotoItem.java
index 032c447..8215f73 100644
--- a/src/com/android/camera/data/PhotoItem.java
+++ b/src/com/android/camera/data/PhotoItem.java
@@ -222,7 +222,7 @@
         final Bitmap bitmap;
 
         if (getAttributes().isRendering()) {
-            return Storage.getPlaceholderForSession(data.getUri());
+            return Storage.instance().getPlaceholderForSession(data.getUri());
         } else {
 
             FileInputStream stream;
diff --git a/src/com/android/camera/data/SessionItem.java b/src/com/android/camera/data/SessionItem.java
index e36931b..f059bdd 100644
--- a/src/com/android/camera/data/SessionItem.java
+++ b/src/com/android/camera/data/SessionItem.java
@@ -52,7 +52,7 @@
      * @return If the session was found, a new SessionItem is returned.
      */
     public static Optional<SessionItem> create(Context context, Uri uri) {
-        if (!Storage.containsPlaceholderSize(uri)) {
+        if (!Storage.instance().containsPlaceholderSize(uri)) {
             return Optional.absent();
         }
         Size dimension = getSessionSize(uri);
@@ -82,7 +82,7 @@
     }
 
     private static Size getSessionSize(Uri uri) {
-        Point size = Storage.getSizeForSession(uri);
+        Point size = Storage.instance().getSizeForSession(uri);
         if (size == null) {
             return null;
         }
@@ -101,7 +101,7 @@
             imageView.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
         }
 
-        Optional<Bitmap> placeholder = Storage.getPlaceholderForSession(mData.getUri());
+        Optional<Bitmap> placeholder = Storage.instance().getPlaceholderForSession(mData.getUri());
         if (placeholder.isPresent()) {
             imageView.setImageBitmap(placeholder.get());
         } else {
@@ -171,7 +171,7 @@
 
     @Override
     public Optional<Bitmap> generateThumbnail(int boundingWidthPx, int boundingHeightPx) {
-        return Storage.getPlaceholderForSession(mUri);
+        return Storage.instance().getPlaceholderForSession(mUri);
     }
 
     @Override
diff --git a/src/com/android/camera/one/v2/OneCameraImpl.java b/src/com/android/camera/one/v2/OneCameraImpl.java
index c3b8ecb..8f0ffef 100644
--- a/src/com/android/camera/one/v2/OneCameraImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraImpl.java
@@ -162,7 +162,7 @@
     }
 
     /** Directory to store raw DNG files in. */
-    private static final File RAW_DIRECTORY = new File(Storage.DIRECTORY, "DNG");
+    private final File mRawDirectory;
 
     /** Current CONTROL_AF_MODE request to Camera2 API. */
     private int mControlAFMode = CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
@@ -322,6 +322,7 @@
         mLensRange = LensRangeCalculator.getDiopterToRatioCalculator(characteristics);
         mDirectionProvider = new CameraDirectionProvider(characteristics);
         mFullSizeAspectRatio = calculateFullSizeAspectRatio(characteristics);
+        mRawDirectory = new File(Storage.instance().DIRECTORY, "DNG");
 
         // Override pictureSize for RAW (our picture size settings don't include
         // RAW, which typically only supports one size (sensor size). This also
@@ -766,12 +767,12 @@
         // TODO: If we make this a real feature we should probably put the DNGs
         // into the Camera directly.
         if (sCaptureImageFormat == ImageFormat.RAW_SENSOR) {
-            if (!RAW_DIRECTORY.exists()) {
-                if (!RAW_DIRECTORY.mkdirs()) {
+            if (!mRawDirectory.exists()) {
+                if (!mRawDirectory.mkdirs()) {
                     throw new RuntimeException("Could not create RAW directory.");
                 }
             }
-            File dngFile = new File(RAW_DIRECTORY, capture.session.getTitle() + ".dng");
+            File dngFile = new File(mRawDirectory, capture.session.getTitle() + ".dng");
             writeDngBytesAndClose(capture.image, capture.totalCaptureResult,
                     mCharacteristics, dngFile);
         } else {
diff --git a/src/com/android/camera/session/PlaceholderManager.java b/src/com/android/camera/session/PlaceholderManager.java
index b54405f..a474250 100644
--- a/src/com/android/camera/session/PlaceholderManager.java
+++ b/src/com/android/camera/session/PlaceholderManager.java
@@ -69,7 +69,7 @@
      * @return A session instance representing the new placeholder.
      */
     public Placeholder insertEmptyPlaceholder(String title, Size size, long timestamp) {
-        Uri uri =  Storage.addEmptyPlaceholder(size);
+        Uri uri =  Storage.instance().addEmptyPlaceholder(size);
         return new Placeholder(title, uri, timestamp);
     }
 
@@ -91,7 +91,7 @@
             throw new IllegalArgumentException("Image had bad height/width");
         }
 
-        Uri uri =  Storage.addPlaceholder(placeholder);
+        Uri uri =  Storage.instance().addPlaceholder(placeholder);
         if (uri == null) {
             return null;
         }
@@ -134,7 +134,7 @@
      */
     public Uri finishPlaceholder(Placeholder placeholder, Location location, int orientation,
             ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) throws IOException {
-        Uri resultUri = Storage.updateImage(placeholder.outputUri, mContext.getContentResolver(),
+        Uri resultUri = Storage.instance().updateImage(placeholder.outputUri, mContext.getContentResolver(),
                 placeholder.outputTitle, placeholder.time, location, orientation, exif, jpeg, width,
                 height, mimeType);
         CameraUtil.broadcastNewPicture(mContext, resultUri);
@@ -148,7 +148,7 @@
      * @param placeholder the placeholder bitmap
      */
     public void replacePlaceholder(Placeholder session, Bitmap placeholder) {
-        Storage.replacePlaceholder(session.outputUri, placeholder);
+        Storage.instance().replacePlaceholder(session.outputUri, placeholder);
         CameraUtil.broadcastNewPicture(mContext, session.outputUri);
     }
 
@@ -158,7 +158,7 @@
      * @param placeholder the session for which to retrieve bitmap placeholder
      */
     public Optional<Bitmap> getPlaceholder(Placeholder placeholder) {
-        return Storage.getPlaceholderForSession(placeholder.outputUri);
+        return Storage.instance().getPlaceholderForSession(placeholder.outputUri);
     }
 
 
@@ -168,7 +168,7 @@
      * @param placeholder the session for which to remove the bitmap placeholder.
      */
     public void removePlaceholder(Placeholder placeholder) {
-        Storage.removePlaceholder(placeholder.outputUri);
+        Storage.instance().removePlaceholder(placeholder.outputUri);
     }
 
     /**
diff --git a/src/com/android/camera/session/StackSaverImpl.java b/src/com/android/camera/session/StackSaverImpl.java
index f5b1968..81870ae 100644
--- a/src/com/android/camera/session/StackSaverImpl.java
+++ b/src/com/android/camera/session/StackSaverImpl.java
@@ -17,6 +17,8 @@
 package com.android.camera.session;
 
 import android.content.ContentResolver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.location.Location;
 import android.net.Uri;
 
@@ -62,16 +64,18 @@
     public Uri saveStackedImage(File inputImagePath, String title, int width, int height,
             int imageOrientation, long captureTimeEpoch, String mimeType) {
         String filePath =
-                Storage.generateFilepath(mStackDirectory.getAbsolutePath(), title, mimeType);
+                Storage.instance().generateFilepath(mStackDirectory.getAbsolutePath(),
+                title, mimeType);
         Log.d(TAG, "Saving using stack image saver: " + filePath);
         File outputImagePath = new File(filePath);
+        Bitmap bitmap = BitmapFactory.decodeFile(filePath);
 
-        if (Storage.renameFile(inputImagePath, outputImagePath)) {
+        if (Storage.instance().renameFile(inputImagePath, outputImagePath)) {
             long fileLength = outputImagePath.length();
             if (fileLength > 0) {
-                return Storage.addImageToMediaStore(mContentResolver, title, captureTimeEpoch,
-                        mGpsLocation, imageOrientation, fileLength, filePath, width, height,
-                        mimeType);
+                return Storage.instance().addImageToMediaStore(mContentResolver, title,
+                        captureTimeEpoch, mGpsLocation, imageOrientation, fileLength, bitmap,
+                        width, height, mimeType, null);
             }
         }