gcam: Add placeholder image.

Bug: 11050749

Change-Id: I374c5919d6da0609fccd21c09775fa91894d5a24
diff --git a/res/layout/placeholder_progressbar.xml b/res/layout/placeholder_progressbar.xml
new file mode 100644
index 0000000..45eadfb
--- /dev/null
+++ b/res/layout/placeholder_progressbar.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/placeholder_progress"
+        style="@android:style/Widget.Holo.ProgressBar.Large"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|center_horizontal"
+        android:paddingBottom="58dp"
+        android:visibility="visible"
+        android:indeterminate="true"
+        android:indeterminateOnly="true"
+        android:background="@null"/>
+
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index b971b28..eb2da1d 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -64,6 +64,7 @@
 import android.widget.ShareActionProvider;
 
 import com.android.camera.app.AppManagerFactory;
+import com.android.camera.app.PlaceholderManager;
 import com.android.camera.app.PanoramaStitchingManager;
 import com.android.camera.crop.CropActivity;
 import com.android.camera.data.CameraDataAdapter;
@@ -143,6 +144,7 @@
     private LocalDataAdapter mWrappedDataAdapter;
 
     private PanoramaStitchingManager mPanoramaManager;
+    private PlaceholderManager mPlaceholderManager;
     private int mCurrentModuleIndex;
     private CameraModule mCurrentModule;
     private FrameLayout mAboveFilmstripControlLayout;
@@ -688,6 +690,41 @@
             item.setVisible(visible);
     }
 
+    private ImageTaskManager.TaskListener mPlaceholderListener =
+            new ImageTaskManager.TaskListener() {
+
+                @Override
+                public void onTaskQueued(String filePath, final Uri imageUri) {
+                    mMainHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            notifyNewMedia(imageUri);
+                            int dataID = mDataAdapter.findDataByContentUri(imageUri);
+                            if (dataID != -1) {
+                                LocalData d = mDataAdapter.getLocalData(dataID);
+                                InProgressDataWrapper newData = new InProgressDataWrapper(d, true);
+                                mDataAdapter.updateData(dataID, newData);
+                            }
+                        }
+                    });
+                }
+
+                @Override
+                public void onTaskDone(String filePath, final Uri imageUri) {
+                    mMainHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mDataAdapter.refresh(getContentResolver(), imageUri);
+                        }
+                    });
+                }
+
+                @Override
+                public void onTaskProgress(String filePath, Uri imageUri, int progress) {
+                    // Do nothing
+                }
+    };
+
     private ImageTaskManager.TaskListener mStitchingListener =
             new ImageTaskManager.TaskListener() {
                 @Override
@@ -761,6 +798,8 @@
             mDataAdapter.addNewPhoto(cr, uri);
         } else if (mimeType.startsWith("application/stitching-preview")) {
             mDataAdapter.addNewPhoto(cr, uri);
+        } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) {
+            mDataAdapter.addNewPhoto(cr, uri);
         } else {
             android.util.Log.w(TAG, "Unknown new media with MIME type:"
                     + mimeType + ", uri:" + uri);
@@ -966,7 +1005,10 @@
         this.setSystemBarsVisibility(false);
         mPanoramaManager = AppManagerFactory.getInstance(this)
                 .getPanoramaStitchingManager();
+        mPlaceholderManager = AppManagerFactory.getInstance(this)
+                .getGcamProcessingManager();
         mPanoramaManager.addTaskListener(mStitchingListener);
+        mPlaceholderManager.addTaskListener(mPlaceholderListener);
         LayoutInflater inflater = getLayoutInflater();
         View rootLayout = inflater.inflate(R.layout.camera, null, false);
         mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root);
diff --git a/src/com/android/camera/Storage.java b/src/com/android/camera/Storage.java
index a8ce08b..499d030 100644
--- a/src/com/android/camera/Storage.java
+++ b/src/com/android/camera/Storage.java
@@ -43,6 +43,7 @@
             Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString();
 
     public static final String DIRECTORY = DCIM + "/Camera";
+    public static final String JPEG_POSTFIX = ".jpg";
 
     // Match the code in MediaProvider.computeBucketValues().
     public static final String BUCKET_ID =
@@ -62,6 +63,18 @@
         }
     }
 
