summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/gallery3d/app/CropImage.java26
-rw-r--r--src/com/android/gallery3d/app/MovieActivity.java18
-rw-r--r--src/com/android/gallery3d/app/MoviePlayer.java65
-rw-r--r--src/com/android/gallery3d/app/PackagesMonitor.java29
-rw-r--r--src/com/android/gallery3d/app/PhotoPage.java27
-rw-r--r--src/com/android/gallery3d/app/Wallpaper.java20
-rw-r--r--src/com/android/gallery3d/data/DataManager.java14
-rw-r--r--src/com/android/gallery3d/data/FilterDeleteSet.java225
-rw-r--r--src/com/android/gallery3d/data/LocalAlbum.java1
-rw-r--r--src/com/android/gallery3d/data/LocalImage.java20
-rw-r--r--src/com/android/gallery3d/data/LocalVideo.java1
-rw-r--r--src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java12
-rw-r--r--src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java60
-rw-r--r--src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java131
-rw-r--r--src/com/android/gallery3d/ui/GLRootView.java15
-rw-r--r--src/com/android/gallery3d/ui/PhotoView.java66
-rw-r--r--src/com/android/gallery3d/ui/PositionController.java16
-rw-r--r--src/com/android/gallery3d/ui/TileImageView.java13
-rw-r--r--src/com/android/gallery3d/ui/TileImageViewAdapter.java50
-rw-r--r--src/com/android/gallery3d/util/GalleryUtils.java2
20 files changed, 656 insertions, 155 deletions
diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java
index 4f450d85e..f2646978a 100644
--- a/src/com/android/gallery3d/app/CropImage.java
+++ b/src/com/android/gallery3d/app/CropImage.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.app;
+import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.ProgressDialog;
import android.app.WallpaperManager;
@@ -45,6 +46,7 @@ import android.view.Window;
import android.widget.Toast;
import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.DataManager;
@@ -96,10 +98,6 @@ public class CropImage extends AbstractGalleryActivity {
private static final int DEFAULT_COMPRESS_QUALITY = 90;
private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
- // Change these to Images.Media.WIDTH/HEIGHT after they are unhidden.
- private static final String WIDTH = "width";
- private static final String HEIGHT = "height";
-
public static final String KEY_RETURN_DATA = "return-data";
public static final String KEY_CROPPED_RECT = "cropped-rect";
public static final String KEY_ASPECT_X = "aspectX";
@@ -371,6 +369,15 @@ public class CropImage extends AbstractGalleryActivity {
}
}
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ 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(Images.Media.WIDTH, width);
+ values.put(Images.Media.HEIGHT, height);
+ }
+ }
+
private Uri savePicasaImage(JobContext jc, Bitmap cropped) {
if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) {
throw new RuntimeException("cannot create download folder");
@@ -395,8 +402,7 @@ public class CropImage extends AbstractGalleryActivity {
values.put(Images.Media.ORIENTATION, 0);
values.put(Images.Media.DATA, output.getAbsolutePath());
values.put(Images.Media.SIZE, output.length());
- values.put(WIDTH, cropped.getWidth());
- values.put(HEIGHT, cropped.getHeight());
+ setImageSize(values, cropped.getWidth(), cropped.getHeight());
double latitude = PicasaSource.getLatitude(mMediaItem);
double longitude = PicasaSource.getLongitude(mMediaItem);
@@ -434,8 +440,8 @@ public class CropImage extends AbstractGalleryActivity {
values.put(Images.Media.ORIENTATION, 0);
values.put(Images.Media.DATA, output.getAbsolutePath());
values.put(Images.Media.SIZE, output.length());
- values.put(WIDTH, cropped.getWidth());
- values.put(HEIGHT, cropped.getHeight());
+
+ setImageSize(values, cropped.getWidth(), cropped.getHeight());
if (GalleryUtils.isValidLocation(localImage.latitude, localImage.longitude)) {
values.put(Images.Media.LATITUDE, localImage.latitude);
@@ -467,8 +473,8 @@ public class CropImage extends AbstractGalleryActivity {
values.put(Images.Media.ORIENTATION, 0);
values.put(Images.Media.DATA, output.getAbsolutePath());
values.put(Images.Media.SIZE, output.length());
- values.put(WIDTH, cropped.getWidth());
- values.put(HEIGHT, cropped.getHeight());
+
+ setImageSize(values, cropped.getWidth(), cropped.getHeight());
return getContentResolver().insert(
Images.Media.EXTERNAL_CONTENT_URI, values);
diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java
index 78fe1ee78..5f4db1d13 100644
--- a/src/com/android/gallery3d/app/MovieActivity.java
+++ b/src/com/android/gallery3d/app/MovieActivity.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.app;
+import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.content.AsyncQueryHandler;
@@ -27,6 +28,7 @@ import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.media.AudioManager;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
@@ -39,6 +41,7 @@ import android.view.WindowManager;
import android.widget.ShareActionProvider;
import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.Utils;
/**
@@ -59,6 +62,15 @@ public class MovieActivity extends Activity {
private Uri mUri;
private boolean mTreatUpAsBack;
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ private void setSystemUiVisibility(View rootView) {
+ if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
+ rootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ }
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -68,9 +80,9 @@ public class MovieActivity extends Activity {
setContentView(R.layout.movie_view);
View rootView = findViewById(R.id.movie_view_root);
- rootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+
+ setSystemUiVisibility(rootView);
+
Intent intent = getIntent();
initializeActionBar(intent);
mFinishOnCompletion = intent.getBooleanExtra(
diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java
index ea12e4b7e..98d7a6434 100644
--- a/src/com/android/gallery3d/app/MoviePlayer.java
+++ b/src/com/android/gallery3d/app/MoviePlayer.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.app;
+import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -37,6 +38,7 @@ import android.view.ViewGroup;
import android.widget.VideoView;
import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.BlobCache;
import com.android.gallery3d.util.CacheManager;
import com.android.gallery3d.util.GalleryUtils;
@@ -149,6 +151,37 @@ public class MoviePlayer implements
}
}, BLACK_TIMEOUT);
+ setOnSystemUiVisibilityChangeListener();
+ // Hide system UI by default
+ showSystemUi(false);
+
+ mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
+ mAudioBecomingNoisyReceiver.register();
+
+ Intent i = new Intent(SERVICECMD);
+ i.putExtra(CMDNAME, CMDPAUSE);
+ movieActivity.sendBroadcast(i);
+
+ if (savedInstance != null) { // this is a resumed activity
+ mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
+ mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
+ mVideoView.start();
+ mVideoView.suspend();
+ mHasPaused = true;
+ } else {
+ final Integer bookmark = mBookmarker.getBookmark(mUri);
+ if (bookmark != null) {
+ showResumeDialog(movieActivity, bookmark);
+ } else {
+ startVideo();
+ }
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private void setOnSystemUiVisibilityChangeListener() {
+ if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION) return;
+
// When the user touches the screen or uses some hard key, the framework
// will change system ui visibility from invisible to visible. We show
// the media control and enable system UI (e.g. ActionBar) to be visible at this point
@@ -177,37 +210,15 @@ public class MoviePlayer implements
}
}
});
-
- // Hide system UI by default
- showSystemUi(false);
-
- mAudioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver();
- mAudioBecomingNoisyReceiver.register();
-
- Intent i = new Intent(SERVICECMD);
- i.putExtra(CMDNAME, CMDPAUSE);
- movieActivity.sendBroadcast(i);
-
- if (savedInstance != null) { // this is a resumed activity
- mVideoPosition = savedInstance.getInt(KEY_VIDEO_POSITION, 0);
- mResumeableTime = savedInstance.getLong(KEY_RESUMEABLE_TIME, Long.MAX_VALUE);
- mVideoView.start();
- mVideoView.suspend();
- mHasPaused = true;
- } else {
- final Integer bookmark = mBookmarker.getBookmark(mUri);
- if (bookmark != null) {
- showResumeDialog(movieActivity, bookmark);
- } else {
- startVideo();
- }
- }
}
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
private void showSystemUi(boolean visible) {
+ if (!ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) return;
+
int flag = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
if (!visible) {
flag |= View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -497,7 +508,7 @@ class Bookmarker {
DataInputStream dis = new DataInputStream(
new ByteArrayInputStream(data));
- String uriString = dis.readUTF(dis);
+ String uriString = DataInputStream.readUTF(dis);
int bookmark = dis.readInt();
int duration = dis.readInt();
diff --git a/src/com/android/gallery3d/app/PackagesMonitor.java b/src/com/android/gallery3d/app/PackagesMonitor.java
index e4bb8eedb..d8aa8c914 100644
--- a/src/com/android/gallery3d/app/PackagesMonitor.java
+++ b/src/com/android/gallery3d/app/PackagesMonitor.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.app;
+import android.app.IntentService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -34,23 +35,23 @@ public class PackagesMonitor extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
- final PendingResult result = goAsync();
- new Thread("GalleryPackagesMonitorAsync") {
- @Override
- public void run() {
- try {
- onReceiveAsync(context, intent);
- } catch (Throwable t) {
- Log.e("PackagesMonitor", "onReceiveAsync", t);
- } finally {
- result.finish();
- }
- }
- }.start();
+ intent.setClass(context, AsyncService.class);
+ context.startService(intent);
+ }
+
+ public static class AsyncService extends IntentService {
+ public AsyncService() {
+ super("GalleryPackagesMonitorAsync");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ onReceiveAsync(this, intent);
+ }
}
// Runs in a background thread.
- private void onReceiveAsync(Context context, Intent intent) {
+ private static void onReceiveAsync(Context context, Intent intent) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
int version = prefs.getInt(KEY_PACKAGES_VERSION, 1);
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index c94454352..17f57c204 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -218,10 +218,12 @@ public class PhotoPage extends ActivityState implements
mShowBars = false;
}
+ MediaSet originalSet = mActivity.getDataManager()
+ .getMediaSet(mSetPathString);
+ mSelectionManager.setSourceMediaSet(originalSet);
mSetPathString = "/filter/delete/{" + mSetPathString + "}";
mMediaSet = (FilterDeleteSet) mActivity.getDataManager()
.getMediaSet(mSetPathString);
- mSelectionManager.setSourceMediaSet(mMediaSet);
mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
if (mMediaSet == null) {
Log.w(TAG, "failed to restore " + mSetPathString);
@@ -251,7 +253,12 @@ public class PhotoPage extends ActivityState implements
MediaItem photo = mModel.getMediaItem(0);
if (photo != null) updateCurrentPhoto(photo);
} else if (mIsActive) {
- mActivity.getStateManager().finishState(PhotoPage.this);
+ // We only want to finish the PhotoPage if there is no
+ // deletion that the user can undo.
+ if (mMediaSet.getNumberOfDeletions() == 0) {
+ mActivity.getStateManager().finishState(
+ PhotoPage.this);
+ }
}
}
@@ -717,32 +724,25 @@ public class PhotoPage extends ActivityState implements
// the deletion, we then actually delete the media item.
@Override
public void onDeleteImage(Path path, int offset) {
- commitDeletion(); // commit the previous deletion
+ onCommitDeleteImage(); // commit the previous deletion
mDeletePath = path;
mDeleteIsFocus = (offset == 0);
- mMediaSet.setDeletion(path, mCurrentIndex + offset);
- mPhotoView.showUndoBar();
+ mMediaSet.addDeletion(path, mCurrentIndex + offset);
}
@Override
public void onUndoDeleteImage() {
+ if (mDeletePath == null) return;
// If the deletion was done on the focused item, we want the model to
// focus on it when it is undeleted.
if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath);
- mMediaSet.setDeletion(null, 0);
+ mMediaSet.removeDeletion(mDeletePath);
mDeletePath = null;
- mPhotoView.hideUndoBar();
}
@Override
public void onCommitDeleteImage() {
if (mDeletePath == null) return;
- commitDeletion();
- mPhotoView.hideUndoBar();
- }
-
- private void commitDeletion() {
- if (mDeletePath == null) return;
mSelectionManager.deSelectAll();
mSelectionManager.toggle(mDeletePath);
mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false);
@@ -857,6 +857,7 @@ public class PhotoPage extends ActivityState implements
onCommitDeleteImage();
mMenuExecutor.pause();
+ if (mMediaSet != null) mMediaSet.clearDeletion();
}
@Override
diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java
index c08c1d705..1ece66c49 100644
--- a/src/com/android/gallery3d/app/Wallpaper.java
+++ b/src/com/android/gallery3d/app/Wallpaper.java
@@ -16,11 +16,16 @@
package com.android.gallery3d.app;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Point;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.view.Display;
+
+import com.android.gallery3d.common.ApiHelper;
/**
* Wallpaper picker for the gallery application. This just redirects to the
@@ -57,6 +62,18 @@ public class Wallpaper extends Activity {
}
}
+ @SuppressWarnings("deprecation")
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR2)
+ private Point getDefaultDisplaySize(Point size) {
+ Display d = getWindowManager().getDefaultDisplay();
+ if (Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.HONEYCOMB_MR2) {
+ d.getSize(size);
+ } else {
+ size.set(d.getWidth(), d.getHeight());
+ }
+ return size;
+ }
+
@SuppressWarnings("fallthrough")
@Override
protected void onResume() {
@@ -78,8 +95,7 @@ public class Wallpaper extends Activity {
case STATE_PHOTO_PICKED: {
int width = getWallpaperDesiredMinimumWidth();
int height = getWallpaperDesiredMinimumHeight();
- Point size = new Point();
- getWindowManager().getDefaultDisplay().getSize(size);
+ Point size = getDefaultDisplaySize(new Point());
float spotlightX = (float) size.x / width;
float spotlightY = (float) size.y / height;
Intent request = new Intent(CropImage.ACTION_CROP)
diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java
index 0eb6af55e..85513279c 100644
--- a/src/com/android/gallery3d/data/DataManager.java
+++ b/src/com/android/gallery3d/data/DataManager.java
@@ -16,9 +16,11 @@
package com.android.gallery3d.data;
+import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.support.v4.content.LocalBroadcastManager;
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.common.Utils;
@@ -78,6 +80,9 @@ public class DataManager {
private static final String TOP_LOCAL_VIDEO_SET_PATH =
"/local/video";
+ private static final String ACTION_DELETE_PICTURE =
+ "com.android.gallery3d.action.DELETE_PICTURE";
+
public static final Comparator<MediaItem> sDateTakenComparator =
new DateTakenComparator();
@@ -305,6 +310,15 @@ public class DataManager {
}
}
+ // Sends a local broadcast if a local image or video is deleted. This is
+ // used to update the thumbnail shown in the camera app.
+ public void broadcastLocalDeletion() {
+ LocalBroadcastManager manager = LocalBroadcastManager.getInstance(
+ mApplication.getAndroidContext());
+ Intent intent = new Intent(ACTION_DELETE_PICTURE);
+ manager.sendBroadcast(intent);
+ }
+
private static class NotifyBroker extends ContentObserver {
private WeakHashMap<ChangeNotifier, Object> mNotifiers =
new WeakHashMap<ChangeNotifier, Object>();
diff --git a/src/com/android/gallery3d/data/FilterDeleteSet.java b/src/com/android/gallery3d/data/FilterDeleteSet.java
index fc94eb8e0..dbd85d57d 100644
--- a/src/com/android/gallery3d/data/FilterDeleteSet.java
+++ b/src/com/android/gallery3d/data/FilterDeleteSet.java
@@ -18,25 +18,51 @@ package com.android.gallery3d.data;
import java.util.ArrayList;
-// FilterDeleteSet filters a base MediaSet to remove a deletion item. The user
-// can use the following method to change the deletion item:
+// FilterDeleteSet filters a base MediaSet to remove some deletion items (we
+// expect the number to be small). The user can use the following methods to
+// add/remove deletion items:
//
-// void setDeletion(Path path, int index);
+// void addDeletion(Path path, int index);
+// void removeDelection(Path path);
+// void clearDeletion();
+// int getNumberOfDeletions();
//
-// If the path is null, there is no deletion item.
public class FilterDeleteSet extends MediaSet implements ContentListener {
private static final String TAG = "FilterDeleteSet";
+ private static final int REQUEST_ADD = 1;
+ private static final int REQUEST_REMOVE = 2;
+ private static final int REQUEST_CLEAR = 3;
+
+ private static class Request {
+ int type; // one of the REQUEST_* constants
+ Path path;
+ int indexHint;
+ public Request(int type, Path path, int indexHint) {
+ this.type = type;
+ this.path = path;
+ this.indexHint = indexHint;
+ }
+ }
+
+ private static class Deletion {
+ Path path;
+ int index;
+ public Deletion(Path path, int index) {
+ this.path = path;
+ this.index = index;
+ }
+ }
+
+ // The underlying MediaSet
private final MediaSet mBaseSet;
- private Path mDeletionPath;
- private int mDeletionIndexHint;
- private boolean mNewDeletionSettingPending = false;
- // This is set to true or false in reload(), so we know if the given
- // mDelectionPath is still in the mBaseSet, and if so we can adjust the
- // index and items.
- private boolean mDeletionInEffect;
- private int mDeletionIndex;
+ // Pending Requests
+ private ArrayList<Request> mRequests = new ArrayList<Request>();
+
+ // Deletions currently in effect, ordered by index
+ private ArrayList<Deletion> mCurrent = new ArrayList<Deletion>();
+ private int mMediaItemCount;
public FilterDeleteSet(Path path, MediaSet baseSet) {
super(path, INVALID_DATA_VERSION);
@@ -51,65 +77,176 @@ public class FilterDeleteSet extends MediaSet implements ContentListener {
@Override
public int getMediaItemCount() {
- if (mDeletionInEffect) {
- return mBaseSet.getMediaItemCount() - 1;
- } else {
- return mBaseSet.getMediaItemCount();
- }
+ return mMediaItemCount;
}
+ // Gets the MediaItems whose (post-deletion) index are in the range [start,
+ // start + count). Because we remove some of the MediaItems, the index need
+ // to be adjusted.
+ //
+ // For example, if there are 12 items in total. The deleted items are 3, 5,
+ // 10, and the the requested range is [3, 7]:
+ //
+ // The original index: 0 1 2 3 4 5 6 7 8 9 A B C
+ // The deleted items: X X X
+ // The new index: 0 1 2 3 4 5 6 7 8 9
+ // Requested: * * * * *
+ //
+ // We need to figure out the [3, 7] actually maps to the original index 4,
+ // 6, 7, 8, 9.
+ //
+ // We can break the MediaItems into segments, each segment other than the
+ // last one ends in a deleted item. The difference between the new index and
+ // the original index increases with each segment:
+ //
+ // 0 1 2 X (new index = old index)
+ // 4 X (new index = old index - 1)
+ // 6 7 8 9 X (new index = old index - 2)
+ // B C (new index = old index - 3)
+ //
@Override
public ArrayList<MediaItem> getMediaItem(int start, int count) {
- if (!mDeletionInEffect || mDeletionIndex >= start + count) {
- return mBaseSet.getMediaItem(start, count);
+ if (count <= 0) return new ArrayList<MediaItem>();
+
+ int end = start + count - 1;
+ int n = mCurrent.size();
+ // Find the segment that "start" falls into. Count the number of items
+ // not yet deleted until it reaches "start".
+ int i = 0;
+ for (i = 0; i < n; i++) {
+ Deletion d = mCurrent.get(i);
+ if (d.index - i > start) break;
+ }
+ // Find the segment that "end" falls into.
+ int j = i;
+ for (; j < n; j++) {
+ Deletion d = mCurrent.get(j);
+ if (d.index - j > end) break;
}
- if (mDeletionIndex < start) {
- return mBaseSet.getMediaItem(start + 1, count);
+
+ // Now get enough to cover deleted items in [start, end]
+ ArrayList<MediaItem> base = mBaseSet.getMediaItem(start + i, count + (j - i));
+
+ // Remove the deleted items.
+ for (int m = j - 1; m >= i; m--) {
+ Deletion d = mCurrent.get(m);
+ int k = d.index - (start + i);
+ base.remove(k);
}
- ArrayList<MediaItem> base = mBaseSet.getMediaItem(start, count + 1);
- base.remove(mDeletionIndex - start);
return base;
}
+ // We apply the pending requests in the mRequests to construct mCurrent in reload().
@Override
public long reload() {
boolean newData = mBaseSet.reload() > mDataVersion;
- if (!newData && !mNewDeletionSettingPending) return mDataVersion;
- mNewDeletionSettingPending = false;
- mDeletionInEffect = false;
- if (mDeletionPath != null) {
- // See if mDeletionPath can be found in the MediaSet. We don't want
- // to search the whole mBaseSet, so we just search a small window
- // that is close the the index hint.
+ synchronized (mRequests) {
+ if (!newData && mRequests.isEmpty()) {
+ return mDataVersion;
+ }
+ for (int i = 0; i < mRequests.size(); i++) {
+ Request r = mRequests.get(i);
+ switch (r.type) {
+ case REQUEST_ADD: {
+ // Add the path into mCurrent if there is no duplicate.
+ int n = mCurrent.size();
+ int j;
+ for (j = 0; j < n; j++) {
+ if (mCurrent.get(j).path == r.path) break;
+ }
+ if (j == n) {
+ mCurrent.add(new Deletion(r.path, r.indexHint));
+ }
+ break;
+ }
+ case REQUEST_REMOVE: {
+ // Remove the path from mCurrent.
+ int n = mCurrent.size();
+ for (int j = 0; j < n; j++) {
+ if (mCurrent.get(j).path == r.path) {
+ mCurrent.remove(j);
+ break;
+ }
+ }
+ break;
+ }
+ case REQUEST_CLEAR: {
+ mCurrent.clear();
+ break;
+ }
+ }
+ }
+ mRequests.clear();
+ }
+
+ if (!mCurrent.isEmpty()) {
+ // See if the elements in mCurrent can be found in the MediaSet. We
+ // don't want to search the whole mBaseSet, so we just search a
+ // small window that contains the index hints (plus some margin).
+ int minIndex = mCurrent.get(0).index;
+ int maxIndex = minIndex;
+ for (int i = 1; i < mCurrent.size(); i++) {
+ Deletion d = mCurrent.get(i);
+ minIndex = Math.min(d.index, minIndex);
+ maxIndex = Math.max(d.index, maxIndex);
+ }
+
int n = mBaseSet.getMediaItemCount();
- int from = Math.max(mDeletionIndexHint - 5, 0);
- int to = Math.min(mDeletionIndexHint + 5, n);
+ int from = Math.max(minIndex - 5, 0);
+ int to = Math.min(maxIndex + 5, n);
ArrayList<MediaItem> items = mBaseSet.getMediaItem(from, to - from);
+ ArrayList<Deletion> result = new ArrayList<Deletion>();
for (int i = 0; i < items.size(); i++) {
MediaItem item = items.get(i);
- if (item != null && item.getPath() == mDeletionPath) {
- mDeletionInEffect = true;
- mDeletionIndex = i + from;
+ if (item == null) continue;
+ Path p = item.getPath();
+ // Find the matching path in mCurrent, if found move it to result
+ for (int j = 0; j < mCurrent.size(); j++) {
+ Deletion d = mCurrent.get(j);
+ if (d.path == p) {
+ d.index = from + i;
+ result.add(d);
+ mCurrent.remove(j);
+ break;
+ }
}
}
- // We cannot find this path. Set it to null to avoid further search.
- if (!mDeletionInEffect) {
- mDeletionPath = null;
- }
+ mCurrent = result;
}
+
+ mMediaItemCount = mBaseSet.getMediaItemCount() - mCurrent.size();
mDataVersion = nextVersionNumber();
return mDataVersion;
}
+ private void sendRequest(int type, Path path, int indexHint) {
+ Request r = new Request(type, path, indexHint);
+ synchronized (mRequests) {
+ mRequests.add(r);
+ }
+ notifyContentChanged();
+ }
+
@Override
public void onContentDirty() {
notifyContentChanged();
}
- public void setDeletion(Path path, int indexHint) {
- mDeletionPath = path;
- mDeletionIndexHint = indexHint;
- mNewDeletionSettingPending = true;
- notifyContentChanged();
+ public void addDeletion(Path path, int indexHint) {
+ sendRequest(REQUEST_ADD, path, indexHint);
+ }
+
+ public void removeDeletion(Path path) {
+ sendRequest(REQUEST_REMOVE, path, 0 /* unused */);
+ }
+
+ public void clearDeletion() {
+ sendRequest(REQUEST_CLEAR, null /* unused */ , 0 /* unused */);
+ }
+
+ // Returns number of deletions _in effect_ (the number will only gets
+ // updated after a reload()).
+ public int getNumberOfDeletions() {
+ return mCurrent.size();
}
}
diff --git a/src/com/android/gallery3d/data/LocalAlbum.java b/src/com/android/gallery3d/data/LocalAlbum.java
index 117dbb6fa..f45eebfd8 100644
--- a/src/com/android/gallery3d/data/LocalAlbum.java
+++ b/src/com/android/gallery3d/data/LocalAlbum.java
@@ -260,6 +260,7 @@ public class LocalAlbum extends MediaSet {
GalleryUtils.assertNotInRenderThread();
mResolver.delete(mBaseUri, mWhereClause,
new String[]{String.valueOf(mBucketId)});
+ mApplication.getDataManager().broadcastLocalDeletion();
}
@Override
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index aa27c6f42..316e32469 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.data;
+import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
@@ -26,9 +27,11 @@ import android.media.ExifInterface;
import android.net.Uri;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.MediaColumns;
import android.util.Log;
import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.ThreadPool.Job;
@@ -74,10 +77,22 @@ public class LocalImage extends LocalMediaItem {
ImageColumns.ORIENTATION, // 9
ImageColumns.BUCKET_ID, // 10
ImageColumns.SIZE, // 11
- ImageColumns.WIDTH, // 12
- ImageColumns.HEIGHT // 13
+ "0", // 12
+ "0" // 13
};
+ static {
+ updateWidthAndHeightProjection();
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private static void updateWidthAndHeightProjection() {
+ if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) {
+ PROJECTION[INDEX_WIDTH] = MediaColumns.WIDTH;
+ PROJECTION[INDEX_HEIGHT] = MediaColumns.HEIGHT;
+ }
+ }
+
private final GalleryApp mApplication;
public int rotation;
@@ -231,6 +246,7 @@ public class LocalImage extends LocalMediaItem {
Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI;
mApplication.getContentResolver().delete(baseUri, "_id=?",
new String[]{String.valueOf(id)});
+ mApplication.getDataManager().broadcastLocalDeletion();
}
private static String getExifOrientation(int orientation) {
diff --git a/src/com/android/gallery3d/data/LocalVideo.java b/src/com/android/gallery3d/data/LocalVideo.java
index 4e888a573..5ccc21b93 100644
--- a/src/com/android/gallery3d/data/LocalVideo.java
+++ b/src/com/android/gallery3d/data/LocalVideo.java
@@ -187,6 +187,7 @@ public class LocalVideo extends LocalMediaItem {
Uri baseUri = Video.Media.EXTERNAL_CONTENT_URI;
mApplication.getContentResolver().delete(baseUri, "_id=?",
new String[]{String.valueOf(id)});
+ mApplication.getDataManager().broadcastLocalDeletion();
}
@Override
diff --git a/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
index c18652d5b..98139026e 100644
--- a/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
+++ b/src/com/android/gallery3d/gadget/PhotoAppWidgetProvider.java
@@ -29,6 +29,7 @@ import android.widget.RemoteViews;
import com.android.gallery3d.R;
import com.android.gallery3d.gadget.WidgetDatabaseHelper.Entry;
+import com.android.gallery3d.onetimeinitializer.GalleryWidgetMigrator;
public class PhotoAppWidgetProvider extends AppWidgetProvider {
@@ -49,6 +50,9 @@ public class PhotoAppWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context,
AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ // migrate gallery widgets from pre-JB releases to JB due to bucket ID change
+ GalleryWidgetMigrator.migrateGalleryWidgets(context);
+
WidgetDatabaseHelper helper = new WidgetDatabaseHelper(context);
try {
for (int id : appWidgetIds) {
@@ -66,6 +70,7 @@ public class PhotoAppWidgetProvider extends AppWidgetProvider {
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
+ @SuppressWarnings("deprecation")
private static RemoteViews buildStackWidget(Context context, int widgetId, Entry entry) {
RemoteViews views = new RemoteViews(
context.getPackageName(), R.layout.appwidget_main);
@@ -76,7 +81,10 @@ public class PhotoAppWidgetProvider extends AppWidgetProvider {
intent.putExtra(WidgetService.EXTRA_ALBUM_PATH, entry.albumPath);
intent.setData(Uri.parse("widget://gallery/" + widgetId));
- views.setRemoteAdapter(R.id.appwidget_stack_view, intent);
+ // We use the deprecated API for backward compatibility
+ // The new API is available in ICE_CREAM_SANDWICH (15)
+ views.setRemoteAdapter(widgetId, R.id.appwidget_stack_view, intent);
+
views.setEmptyView(R.id.appwidget_stack_view, R.id.appwidget_empty_view);
Intent clickIntent = new Intent(context, WidgetClickHandler.class);
@@ -122,4 +130,4 @@ public class PhotoAppWidgetProvider extends AppWidgetProvider {
}
helper.close();
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java
index b8ef7a74f..c411c365f 100644
--- a/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java
+++ b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java
@@ -30,6 +30,7 @@ import com.android.gallery3d.common.Utils;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
+import java.util.List;
public class WidgetDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "PhotoDatabaseHelper";
@@ -50,12 +51,15 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper {
public static final int TYPE_ALBUM = 2;
private static final String[] PROJECTION = {
- FIELD_WIDGET_TYPE, FIELD_IMAGE_URI, FIELD_PHOTO_BLOB, FIELD_ALBUM_PATH};
+ FIELD_WIDGET_TYPE, FIELD_IMAGE_URI, FIELD_PHOTO_BLOB, FIELD_ALBUM_PATH,
+ FIELD_APPWIDGET_ID};
private static final int INDEX_WIDGET_TYPE = 0;
private static final int INDEX_IMAGE_URI = 1;
private static final int INDEX_PHOTO_BLOB = 2;
private static final int INDEX_ALBUM_PATH = 3;
- private static final String WHERE_CLAUSE = FIELD_APPWIDGET_ID + " = ?";
+ private static final int INDEX_APPWIDGET_ID = 4;
+ private static final String WHERE_APPWIDGET_ID = FIELD_APPWIDGET_ID + " = ?";
+ private static final String WHERE_WIDGET_TYPE = FIELD_WIDGET_TYPE + " = ?";
public static class Entry {
public int widgetId;
@@ -76,6 +80,10 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper {
albumPath = cursor.getString(INDEX_ALBUM_PATH);
}
}
+
+ private Entry(Cursor cursor) {
+ this(cursor.getInt(INDEX_APPWIDGET_ID), cursor);
+ }
}
public WidgetDatabaseHelper(Context context) {
@@ -212,7 +220,7 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper {
try {
SQLiteDatabase db = getReadableDatabase();
cursor = db.query(TABLE_WIDGETS, PROJECTION,
- WHERE_CLAUSE, new String[] {String.valueOf(appWidgetId)},
+ WHERE_APPWIDGET_ID, new String[] {String.valueOf(appWidgetId)},
null, null, null);
if (cursor == null || !cursor.moveToNext()) {
Log.e(TAG, "query fail: empty cursor: " + cursor);
@@ -227,16 +235,58 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper {
}
}
+ public List<Entry> getEntries(int type) {
+ Cursor cursor = null;
+ try {
+ SQLiteDatabase db = getReadableDatabase();
+ cursor = db.query(TABLE_WIDGETS, PROJECTION,
+ WHERE_WIDGET_TYPE, new String[] {String.valueOf(type)},
+ null, null, null);
+ if (cursor == null) {
+ Log.e(TAG, "query fail: null cursor: " + cursor);
+ return null;
+ }
+ ArrayList<Entry> result = new ArrayList<Entry>(cursor.getCount());
+ while (cursor.moveToNext()) {
+ result.add(new Entry(cursor));
+ }
+ return result;
+ } catch (Throwable e) {
+ Log.e(TAG, "Could not load widget from database", e);
+ return null;
+ } finally {
+ Utils.closeSilently(cursor);
+ }
+ }
+
+ /**
+ * Updates the entry in the widget database.
+ */
+ public void updateEntry(Entry entry) {
+ deleteEntry(entry.widgetId);
+ try {
+ ContentValues values = new ContentValues();
+ values.put(FIELD_APPWIDGET_ID, entry.widgetId);
+ values.put(FIELD_WIDGET_TYPE, entry.type);
+ values.put(FIELD_ALBUM_PATH, entry.albumPath);
+ values.put(FIELD_IMAGE_URI, entry.imageUri);
+ values.put(FIELD_PHOTO_BLOB, entry.imageData);
+ getWritableDatabase().insert(TABLE_WIDGETS, null, values);
+ } catch (Throwable e) {
+ Log.e(TAG, "set widget fail", e);
+ }
+ }
+
/**
* Remove any bitmap associated with the given appWidgetId.
*/
public void deleteEntry(int appWidgetId) {
try {
SQLiteDatabase db = getWritableDatabase();
- db.delete(TABLE_WIDGETS, WHERE_CLAUSE,
+ db.delete(TABLE_WIDGETS, WHERE_APPWIDGET_ID,
new String[] {String.valueOf(appWidgetId)});
} catch (SQLiteException e) {
Log.e(TAG, "Could not delete photo from database", e);
}
}
-} \ No newline at end of file
+}
diff --git a/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java b/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java
new file mode 100644
index 000000000..4d85baa40
--- /dev/null
+++ b/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.onetimeinitializer;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.LocalAlbum;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.gadget.WidgetDatabaseHelper;
+import com.android.gallery3d.gadget.WidgetDatabaseHelper.Entry;
+import com.android.gallery3d.util.GalleryUtils;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This one-timer migrates local-album gallery app widgets from pre-JB releases to JB (or later)
+ * due to bucket ID (i.e., directory hash) change in JB (as the external storage path is changed
+ * from /mnt/sdcard to /storage/sdcard0).
+ */
+public class GalleryWidgetMigrator {
+ private static final String TAG = "GalleryWidgetMigrator";
+ private static final String OLD_EXT_PATH = "/mnt/sdcard";
+ private static final String NEW_EXT_PATH =
+ Environment.getExternalStorageDirectory().getAbsolutePath();
+ private static final int RELATIVE_PATH_START = NEW_EXT_PATH.length();
+ private static final String KEY_MIGRATION_DONE = "gallery_widget_migration_done";
+
+ /**
+ * Migrates local-album gallery widgets from pre-JB releases to JB (or later) due to bucket ID
+ * (i.e., directory hash) change in JB.
+ */
+ public static void migrateGalleryWidgets(Context context) {
+ // no migration needed if path of external storage is not changed
+ if (OLD_EXT_PATH.equals(NEW_EXT_PATH)) return;
+
+ // only need to migrate once; the "done" bit is saved to SharedPreferences
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean isDone = prefs.getBoolean(KEY_MIGRATION_DONE, false);
+ if (isDone) return;
+
+ try {
+ migrateGalleryWidgetsInternal(context);
+ prefs.edit().putBoolean(KEY_MIGRATION_DONE, true).commit();
+ } catch (Throwable t) {
+ // exception may be thrown if external storage is not available(?)
+ Log.w(TAG, "migrateGalleryWidgets", t);
+ }
+ }
+
+ private static void migrateGalleryWidgetsInternal(Context context) {
+ GalleryApp galleryApp = (GalleryApp) context.getApplicationContext();
+ DataManager manager = galleryApp.getDataManager();
+ WidgetDatabaseHelper dbHelper = new WidgetDatabaseHelper(context);
+
+ // only need to migrate local-album entries of type TYPE_ALBUM
+ List<Entry> entries = dbHelper.getEntries(WidgetDatabaseHelper.TYPE_ALBUM);
+ if (entries != null) {
+ HashMap<Integer, Entry> localEntries = new HashMap<Integer, Entry>(entries.size());
+ for (Entry entry : entries) {
+ Path path = Path.fromString(entry.albumPath);
+ MediaSet mediaSet = (MediaSet) manager.getMediaObject(path);
+ if (mediaSet instanceof LocalAlbum) {
+ int bucketId = Integer.parseInt(path.getSuffix());
+ localEntries.put(bucketId, entry);
+ }
+ }
+ if (!localEntries.isEmpty()) migrateLocalEntries(localEntries, dbHelper);
+ }
+ }
+
+ private static void migrateLocalEntries(
+ HashMap<Integer, Entry> entries, WidgetDatabaseHelper dbHelper) {
+ File root = Environment.getExternalStorageDirectory();
+
+ // check the DCIM directory first; this should take care of 99% use cases
+ updatePath(new File(root, "DCIM"), entries, dbHelper);
+
+ // check other directories if DCIM doesn't cut it
+ if (!entries.isEmpty()) updatePath(root, entries, dbHelper);
+ }
+
+ private static void updatePath(
+ File root, HashMap<Integer, Entry> entries, WidgetDatabaseHelper dbHelper) {
+ File[] files = root.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory() && !entries.isEmpty()) {
+ String path = file.getAbsolutePath();
+ String oldPath = OLD_EXT_PATH + path.substring(RELATIVE_PATH_START);
+ int oldBucketId = GalleryUtils.getBucketId(oldPath);
+ Entry entry = entries.remove(oldBucketId);
+ if (entry != null) {
+ int newBucketId = GalleryUtils.getBucketId(path);
+ String newAlbumPath = Path.fromString(entry.albumPath)
+ .getParent()
+ .getChild(newBucketId)
+ .toString();
+ Log.d(TAG, "migrate from " + entry.albumPath + " to " + newAlbumPath);
+ entry.albumPath = newAlbumPath;
+ dbHelper.updateEntry(entry);
+ }
+ updatePath(file, entries, dbHelper); // recursion
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java
index 99ed8cb42..478cb4f87 100644
--- a/src/com/android/gallery3d/ui/GLRootView.java
+++ b/src/com/android/gallery3d/ui/GLRootView.java
@@ -16,6 +16,7 @@
package com.android.gallery3d.ui;
+import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
@@ -29,6 +30,7 @@ import android.view.View;
import com.android.gallery3d.R;
import com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.Profile;
@@ -536,12 +538,15 @@ public class GLRootView extends GLSurfaceView
}
@Override
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
public void setLightsOutMode(boolean enabled) {
- int flags = enabled
- ? SYSTEM_UI_FLAG_LOW_PROFILE
- | SYSTEM_UI_FLAG_FULLSCREEN
- | SYSTEM_UI_FLAG_LAYOUT_STABLE
- : 0;
+ int flags = 0;
+ if (enabled) {
+ flags = SYSTEM_UI_FLAG_LOW_PROFILE;
+ if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
+ flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+ }
setSystemUiVisibility(flags);
}
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index 496d34d05..e44905dbf 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -141,7 +141,8 @@ public class PhotoView extends GLView {
private static final int MSG_CAPTURE_ANIMATION_DONE = 4;
private static final int MSG_DELETE_ANIMATION_DONE = 5;
private static final int MSG_DELETE_DONE = 6;
- private static final int MSG_HIDE_UNDO_BAR = 7;
+ private static final int MSG_UNDO_BAR_TIMEOUT = 7;
+ private static final int MSG_UNDO_BAR_FULL_CAMERA = 8;
private static final int MOVE_THRESHOLD = 256;
private static final float SWIPE_THRESHOLD = 300f;
@@ -240,6 +241,7 @@ public class PhotoView extends GLView {
@Override
public void onClick(GLView v) {
mListener.onUndoDeleteImage();
+ hideUndoBar();
}
});
mLoadingText = StringTexture.newInstance(
@@ -330,6 +332,15 @@ public class PhotoView extends GLView {
mHandler.removeMessages(MSG_DELETE_DONE);
Message m = mHandler.obtainMessage(MSG_DELETE_DONE);
mHandler.sendMessageDelayed(m, 2000);
+
+ int numberOfPictures = mNextBound - mPrevBound + 1;
+ if (numberOfPictures == 2) {
+ if (mModel.isCamera(mNextBound)
+ || mModel.isCamera(mPrevBound)) {
+ numberOfPictures--;
+ }
+ }
+ showUndoBar(numberOfPictures <= 1);
break;
}
case MSG_DELETE_DONE: {
@@ -339,10 +350,14 @@ public class PhotoView extends GLView {
}
break;
}
- case MSG_HIDE_UNDO_BAR: {
+ case MSG_UNDO_BAR_TIMEOUT: {
checkHideUndoBar(UNDO_BAR_TIMEOUT);
break;
}
+ case MSG_UNDO_BAR_FULL_CAMERA: {
+ checkHideUndoBar(UNDO_BAR_FULL_CAMERA);
+ break;
+ }
default: throw new AssertionError(message.what);
}
}
@@ -593,7 +608,7 @@ public class PhotoView extends GLView {
if ((mHolding & ~HOLD_TOUCH_DOWN) != 0) return;
boolean isCenter = mPositionController.isCenter();
- boolean isCameraCenter = mIsCamera && isCenter;
+ boolean isCameraCenter = mIsCamera && isCenter && !canUndoLastPicture();
if (mWasCameraCenter && mIsCamera && !isCenter && !mFilmMode) {
// Temporary disabled to de-emphasize filmstrip.
@@ -1255,6 +1270,7 @@ public class PhotoView extends GLView {
for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
mPictures.get(i).setScreenNail(null);
}
+ hideUndoBar();
}
public void resume() {
@@ -1275,33 +1291,56 @@ public class PhotoView extends GLView {
private static final int UNDO_BAR_SHOW = 1;
private static final int UNDO_BAR_TIMEOUT = 2;
private static final int UNDO_BAR_TOUCHED = 4;
+ private static final int UNDO_BAR_FULL_CAMERA = 8;
+ private static final int UNDO_BAR_DELETE_LAST = 16;
- public void showUndoBar() {
- mHandler.removeMessages(MSG_HIDE_UNDO_BAR);
+ // "deleteLast" means if the deletion is on the last remaining picture in
+ // the album.
+ private void showUndoBar(boolean deleteLast) {
+ mHandler.removeMessages(MSG_UNDO_BAR_TIMEOUT);
mUndoBarState = UNDO_BAR_SHOW;
+ if(deleteLast) mUndoBarState |= UNDO_BAR_DELETE_LAST;
mUndoBar.animateVisibility(GLView.VISIBLE);
- mHandler.sendEmptyMessageDelayed(MSG_HIDE_UNDO_BAR, 3000);
+ mHandler.sendEmptyMessageDelayed(MSG_UNDO_BAR_TIMEOUT, 3000);
}
- public void hideUndoBar() {
- mHandler.removeMessages(MSG_HIDE_UNDO_BAR);
+ private void hideUndoBar() {
+ mHandler.removeMessages(MSG_UNDO_BAR_TIMEOUT);
mListener.onCommitDeleteImage();
mUndoBar.animateVisibility(GLView.INVISIBLE);
mUndoBarState = 0;
mUndoIndexHint = Integer.MAX_VALUE;
}
- // Check if the all conditions for hiding the undo bar have been met. The
- // conditions are: it has been three seconds since last showing, and the
- // user has touched.
+ // Check if the one of the conditions for hiding the undo bar has been
+ // met. The conditions are:
+ //
+ // 1. It has been three seconds since last showing, and (a) the user has
+ // touched, or (b) the deleted picture is the last remaining picture in the
+ // album.
+ //
+ // 2. The camera is shown in full screen.
private void checkHideUndoBar(int addition) {
mUndoBarState |= addition;
- if (mUndoBarState ==
- (UNDO_BAR_SHOW | UNDO_BAR_TIMEOUT | UNDO_BAR_TOUCHED)) {
+ if ((mUndoBarState & UNDO_BAR_SHOW) == 0) return;
+ boolean timeout = (mUndoBarState & UNDO_BAR_TIMEOUT) != 0;
+ boolean touched = (mUndoBarState & UNDO_BAR_TOUCHED) != 0;
+ boolean fullCamera = (mUndoBarState & UNDO_BAR_FULL_CAMERA) != 0;
+ boolean deleteLast = (mUndoBarState & UNDO_BAR_DELETE_LAST) != 0;
+ if ((timeout && (touched || deleteLast)) || fullCamera) {
hideUndoBar();
}
}
+ // Returns true if the user can still undo the deletion of the last
+ // remaining picture in the album. We need to check this and delay making
+ // the camera preview full screen, otherwise the user won't have a chance to
+ // undo it.
+ private boolean canUndoLastPicture() {
+ if ((mUndoBarState & UNDO_BAR_SHOW) == 0) return false;
+ return (mUndoBarState & UNDO_BAR_DELETE_LAST) != 0;
+ }
+
////////////////////////////////////////////////////////////////////////////
// Rendering
////////////////////////////////////////////////////////////////////////////
@@ -1315,6 +1354,7 @@ public class PhotoView extends GLView {
if (full != mFullScreenCamera) {
mFullScreenCamera = full;
mListener.onFullScreenChanged(full);
+ if (full) mHandler.sendEmptyMessage(MSG_UNDO_BAR_FULL_CAMERA);
}
// Determine how many photos we need to draw in addition to the center
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index d556cafdc..6d334709d 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -110,11 +110,11 @@ class PositionController {
private Listener mListener;
private volatile Rect mOpenAnimationRect;
- // Use a large enough value, so we won't see the gray shadown in the beginning.
+ // Use a large enough value, so we won't see the gray shadow in the beginning.
private int mViewW = 1200;
private int mViewH = 1200;
- // A scaling guesture is in progress.
+ // A scaling gesture is in progress.
private boolean mInScale;
// The focus point of the scaling gesture, relative to the center of the
// picture in bitmap pixels.
@@ -178,7 +178,7 @@ class PositionController {
private RangeArray<Gap> mTempGaps =
new RangeArray<Gap>(-BOX_MAX, BOX_MAX - 1);
- // The output of the PositionController. Available throught getPosition().
+ // The output of the PositionController. Available through getPosition().
private RangeArray<Rect> mRects = new RangeArray<Rect>(-BOX_MAX, BOX_MAX);
// The direction of a new picture should appear. New pictures pop from top
@@ -211,7 +211,7 @@ class PositionController {
mListener = listener;
mPageScroller = new FlingScroller();
mFilmScroller = new OverScroller(context,
- null /* default interpolator */, false /* no flywheel */);
+ null /* default interpolator */, 0, 0, false /* no flywheel */);
// Initialize the areas.
initPlatform();
@@ -519,7 +519,7 @@ class PositionController {
Platform p = mPlatform;
// We want to keep the focus point (on the bitmap) the same as when we
- // begin the scale guesture, that is,
+ // begin the scale gesture, that is,
//
// (focusX' - currentX') / scale' = (focusX - currentX) / scale
//
@@ -1005,7 +1005,7 @@ class PositionController {
// N N N N N N N -- all new boxes
// -3 -2 -1 0 1 2 3 -- nothing changed
// -2 -1 0 1 2 3 N -- focus goes to the next box
- // N -3 -2 -1 0 1 2 -- focuse goes to the previous box
+ // N -3 -2 -1 0 1 2 -- focus goes to the previous box
// -3 -2 -1 1 2 3 N -- the focused box was deleted.
//
// hasPrev/hasNext indicates if there are previous/next boxes for the
@@ -1019,7 +1019,7 @@ class PositionController {
RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
- // 1. Get the absolute X coordiates for the boxes.
+ // 1. Get the absolute X coordinates for the boxes.
layoutAndSetPosition();
for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
Box b = mBoxes.get(i);
@@ -1366,7 +1366,7 @@ class PositionController {
public int mAnimationKind;
public int mAnimationDuration;
- // This should be overidden in subclass to change the animation values
+ // This should be overridden in subclass to change the animation values
// give the progress value in [0, 1].
protected abstract boolean interpolate(float progress);
public abstract boolean startSnapback();
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index adbf850c1..fc61efd64 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -23,6 +23,7 @@ import android.graphics.RectF;
import android.util.FloatMath;
import com.android.gallery3d.app.GalleryContext;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.LongSparseArray;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.BitmapPool;
@@ -48,7 +49,9 @@ public class TileImageView extends GLView {
private static final int UPLOAD_LIMIT = 1;
private static final BitmapPool sTilePool =
- new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128);
+ ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER
+ ? new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128)
+ : null;
/*
* This is the tile state in the CPU side.
@@ -378,7 +381,7 @@ public class TileImageView extends GLView {
}
}
setScreenNail(null);
- sTilePool.clear();
+ if (sTilePool != null) sTilePool.clear();
}
public void prepareTextures() {
@@ -487,7 +490,7 @@ public class TileImageView extends GLView {
if (tile.mTileState == STATE_RECYCLING) {
tile.mTileState = STATE_RECYCLED;
if (tile.mDecodedTile != null) {
- sTilePool.recycle(tile.mDecodedTile);
+ if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
tile.mDecodedTile = null;
}
mRecycledQueue.push(tile);
@@ -515,7 +518,7 @@ public class TileImageView extends GLView {
}
tile.mTileState = STATE_RECYCLED;
if (tile.mDecodedTile != null) {
- sTilePool.recycle(tile.mDecodedTile);
+ if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
tile.mDecodedTile = null;
}
mRecycledQueue.push(tile);
@@ -653,7 +656,7 @@ public class TileImageView extends GLView {
@Override
protected void onFreeBitmap(Bitmap bitmap) {
- sTilePool.recycle(bitmap);
+ if (sTilePool != null) sTilePool.recycle(bitmap);
}
boolean decode() {
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index 0b4ac0375..4865e5c67 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -20,8 +20,10 @@ import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
import android.graphics.Rect;
+import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.BitmapPool;
@@ -108,6 +110,11 @@ public class TileImageViewAdapter implements TileImageView.Model {
@Override
public Bitmap getTile(int level, int x, int y, int tileSize,
int borderSize, BitmapPool pool) {
+
+ if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER) {
+ return getTileWithoutReusingBitmap(level, x, y, tileSize, borderSize);
+ }
+
int b = borderSize << level;
int t = tileSize << level;
@@ -158,6 +165,49 @@ public class TileImageViewAdapter implements TileImageView.Model {
return bitmap;
}
+ private Bitmap getTileWithoutReusingBitmap(
+ int level, int x, int y, int tileSize, int borderSize) {
+ int b = borderSize << level;
+ int t = tileSize << level;
+ Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b);
+
+ BitmapRegionDecoder regionDecoder;
+ Rect overlapRegion;
+
+ synchronized (this) {
+ regionDecoder = mRegionDecoder;
+ if (regionDecoder == null) return null;
+ overlapRegion = new Rect(0, 0, mImageWidth, mImageHeight);
+ Utils.assertTrue(overlapRegion.intersect(wantRegion));
+ }
+
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredConfig = Config.ARGB_8888;
+ options.inPreferQualityOverSpeed = true;
+ options.inSampleSize = (1 << level);
+ Bitmap bitmap = null;
+
+ // In CropImage, we may call the decodeRegion() concurrently.
+ synchronized (regionDecoder) {
+ bitmap = regionDecoder.decodeRegion(overlapRegion, options);
+ }
+
+ if (bitmap == null) {
+ Log.w(TAG, "fail in decoding region");
+ }
+
+ if (wantRegion.equals(overlapRegion)) return bitmap;
+
+ int s = tileSize + 2 * borderSize;
+ Bitmap result = Bitmap.createBitmap(s, s, Config.ARGB_8888);
+ Canvas canvas = new Canvas(result);
+ canvas.drawBitmap(bitmap,
+ (overlapRegion.left - wantRegion.left) >> level,
+ (overlapRegion.top - wantRegion.top) >> level, null);
+ return result;
+ }
+
+
@Override
public ScreenNail getScreenNail() {
return mScreenNail;
diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java
index 1d70914ce..1291ee9f7 100644
--- a/src/com/android/gallery3d/util/GalleryUtils.java
+++ b/src/com/android/gallery3d/util/GalleryUtils.java
@@ -16,7 +16,6 @@
package com.android.gallery3d.util;
-import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
@@ -24,7 +23,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.ConditionVariable;
import android.os.Environment;