summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/camera/PhotoModule.java18
-rw-r--r--src/com/android/camera/PhotoUI.java39
-rw-r--r--src/com/android/gallery3d/data/LocalImage.java8
-rw-r--r--src/com/android/gallery3d/filtershow/FilterShowActivity.java29
-rw-r--r--src/com/android/gallery3d/filtershow/cache/ImageLoader.java4
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java1
-rw-r--r--src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java288
-rw-r--r--src/com/android/gallery3d/ingest/ImportTask.java1
-rw-r--r--src/com/android/gallery3d/ingest/IngestActivity.java21
-rw-r--r--src/com/android/gallery3d/ingest/MtpDeviceIndex.java37
-rw-r--r--src/com/android/gallery3d/ingest/ui/MtpImageView.java11
-rw-r--r--src/com/android/gallery3d/util/SaveVideoFileUtils.java8
12 files changed, 382 insertions, 83 deletions
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 49b209d34..7c4487b90 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -49,7 +49,6 @@ import android.os.SystemClock;
import android.provider.MediaStore;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.SurfaceHolder;
import android.view.View;
@@ -74,7 +73,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Formatter;
import java.util.List;
@@ -940,18 +938,16 @@ public class PhotoModule
}
private void animateFlash() {
- /* //TODO:
// Only animate when in full screen capture mode
// i.e. If monkey/a user swipes to the gallery during picture taking,
// don't show animation
- if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
- && mActivity.mShowCameraAppView) {
- // Start capture animation.
- ((CameraScreenNail) mActivity.mCameraScreenNail).animateFlash(mDisplayRotation);
- mUI.enablePreviewThumb(true);
- mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
- CaptureAnimManager.getAnimationDuration());
- } */
+ if (!mIsImageCaptureIntent) {
+ mUI.animateFlash();
+
+ // TODO: mUI.enablePreviewThumb(true);
+ // mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
+ // CaptureAnimManager.getAnimationDuration());
+ }
}
@Override
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index 1468a3ce4..b2a9df8cc 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -17,6 +17,9 @@
package com.android.camera;
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
@@ -108,6 +111,8 @@ public class PhotoUI implements PieListener,
private float mSurfaceTextureUncroppedHeight;
private View mPreviewThumb;
+ private ObjectAnimator mFlashAnim;
+ private View mFlashOverlay;
private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener;
private TextureView mTextureView;
@@ -153,6 +158,26 @@ public class PhotoUI implements PieListener,
}
};
+ private ValueAnimator.AnimatorListener mAnimatorListener =
+ new ValueAnimator.AnimatorListener() {
+
+ @Override
+ public void onAnimationCancel(Animator arg0) {}
+
+ @Override
+ public void onAnimationEnd(Animator arg0) {
+ mFlashOverlay.setAlpha(0f);
+ mFlashOverlay.setVisibility(View.GONE);
+ mFlashAnim.removeListener(this);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator arg0) {}
+
+ @Override
+ public void onAnimationStart(Animator arg0) {}
+ };
+
public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
mActivity = activity;
mController = controller;
@@ -161,6 +186,7 @@ public class PhotoUI implements PieListener,
mActivity.getLayoutInflater().inflate(R.layout.photo_module,
(ViewGroup) mRootView, true);
mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
+ mFlashOverlay = mRootView.findViewById(R.id.flash_overlay);
// display the view
mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);
mTextureView.setSurfaceTextureListener(this);
@@ -444,6 +470,19 @@ public class PhotoUI implements PieListener,
public void setCameraState(int state) {
}
+ public void animateFlash() {
+ // End the previous animation if the previous one is still running
+ if (mFlashAnim != null && mFlashAnim.isRunning()) {
+ mFlashAnim.end();
+ }
+ // Start new flash animation.
+ mFlashOverlay.setVisibility(View.VISIBLE);
+ mFlashAnim = ObjectAnimator.ofFloat((Object) mFlashOverlay, "alpha", 0.3f, 0f);
+ mFlashAnim.setDuration(300);
+ mFlashAnim.addListener(mAnimatorListener);
+ mFlashAnim.start();
+ }
+
public void enableGestures(boolean enable) {
if (mGestures != null) {
mGestures.setEnabled(enable);
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index 1ed67ecf4..a7c98af77 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -35,9 +35,9 @@ import com.android.gallery3d.app.PanoramaMetadataSupport;
import com.android.gallery3d.app.StitchingProgressManager;
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.filtershow.tools.SaveCopyTask;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
@@ -46,8 +46,6 @@ import com.android.gallery3d.util.UpdateHelper;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel.MapMode;
// LocalImage represents an image in the local storage.
public class LocalImage extends LocalMediaItem {
@@ -271,7 +269,9 @@ public class LocalImage extends LocalMediaItem {
public void delete() {
GalleryUtils.assertNotInRenderThread();
Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI;
- mApplication.getContentResolver().delete(baseUri, "_id=?",
+ ContentResolver contentResolver = mApplication.getContentResolver();
+ SaveCopyTask.deleteAuxFiles(contentResolver, getContentUri());
+ contentResolver.delete(baseUri, "_id=?",
new String[]{String.valueOf(id)});
}
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index 7ba0fcc9c..289a4c37b 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -138,6 +138,8 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
private Uri mOriginalImageUri = null;
private ImagePreset mOriginalPreset = null;
+ private Uri mSelectedImageUri = null;
+
private CategoryAdapter mCategoryLooksAdapter = null;
private CategoryAdapter mCategoryBordersAdapter = null;
private CategoryAdapter mCategoryGeometryAdapter = null;
@@ -310,12 +312,13 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
}
mAction = intent.getAction();
- Uri srcUri = intent.getData();
+ mSelectedImageUri = intent.getData();
+ Uri loadUri = mSelectedImageUri;
if (mOriginalImageUri != null) {
- srcUri = mOriginalImageUri;
+ loadUri = mOriginalImageUri;
}
- if (srcUri != null) {
- startLoadBitmap(srcUri);
+ if (loadUri != null) {
+ startLoadBitmap(loadUri);
} else {
pickImage();
}
@@ -925,11 +928,13 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialog, int id) {
saveImage();
}
});
builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialog, int id) {
done();
}
@@ -983,7 +988,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
public void saveImage() {
if (mImageShow.hasModifications()) {
// Get the name of the album, to which the image will be saved
- File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri());
+ File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mSelectedImageUri);
int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
showSavingProgress(albumName);
@@ -1002,10 +1007,6 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
finish();
}
- static {
- System.loadLibrary("jni_filtershow_filters");
- }
-
private void extractXMPData() {
XMresults res = XmpPresets.extractXMPData(
getBaseContext(), mMasterImage, getIntent().getData());
@@ -1015,4 +1016,14 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
mOriginalImageUri = res.originalimage;
mOriginalPreset = res.preset;
}
+
+ public Uri getSelectedImageUri() {
+ return mSelectedImageUri;
+ }
+
+ static {
+ System.loadLibrary("jni_filtershow_filters");
+ }
+
+
}
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
index 7ddd9bebe..491340e0d 100644
--- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -394,7 +394,9 @@ public class ImageLoader {
public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
File destination) {
- new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() {
+ Uri selectedImageUri = filterShowActivity.getSelectedImageUri();
+ new SaveCopyTask(mContext, mUri, selectedImageUri, destination,
+ new SaveCopyTask.Callback() {
@Override
public void onComplete(Uri result) {
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
index dfe0308c1..abe69d110 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
@@ -56,6 +56,7 @@ public class FilterFxRepresentation extends FilterRepresentation {
if (a instanceof FilterFxRepresentation) {
FilterFxRepresentation representation = (FilterFxRepresentation) a;
setName(representation.getName());
+ setSerializationName(representation.getSerializationName());
setBitmapResource(representation.getBitmapResource());
setNameResource(representation.getNameResource());
}
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
index 1d8ab07a1..9e96e96c3 100644
--- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
+++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
@@ -25,6 +25,7 @@ import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
+import android.provider.MediaStore;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.util.Log;
@@ -40,6 +41,7 @@ import com.android.gallery3d.util.XmpUtilHelper;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Date;
@@ -61,7 +63,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
void onComplete(Uri result);
}
- private interface ContentResolverQueryCallback {
+ public interface ContentResolverQueryCallback {
void onCursorResult(Cursor cursor);
}
@@ -70,26 +72,68 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
private static final String PREFIX_PANO = "PANO";
private static final String PREFIX_IMG = "IMG";
private static final String POSTFIX_JPG = ".jpg";
+ private static final String AUX_DIR_NAME = ".aux";
+
+ private final Context mContext;
+ private final Uri mSourceUri;
+ private final Callback mCallback;
+ private final File mDestinationFile;
+ private final Uri mSelectedImageUri;
+
+ // In order to support the new edit-save behavior such that user won't see
+ // the edited image together with the original image, we are adding a new
+ // auxiliary directory for the edited image. Basically, the original image
+ // will be hidden in that directory after edit and user will see the edited
+ // image only.
+ // Note that deletion on the edited image will also cause the deletion of
+ // the original image under auxiliary directory.
+ //
+ // There are several situations we need to consider:
+ // 1. User edit local image local01.jpg. A local02.jpg will be created in the
+ // same directory, and original image will be moved to auxiliary directory as
+ // ./.aux/local02.jpg.
+ // If user edit the local02.jpg, local03.jpg will be created in the local
+ // directory and ./.aux/local02.jpg will be renamed to ./.aux/local03.jpg
+ //
+ // 2. User edit remote image remote01.jpg from picassa or other server.
+ // remoteSavedLocal01.jpg will be saved under proper local directory.
+ // In remoteSavedLocal01.jpg, there will be a reference pointing to the
+ // remote01.jpg. There will be no local copy of remote01.jpg.
+ // If user edit remoteSavedLocal01.jpg, then a new remoteSavedLocal02.jpg
+ // will be generated and still pointing to the remote01.jpg
+ //
+ // 3. User delete any local image local.jpg.
+ // Since the filenames are kept consistent in auxiliary directory, every
+ // time a local.jpg get deleted, the files in auxiliary directory whose
+ // names starting with "local." will be deleted.
+ // This pattern will facilitate the multiple images deletion in the auxiliary
+ // directory.
+ //
+ // TODO: Move the saving into a background service.
- private final Context context;
- private final Uri sourceUri;
- private final Callback callback;
- private final String saveFileName;
- private final File destinationFile;
-
- public SaveCopyTask(Context context, Uri sourceUri, File destination, Callback callback) {
- this.context = context;
- this.sourceUri = sourceUri;
- this.callback = callback;
-
+ /**
+ * @param context
+ * @param sourceUri The Uri for the original image, which can be the hidden
+ * image under the auxiliary directory or the same as selectedImageUri.
+ * @param selectedImageUri The Uri for the image selected by the user.
+ * In most cases, it is a content Uri for local image or remote image.
+ * @param destination Destinaton File, if this is null, a new file will be
+ * created under the same directory as selectedImageUri.
+ * @param callback Let the caller know the saving has completed.
+ * @return the newSourceUri
+ */
+ public SaveCopyTask(Context context, Uri sourceUri, Uri selectedImageUri,
+ File destination, Callback callback) {
+ mContext = context;
+ mSourceUri = sourceUri;
+ mCallback = callback;
if (destination == null) {
- this.destinationFile = getNewFile(context, sourceUri);
+ mDestinationFile = getNewFile(context, selectedImageUri);
} else {
- this.destinationFile = destination;
+ mDestinationFile = destination;
}
- saveFileName = PREFIX_IMG + new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(
- System.currentTimeMillis()));
+ mSelectedImageUri = selectedImageUri;
}
public static File getFinalSaveDirectory(Context context, Uri sourceUri) {
@@ -114,12 +158,64 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
return new File(saveDirectory, PREFIX_IMG + filename + POSTFIX_JPG);
}
+ /**
+ * Remove the files in the auxiliary directory whose names are the same as
+ * the source image.
+ * @param contentResolver The application's contentResolver
+ * @param srcContentUri The content Uri for the source image.
+ */
+ public static void deleteAuxFiles(ContentResolver contentResolver,
+ Uri srcContentUri) {
+ final String[] fullPath = new String[1];
+ String[] queryProjection = new String[] { ImageColumns.DATA };
+ querySourceFromContentResolver(contentResolver,
+ srcContentUri, queryProjection,
+ new ContentResolverQueryCallback() {
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ fullPath[0] = cursor.getString(0);
+ }
+ }
+ );
+ if (fullPath[0] != null) {
+ // Construct the auxiliary directory given the source file's path.
+ // Then select and delete all the files starting with the same name
+ // under the auxiliary directory.
+ File currentFile = new File(fullPath[0]);
+
+ String filename = currentFile.getName();
+ int firstDotPos = filename.indexOf(".");
+ final String filenameNoExt = (firstDotPos == -1) ? filename :
+ filename.substring(0, firstDotPos);
+ File auxDir = getLocalAuxDirectory(currentFile);
+ if (auxDir.exists()) {
+ FilenameFilter filter = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ if (name.startsWith(filenameNoExt + ".")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+
+ // Delete all auxiliary files whose name is matching the
+ // current local image.
+ File[] auxFiles = auxDir.listFiles(filter);
+ for (File file : auxFiles) {
+ file.delete();
+ }
+ }
+ }
+ }
+
public Object getPanoramaXMPData(Uri source, ImagePreset preset) {
Object xmp = null;
if (preset.isPanoramaSafe()) {
InputStream is = null;
try {
- is = context.getContentResolver().openInputStream(source);
+ is = mContext.getContentResolver().openInputStream(source);
xmp = XmpUtilHelper.extractXMPMeta(is);
} catch (FileNotFoundException e) {
Log.w(LOGTAG, "Failed to get XMP data from image: ", e);
@@ -139,11 +235,11 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
public ExifInterface getExifData(Uri source) {
ExifInterface exif = new ExifInterface();
- String mimeType = context.getContentResolver().getType(sourceUri);
+ String mimeType = mContext.getContentResolver().getType(mSelectedImageUri);
if (mimeType.equals(ImageLoader.JPEG_MIME_TYPE)) {
InputStream inStream = null;
try {
- inStream = context.getContentResolver().openInputStream(source);
+ inStream = mContext.getContentResolver().openInputStream(source);
exif.readExif(inStream);
} catch (FileNotFoundException e) {
Log.w(LOGTAG, "Cannot find file: " + source, e);
@@ -175,7 +271,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
@Override
protected Uri doInBackground(ImagePreset... params) {
// TODO: Support larger dimensions for photo saving.
- if (params[0] == null || sourceUri == null) {
+ if (params[0] == null || mSourceUri == null || mSelectedImageUri == null) {
return null;
}
ImagePreset preset = params[0];
@@ -183,19 +279,25 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
Uri uri = null;
boolean noBitmap = true;
int num_tries = 0;
+
+ // If necessary, move the source file into the auxiliary directory,
+ // newSourceUri is then pointing to the new location.
+ // If no file is moved, newSourceUri will be the same as mSourceUri.
+ Uri newSourceUri = moveSrcToAuxIfNeeded(mSourceUri, mDestinationFile);
+
// Stopgap fix for low-memory devices.
while (noBitmap) {
try {
// Try to do bitmap operations, downsample if low-memory
- Bitmap bitmap = ImageLoader.loadMutableBitmap(context, sourceUri, options);
+ Bitmap bitmap = ImageLoader.loadMutableBitmap(mContext, newSourceUri, options);
if (bitmap == null) {
return null;
}
CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(), "Saving");
bitmap = pipeline.renderFinalImage(bitmap, preset);
- Object xmp = getPanoramaXMPData(sourceUri, preset);
- ExifInterface exif = getExifData(sourceUri);
+ Object xmp = getPanoramaXMPData(mSelectedImageUri, preset);
+ ExifInterface exif = getExifData(mSelectedImageUri);
// Set tags
long time = System.currentTimeMillis();
@@ -208,12 +310,23 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
exif.removeCompressedThumbnail();
// If we succeed in writing the bitmap as a jpeg, return a uri.
- if (putExifData(this.destinationFile, exif, bitmap)) {
- putPanoramaXMPData(this.destinationFile, xmp);
- uri = insertContent(context, sourceUri, this.destinationFile, saveFileName,
+ if (putExifData(mDestinationFile, exif, bitmap)) {
+ putPanoramaXMPData(mDestinationFile, xmp);
+ uri = insertContent(mContext, mSelectedImageUri, mDestinationFile,
time);
}
- XmpPresets.writeFilterXMP(context, sourceUri, this.destinationFile, preset);
+
+ // mDestinationFile will save the newSourceUri info in the XMP.
+ XmpPresets.writeFilterXMP(mContext, newSourceUri, mDestinationFile, preset);
+
+ // Since we have a new image inserted to media store, we can
+ // safely remove the old one which is selected by the user.
+ String scheme = mSelectedImageUri.getScheme();
+ if (scheme != null && scheme.equals(ContentResolver.SCHEME_CONTENT)) {
+ if (mSelectedImageUri.getAuthority().equals(MediaStore.AUTHORITY)) {
+ mContext.getContentResolver().delete(mSelectedImageUri, null, null);
+ }
+ }
noBitmap = false;
UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
@@ -230,17 +343,73 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
return uri;
}
+ /**
+ * Move the source file to auxiliary directory if needed and return the Uri
+ * pointing to this new source file.
+ * @param srcUri Uri to the source image.
+ * @param dstFile Providing the destination file info to help to build the
+ * auxiliary directory and new source file's name.
+ * @return the newSourceUri pointing to the new source image.
+ */
+ private Uri moveSrcToAuxIfNeeded(Uri srcUri, File dstFile) {
+ File srcFile = getFileFromUri(mContext, srcUri);
+ if (srcFile == null) {
+ Log.d(LOGTAG, "Source file is not a local file, no update.");
+ return srcUri;
+ }
+
+ // Get the destination directory and create the auxilliary directory
+ // if necessary.
+ File auxDiretory = getLocalAuxDirectory(dstFile);
+ if (!auxDiretory.exists()) {
+ auxDiretory.mkdirs();
+ }
+
+ // Make sure there is a .nomedia file in the auxiliary directory, such
+ // that MediaScanner will not report those files under this directory.
+ File noMedia = new File(auxDiretory, ".nomedia");
+ if (!noMedia.exists()) {
+ try {
+ noMedia.createNewFile();
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Can't create the nomedia");
+ return srcUri;
+ }
+ }
+ // We are using the destination file name such that photos sitting in
+ // the auxiliary directory are matching the parent directory.
+ File newSrcFile = new File(auxDiretory, dstFile.getName());
+
+ if (!newSrcFile.exists()) {
+ srcFile.renameTo(newSrcFile);
+ }
+
+ return Uri.fromFile(newSrcFile);
+
+ }
+
+ private static File getLocalAuxDirectory(File dstFile) {
+ File dstDirectory = dstFile.getParentFile();
+ File auxDiretory = new File(dstDirectory + "/" + AUX_DIR_NAME);
+ return auxDiretory;
+ }
@Override
protected void onPostExecute(Uri result) {
- if (callback != null) {
- callback.onComplete(result);
+ if (mCallback != null) {
+ mCallback.onComplete(result);
}
}
private static void querySource(Context context, Uri sourceUri, String[] projection,
ContentResolverQueryCallback callback) {
ContentResolver contentResolver = context.getContentResolver();
+ querySourceFromContentResolver(contentResolver, sourceUri, projection, callback);
+ }
+
+ private static void querySourceFromContentResolver(
+ ContentResolver contentResolver, Uri sourceUri, String[] projection,
+ ContentResolverQueryCallback callback) {
Cursor cursor = null;
try {
cursor = contentResolver.query(sourceUri, projection, null, null,
@@ -258,18 +427,51 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
}
private static File getSaveDirectory(Context context, Uri sourceUri) {
- final File[] dir = new File[1];
- querySource(context, sourceUri, new String[] {
- ImageColumns.DATA
- },
- new ContentResolverQueryCallback() {
+ File file = getFileFromUri(context, sourceUri);
+ if (file != null) {
+ return file.getParentFile();
+ } else {
+ return null;
+ }
+ }
- @Override
- public void onCursorResult(Cursor cursor) {
- dir[0] = new File(cursor.getString(0)).getParentFile();
- }
- });
- return dir[0];
+ /**
+ * Construct a File object based on the srcUri.
+ * @return The file object. Return null if srcUri is invalid or not a local
+ * file.
+ */
+ private static File getFileFromUri(Context context, Uri srcUri) {
+ if (srcUri == null) {
+ Log.e(LOGTAG, "srcUri is null.");
+ return null;
+ }
+
+ String scheme = srcUri.getScheme();
+ if (scheme == null) {
+ Log.e(LOGTAG, "scheme is null.");
+ return null;
+ }
+
+ final File[] file = new File[1];
+ // sourceUri can be a file path or a content Uri, it need to be handled
+ // differently.
+ if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
+ if (srcUri.getAuthority().equals(MediaStore.AUTHORITY)) {
+ querySource(context, srcUri, new String[] {
+ ImageColumns.DATA
+ },
+ new ContentResolverQueryCallback() {
+
+ @Override
+ public void onCursorResult(Cursor cursor) {
+ file[0] = new File(cursor.getString(0));
+ }
+ });
+ }
+ } else if (scheme.equals(ContentResolver.SCHEME_FILE)) {
+ file[0] = new File(srcUri.getPath());
+ }
+ return file[0];
}
/**
@@ -302,16 +504,16 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
/**
* Insert the content (saved file) with proper source photo properties.
*/
- public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName,
+ private static Uri insertContent(Context context, Uri sourceUri, File file,
long time) {
time /= 1000;
final ContentValues values = new ContentValues();
- values.put(Images.Media.TITLE, saveFileName);
+ values.put(Images.Media.TITLE, file.getName());
values.put(Images.Media.DISPLAY_NAME, file.getName());
values.put(Images.Media.MIME_TYPE, "image/jpeg");
values.put(Images.Media.DATE_TAKEN, time);
- values.put(Images.Media.DATE_MODIFIED, time);
+ values.put(Images.Media.DATE_MODIFIED, System.currentTimeMillis());
values.put(Images.Media.DATE_ADDED, time);
values.put(Images.Media.ORIENTATION, 0);
values.put(Images.Media.DATA, file.getAbsolutePath());
diff --git a/src/com/android/gallery3d/ingest/ImportTask.java b/src/com/android/gallery3d/ingest/ImportTask.java
index d850bb8e1..7d2d641a5 100644
--- a/src/com/android/gallery3d/ingest/ImportTask.java
+++ b/src/com/android/gallery3d/ingest/ImportTask.java
@@ -65,6 +65,7 @@ public class ImportTask implements Runnable {
List<MtpObjectInfo> objectsNotImported = new LinkedList<MtpObjectInfo>();
int visited = 0;
int total = mObjectsToImport.size();
+ mListener.onImportProgress(visited, total, null);
File dest = new File(Environment.getExternalStorageDirectory(), mDestAlbumName);
dest.mkdirs();
for (MtpObjectInfo object : mObjectsToImport) {
diff --git a/src/com/android/gallery3d/ingest/IngestActivity.java b/src/com/android/gallery3d/ingest/IngestActivity.java
index ffc4b50cd..687e9fd44 100644
--- a/src/com/android/gallery3d/ingest/IngestActivity.java
+++ b/src/com/android/gallery3d/ingest/IngestActivity.java
@@ -75,6 +75,14 @@ public class IngestActivity extends Activity implements
private MenuItem mMenuSwitcherItem;
private MenuItem mActionMenuSwitcherItem;
+ // The MTP framework components don't give us fine-grained file copy
+ // progress updates, so for large photos and videos, we will be stuck
+ // with a dialog not updating for a long time. To give the user feedback,
+ // we switch to the animated indeterminate progress bar after the timeout
+ // specified by INDETERMINATE_SWITCH_TIMEOUT_MS. On the next update from
+ // the framework, we switch back to the normal progress bar.
+ private static final int INDETERMINATE_SWITCH_TIMEOUT_MS = 3000;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -437,6 +445,9 @@ public class IngestActivity extends Activity implements
mProgressState.current = visitedCount;
mProgressState.title = getResources().getString(R.string.ingest_importing);
mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
+ mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
+ mHandler.sendEmptyMessageDelayed(ItemListHandler.MSG_PROGRESS_INDETERMINATE,
+ INDETERMINATE_SWITCH_TIMEOUT_MS);
}
@Override
@@ -444,6 +455,7 @@ public class IngestActivity extends Activity implements
int numVisited) {
// Not guaranteed to be called on the UI thread
mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
+ mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE);
// TODO: maybe show an extra dialog listing the ones that failed
// importing, if any?
}
@@ -477,6 +489,11 @@ public class IngestActivity extends Activity implements
}
}
+ private void makeProgressDialogIndeterminate() {
+ ProgressDialog dialog = getProgressDialog();
+ dialog.setIndeterminate(true);
+ }
+
private void cleanupProgressDialog() {
if (mProgressDialog != null) {
mProgressDialog.hide();
@@ -490,6 +507,7 @@ public class IngestActivity extends Activity implements
public static final int MSG_PROGRESS_HIDE = 1;
public static final int MSG_NOTIFY_CHANGED = 2;
public static final int MSG_BULK_CHECKED_CHANGE = 3;
+ public static final int MSG_PROGRESS_INDETERMINATE = 4;
WeakReference<IngestActivity> mParentReference;
@@ -515,6 +533,9 @@ public class IngestActivity extends Activity implements
case MSG_BULK_CHECKED_CHANGE:
parent.mPositionMappingCheckBroker.onBulkCheckedChange();
break;
+ case MSG_PROGRESS_INDETERMINATE:
+ parent.makeProgressDialogIndeterminate();
+ break;
default:
break;
}
diff --git a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
index e873dd1ca..d30f94a87 100644
--- a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
+++ b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
@@ -26,8 +26,10 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.Stack;
/**
@@ -62,6 +64,27 @@ import java.util.Stack;
*/
public class MtpDeviceIndex {
+ public static final int FORMAT_MOV = 0x300D; // For some reason this is not in MtpConstants
+
+ public static final Set<Integer> SUPPORTED_IMAGE_FORMATS;
+ public static final Set<Integer> SUPPORTED_VIDEO_FORMATS;
+
+ static {
+ SUPPORTED_IMAGE_FORMATS = new HashSet<Integer>();
+ SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_JFIF);
+ SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_EXIF_JPEG);
+ SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_PNG);
+ SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_GIF);
+ SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_BMP);
+
+ SUPPORTED_VIDEO_FORMATS = new HashSet<Integer>();
+ SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_3GP_CONTAINER);
+ SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_AVI);
+ SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MP4_CONTAINER);
+ SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MPEG);
+ // TODO: add FORMAT_MOV once Media Scanner supports .mov files
+ }
+
@Override
public int hashCode() {
final int prime = 31;
@@ -492,14 +515,12 @@ public class MtpDeviceIndex {
for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) {
MtpObjectInfo objectInfo = mDevice.getObjectInfo(objectHandle);
if (objectInfo == null) throw new IndexingException();
- switch (objectInfo.getFormat()) {
- case MtpConstants.FORMAT_JFIF:
- case MtpConstants.FORMAT_EXIF_JPEG:
- addObject(objectInfo);
- break;
- case MtpConstants.FORMAT_ASSOCIATION:
- pendingDirectories.add(objectHandle);
- break;
+ int format = objectInfo.getFormat();
+ if (format == MtpConstants.FORMAT_ASSOCIATION) {
+ pendingDirectories.add(objectHandle);
+ } else if (SUPPORTED_IMAGE_FORMATS.contains(format)
+ || SUPPORTED_VIDEO_FORMATS.contains(format)) {
+ addObject(objectInfo);
}
}
}
diff --git a/src/com/android/gallery3d/ingest/ui/MtpImageView.java b/src/com/android/gallery3d/ingest/ui/MtpImageView.java
index 67414c6c4..a773f4485 100644
--- a/src/com/android/gallery3d/ingest/ui/MtpImageView.java
+++ b/src/com/android/gallery3d/ingest/ui/MtpImageView.java
@@ -27,12 +27,16 @@ import android.os.Message;
import android.util.AttributeSet;
import android.widget.ImageView;
+import com.android.gallery3d.ingest.MtpDeviceIndex;
import com.android.gallery3d.ingest.data.BitmapWithMetadata;
import com.android.gallery3d.ingest.data.MtpBitmapFetch;
import java.lang.ref.WeakReference;
public class MtpImageView extends ImageView {
+ // We will use the thumbnail for images larger than this threshold
+ private static final int MAX_FULLSIZE_PREVIEW_SIZE = 8388608; // 8 megabytes
+
private int mObjectHandle;
private int mGeneration;
@@ -89,7 +93,12 @@ public class MtpImageView extends ImageView {
}
protected Object fetchMtpImageDataFromDevice(MtpDevice device, MtpObjectInfo info) {
- return MtpBitmapFetch.getFullsize(device, info);
+ if (info.getCompressedSize() <= MAX_FULLSIZE_PREVIEW_SIZE
+ && MtpDeviceIndex.SUPPORTED_IMAGE_FORMATS.contains(info.getFormat())) {
+ return MtpBitmapFetch.getFullsize(device, info);
+ } else {
+ return new BitmapWithMetadata(MtpBitmapFetch.getThumbnail(device, info), 0);
+ }
}
private float mLastBitmapWidth;
diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java
index e2c5f51b9..da0970b1d 100644
--- a/src/com/android/gallery3d/util/SaveVideoFileUtils.java
+++ b/src/com/android/gallery3d/util/SaveVideoFileUtils.java
@@ -25,17 +25,13 @@ import android.os.Environment;
import android.provider.MediaStore.Video;
import android.provider.MediaStore.Video.VideoColumns;
+import com.android.gallery3d.filtershow.tools.SaveCopyTask.ContentResolverQueryCallback;
+
import java.io.File;
import java.sql.Date;
import java.text.SimpleDateFormat;
public class SaveVideoFileUtils {
- // Copy from SaveCopyTask.java in terms of how to handle the destination
- // path and filename : querySource() and getSaveDirectory().
- public interface ContentResolverQueryCallback {
- void onCursorResult(Cursor cursor);
- }
-
// This function can decide which folder to save the video file, and generate
// the needed information for the video file including filename.
public static SaveVideoFileInfo getDstMp4FileInfo(String fileNameFormat,