+    public static void writeFile(String path, byte[] jpeg, ExifInterface exif) {
+        if (exif != null) {
+            try {
+                exif.writeExif(jpeg, path);
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to write data", e);
+            }
+        } else {
+            writeFile(path, jpeg);
+        }
+    }
+
     public static void writeFile(String path, byte[] data) {
         FileOutputStream out = null;
         try {
@@ -73,39 +86,41 @@
             try {
                 out.close();
             } catch (Exception e) {
+                Log.e(TAG, "Failed to close file after write", e);
             }
         }
     }
 
-    // Save the image and add it to media store.
-    public static Uri addImage(ContentResolver resolver, String title,
-            long date, Location location, int orientation, ExifInterface exif,
-            byte[] jpeg, int width, int height) {
-        // Save the image.
+    // Save the image and add it to the MediaStore.
+    public static Uri addImage(ContentResolver resolver, String title, long date,
+            Location location, int orientation, ExifInterface exif, byte[] jpeg, int width,
+            int height) {
+
+        return addImage(resolver, title, date, location, orientation, exif, jpeg, width, height,
+                LocalData.MIME_TYPE_JPEG);
+    }
+
+    // Save the image with a given mimeType and add it the MediaStore.
+    public static Uri addImage(ContentResolver resolver, String title, long date,
+            Location location, int orientation, ExifInterface exif, byte[] jpeg, int width,
+            int height, String mimeType) {
+
         String path = generateFilepath(title);
-        if (exif != null) {
-            try {
-                exif.writeExif(jpeg, path);
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to write data", e);
-            }
-        } else {
-            writeFile(path, jpeg);
-        }
+        writeFile(path, jpeg, exif);
         return addImage(resolver, title, date, location, orientation,
-                jpeg.length, path, width, height);
+                jpeg.length, path, width, height, mimeType);
     }
 
-    // Add the image to media store.
-    public static Uri addImage(ContentResolver resolver, String title,
+    // Get a ContentValues object for the given photo data
+    public static ContentValues getContentValuesForData(String title,
             long date, Location location, int orientation, int jpegLength,
-            String path, int width, int height) {
-        // Insert into MediaStore.
-        ContentValues values = new ContentValues(9);
+            String path, int width, int height, String mimeType) {
+
+        ContentValues values = new ContentValues(11);
         values.put(ImageColumns.TITLE, title);
-        values.put(ImageColumns.DISPLAY_NAME, title + ".jpg");
+        values.put(ImageColumns.DISPLAY_NAME, title + JPEG_POSTFIX);
         values.put(ImageColumns.DATE_TAKEN, date);
-        values.put(ImageColumns.MIME_TYPE, LocalData.MIME_TYPE_JPEG);
+        values.put(ImageColumns.MIME_TYPE, mimeType);
         // Clockwise rotation in degrees. 0, 90, 180, or 270.
         values.put(ImageColumns.ORIENTATION, orientation);
         values.put(ImageColumns.DATA, path);
@@ -117,6 +132,17 @@
             values.put(ImageColumns.LATITUDE, location.getLatitude());
             values.put(ImageColumns.LONGITUDE, location.getLongitude());
         }
+        return values;
+    }
+
+    // Add the image to media store.
+    public static Uri addImage(ContentResolver resolver, String title,
+            long date, Location location, int orientation, int jpegLength,
+            String path, int width, int height, String mimeType) {
+        // Insert into MediaStore.
+        ContentValues values =
+                getContentValuesForData(title, date, location, orientation, jpegLength, path,
+                        width, height, mimeType);
 
         Uri uri = null;
         try {
@@ -132,6 +158,34 @@
         return uri;
     }
 
+    // Overwrites the file and updates the MediaStore
+    public static void updateImage(Uri imageUri, ContentResolver resolver, String title, long date,
+            Location location, int orientation, ExifInterface exif, byte[] jpeg, int width,
+            int height, String mimeType) {
+        String path = generateFilepath(title);
+        writeFile(path, jpeg, exif);
+        updateImage(imageUri, resolver, title, date, location, orientation, jpeg.length, path,
+                width, height, mimeType);
+    }
+
+    // Updates the image values in MediaStore
+    public static void 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);
+
+        // Update the MediaStore
+        int rowsModified = resolver.update(imageUri, values, null, null);
+        if (rowsModified != 1) {
+            // This should never happen
+            throw new IllegalStateException("Bad number of rows (" + rowsModified
+                    + ") updated for uri: " + imageUri);
+        }
+    }
+
     public static void deleteImage(ContentResolver resolver, Uri uri) {
         try {
             resolver.delete(uri, null, null);
diff --git a/src/com/android/camera/WideAnglePanoramaModule.java b/src/com/android/camera/WideAnglePanoramaModule.java
index 20e8885..8d00c70 100644
--- a/src/com/android/camera/WideAnglePanoramaModule.java
+++ b/src/com/android/camera/WideAnglePanoramaModule.java
@@ -46,6 +46,7 @@
 
 import com.android.camera.CameraManager.CameraProxy;
 import com.android.camera.app.OrientationManager;
+import com.android.camera.data.LocalData;
 import com.android.camera.exif.ExifInterface;
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.UsageStatistics;
@@ -759,8 +760,8 @@
                 Storage.writeFile(filepath, jpegData);
             }
             int jpegLength = (int) (new File(filepath).length());
-            return Storage.addImage(mContentResolver, filename, mTimeTaken,
-                    loc, orientation, jpegLength, filepath, width, height);
+            return Storage.addImage(mContentResolver, filename, mTimeTaken, loc, orientation,
+                    jpegLength, filepath, width, height, LocalData.MIME_TYPE_JPEG);
         }
         return null;
     }
