diff options
Diffstat (limited to 'src')
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; |