diff --git a/src/com/android/camera/app/AppManagerFactory.java b/src/com/android/camera/app/AppManagerFactory.java
index 9c047aa..43d2a00 100644
--- a/src/com/android/camera/app/AppManagerFactory.java
+++ b/src/com/android/camera/app/AppManagerFactory.java
@@ -16,9 +16,9 @@
 
 package com.android.camera.app;
 
-import android.app.Application;
 import android.content.Context;
 
+
 /**
  * A singleton class which provides application level utility
  * classes.
@@ -35,13 +35,19 @@
     }
 
     private PanoramaStitchingManager mPanoramaStitchingManager;
+    private PlaceholderManager mGcamProcessingManager;
 
     /** No public constructor. */
     private AppManagerFactory(Context ctx) {
         mPanoramaStitchingManager = new PanoramaStitchingManager(ctx);
+        mGcamProcessingManager = new PlaceholderManager(ctx);
     }
 
     public PanoramaStitchingManager getPanoramaStitchingManager() {
         return mPanoramaStitchingManager;
     }
+
+    public PlaceholderManager getGcamProcessingManager() {
+        return mGcamProcessingManager;
+    }
 }
diff --git a/src/com/android/camera/app/PlaceholderManager.java b/src/com/android/camera/app/PlaceholderManager.java
new file mode 100644
index 0000000..326f0be
--- /dev/null
+++ b/src/com/android/camera/app/PlaceholderManager.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.app;
+
+import android.content.Context;
+import android.graphics.BitmapFactory;
+import android.location.Location;
+import android.net.Uri;
+
+import com.android.camera.ImageTaskManager;
+import com.android.camera.Storage;
+import com.android.camera.exif.ExifInterface;
+import com.android.camera.util.CameraUtil;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class PlaceholderManager implements ImageTaskManager {
+    private static final String TAG = "PlaceholderManager";
+
+    public static final String PLACEHOLDER_MIME_TYPE = "application/placeholder-image";
+    private final Context mContext;
+
+    final private ArrayList<WeakReference<TaskListener>> mListenerRefs;
+
+    public static class Session {
+        String outputTitle;
+        Uri outputUri;
+        long time;
+
+        Session(String title, Uri uri, long timestamp) {
+            outputTitle = title;
+            outputUri = uri;
+            time = timestamp;
+        }
+    }
+
+    public PlaceholderManager(Context context) {
+        mContext = context;
+        mListenerRefs = new ArrayList<WeakReference<TaskListener>>();
+    }
+
+    @Override
+    public void addTaskListener(TaskListener l) {
+        synchronized (mListenerRefs) {
+            if (findTaskListener(l) == -1) {
+                mListenerRefs.add(new WeakReference<TaskListener>(l));
+            }
+        }
+    }
+
+    @Override
+    public void removeTaskListener(TaskListener l) {
+        synchronized (mListenerRefs) {
+            int i = findTaskListener(l);
+            if (i != -1) {
+                mListenerRefs.remove(i);
+            }
+        }
+    }
+
+    @Override
+    public int getTaskProgress(Uri uri) {
+        return 0;
+    }
+
+    private int findTaskListener(TaskListener listener) {
+        int index = -1;
+        for (int i = 0; i < mListenerRefs.size(); i++) {
+            TaskListener l = mListenerRefs.get(i).get();
+            if (l != null && l == listener) {
+                index = i;
+                break;
+            }
+        }
+        return index;
+    }
+
+    private Iterable<TaskListener> getListeners() {
+        return new Iterable<TaskListener>() {
+            @Override
+            public Iterator<TaskListener> iterator() {
+                return new ListenerIterator();
+            }
+        };
+    }
+
+    private class ListenerIterator implements Iterator<TaskListener> {
+        private int mIndex = 0;
+        private TaskListener mNext = null;
+
+        @Override
+        public boolean hasNext() {
+            while (mNext == null && mIndex < mListenerRefs.size()) {
+                mNext = mListenerRefs.get(mIndex).get();
+                if (mNext == null) {
+                    mListenerRefs.remove(mIndex);
+                }
+            }
+            return mNext != null;
+        }
+
+        @Override
+        public TaskListener next() {
+            hasNext(); // Populates mNext
+            mIndex++;
+            TaskListener next = mNext;
+            mNext = null;
+            return next;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public Session insertPlaceholder(String title, byte[] placeholder, long timestamp) {
+        if (title == null || placeholder == null) {
+            throw new IllegalArgumentException("Null argument passed to insertPlaceholder");
+        }
+
+        // Decode bounds
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeByteArray(placeholder, 0, placeholder.length, options);
+        int width = options.outWidth;
+        int height = options.outHeight;
+
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Image had bad height/width");
+        }
+
+        Uri uri =
+                Storage.addImage(mContext.getContentResolver(), title, timestamp, null, 0, null,
+                        placeholder, width, height, PLACEHOLDER_MIME_TYPE);
+
+        if (uri == null) {
+            return null;
+        }
+
+        String filePath = uri.getPath();
+        synchronized (mListenerRefs) {
+            for (TaskListener l : getListeners()) {
+                l.onTaskQueued(filePath, uri);
+            }
+        }
+
+        return new Session(title, uri, timestamp);
+    }
+
+    public void replacePlaceholder(Session session, Location location, int orientation,
+            ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) {
+
+        Storage.updateImage(session.outputUri, mContext.getContentResolver(), session.outputTitle,
+                session.time, location, orientation, exif, jpeg, width, height, mimeType);
+
+        synchronized (mListenerRefs) {
+            for (TaskListener l : getListeners()) {
+                l.onTaskDone(session.outputUri.getPath(), session.outputUri);
+            }
+        }
+        CameraUtil.broadcastNewPicture(mContext, session.outputUri);
+    }
+
+    public void removePlaceholder(Session session) {
+        Storage.deleteImage(mContext.getContentResolver(), session.outputUri);
+    }
+
+}
diff --git a/src/com/android/camera/data/InProgressDataWrapper.java b/src/com/android/camera/data/InProgressDataWrapper.java
index 7de617b..61e87b7 100644
--- a/src/com/android/camera/data/InProgressDataWrapper.java
+++ b/src/com/android/camera/data/InProgressDataWrapper.java
@@ -22,8 +22,10 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.view.View;
+import android.widget.FrameLayout;
 
 import com.android.camera.util.PhotoSphereHelper;
+import com.android.camera2.R;
 
 /**
  * A wrapper class for in-progress data. Data that's still being processed
@@ -34,16 +36,34 @@
 public class InProgressDataWrapper implements LocalData {
 
     final LocalData mLocalData;
+    private boolean mHasProgressBar;
 
     public InProgressDataWrapper(LocalData wrappedData) {
         mLocalData = wrappedData;
     }
 
+    public InProgressDataWrapper(LocalData wrappedData, boolean hasProgressBar) {
+        this(wrappedData);
+        mHasProgressBar = hasProgressBar;
+    }
+
     @Override
     public View getView(
             Activity a, int width, int height,
             Drawable placeHolder, LocalDataAdapter adapter) {
-        return mLocalData.getView(a, width, height, placeHolder, adapter);
+        View v =  mLocalData.getView(a, width, height, placeHolder, adapter);
+
+        if (mHasProgressBar) {
+            // Return a framelayout with the progressbar and imageview.
+            FrameLayout frame = new FrameLayout(a);
+            frame.setLayoutParams(new FrameLayout.LayoutParams(width, height));
+            frame.addView(v);
+            a.getLayoutInflater()
+                    .inflate(R.layout.placeholder_progressbar, frame);
+            return frame;
+        }
+
+        return v;
     }
 
     @Override