blob: e7736918b001dafedcd8e24a8ca8336dadec6ec2 [file] [log] [blame]
/*
* Copyright (C) 2010 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.app;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.provider.MediaStore;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import com.android.gallery3d.R;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.MediaDetails;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.MtpDevice;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.ui.ActionModeHandler;
import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
import com.android.gallery3d.ui.AlbumSlotRenderer;
import com.android.gallery3d.ui.DetailsHelper;
import com.android.gallery3d.ui.DetailsHelper.CloseListener;
import com.android.gallery3d.ui.FadeTexture;
import com.android.gallery3d.ui.GLCanvas;
import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.ui.GLView;
import com.android.gallery3d.ui.PhotoFallbackEffect;
import com.android.gallery3d.ui.RelativePosition;
import com.android.gallery3d.ui.SelectionManager;
import com.android.gallery3d.ui.SlotView;
import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.MediaSetUtils;
public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
SelectionManager.SelectionListener, MediaSet.SyncListener {
@SuppressWarnings("unused")
private static final String TAG = "AlbumPage";
private static final int MSG_PICK_PHOTO = 1;
public static final String KEY_MEDIA_PATH = "media-path";
public static final String KEY_PARENT_MEDIA_PATH = "parent-media-path";
public static final String KEY_SET_CENTER = "set-center";
public static final String KEY_AUTO_SELECT_ALL = "auto-select-all";
public static final String KEY_SHOW_CLUSTER_MENU = "cluster-menu";
public static final String KEY_RESUME_ANIMATION = "resume_animation";
private static final int REQUEST_SLIDESHOW = 1;
private static final int REQUEST_PHOTO = 2;
private static final int REQUEST_DO_ANIMATION = 3;
private static final int BIT_LOADING_RELOAD = 1;
private static final int BIT_LOADING_SYNC = 2;
private static final float USER_DISTANCE_METER = 0.3f;
private boolean mIsActive = false;
private AlbumSlotRenderer mAlbumView;
private Path mMediaSetPath;
private String mParentMediaSetString;
private SlotView mSlotView;
private AlbumDataLoader mAlbumDataAdapter;
protected SelectionManager mSelectionManager;
private Vibrator mVibrator;
private boolean mGetContent;
private boolean mShowClusterMenu;
private ActionMode mActionMode;
private ActionModeHandler mActionModeHandler;
private int mFocusIndex = 0;
private DetailsHelper mDetailsHelper;
private MyDetailsSource mDetailsSource;
private MediaSet mMediaSet;
private boolean mShowDetails;
private float mUserDistance; // in pixel
private Handler mHandler;
private Future<Integer> mSyncTask = null;
private int mLoadingBits = 0;
private boolean mInitialSynced = false;
private RelativePosition mOpenCenter = new RelativePosition();
private PhotoFallbackEffect mResumeEffect;
private PhotoFallbackEffect.PositionProvider mPositionProvider =
new PhotoFallbackEffect.PositionProvider() {
@Override
public Rect getPosition(int index) {
Rect rect = mSlotView.getSlotRect(index);
Rect bounds = mSlotView.bounds();
rect.offset(bounds.left - mSlotView.getScrollX(),
bounds.top - mSlotView.getScrollY());
return rect;
}
@Override
public int getItemIndex(Path path) {
int start = mSlotView.getVisibleStart();
int end = mSlotView.getVisibleEnd();
for (int i = start; i < end; ++i) {
MediaItem item = mAlbumDataAdapter.get(i);
if (item != null && item.getPath() == path) return i;
}
return -1;
}
};
private final GLView mRootPane = new GLView() {
private final float mMatrix[] = new float[16];
@Override
protected void renderBackground(GLCanvas view) {
view.clearBuffer();
}
@Override
protected void onLayout(
boolean changed, int left, int top, int right, int bottom) {
int slotViewTop = mActivity.getGalleryActionBar().getHeight();
int slotViewBottom = bottom - top;
int slotViewRight = right - left;
if (mShowDetails) {
mDetailsHelper.layout(left, slotViewTop, right, bottom);
} else {
mAlbumView.setHighlightItemPath(null);
}
// Set the mSlotView as a reference point to the open animation
mOpenCenter.setReferencePosition(0, slotViewTop);
mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
GalleryUtils.setViewPointMatrix(mMatrix,
(right - left) / 2, (bottom - top) / 2, -mUserDistance);
}
@Override
protected void render(GLCanvas canvas) {
canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
canvas.multiplyMatrix(mMatrix, 0);
super.render(canvas);
if (mResumeEffect != null) {
boolean more = mResumeEffect.draw(canvas);
if (!more) {
mResumeEffect = null;
mAlbumView.setSlotFilter(null);
}
// We want to render one more time even when no more effect
// required. So that the animated thumbnails could be draw
// with declarations in super.render().
invalidate();
}
canvas.restore();
}
};
// This are the transitions we want:
//
// +--------+ +------------+ +-------+ +----------+
// | Camera |---------->| Fullscreen |--->| Album |--->| AlbumSet |
// | View | thumbnail | Photo | up | Page | up | Page |
// +--------+ +------------+ +-------+ +----------+
// ^ | | ^ |
// | | | | | close
// +----------back--------+ +----back----+ +--back-> app
//
@Override
protected void onBackPressed() {
if (mShowDetails) {
hideDetails();
} else if (mSelectionManager.inSelectionMode()) {
mSelectionManager.leaveSelectionMode();
} else {
// TODO: fix this regression
// mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
onUpPressed();
}
}
private void onUpPressed() {
if (mActivity.getStateManager().getStateCount() > 1) {
super.onBackPressed();
} else if (mParentMediaSetString != null) {
Bundle data = new Bundle(getData());
data.putString(AlbumSetPage.KEY_MEDIA_PATH, mParentMediaSetString);
mActivity.getStateManager().switchState(
this, AlbumSetPage.class, data);
}
}
private void onDown(int index) {
mAlbumView.setPressedIndex(index);
}
private void onUp(boolean followedByLongPress) {
if (followedByLongPress) {
// Avoid showing press-up animations for long-press.
mAlbumView.setPressedIndex(-1);
} else {
mAlbumView.setPressedUp();
}
}
private void onSingleTapUp(int slotIndex) {
if (!mIsActive) return;
if (mSelectionManager.inSelectionMode()) {
MediaItem item = mAlbumDataAdapter.get(slotIndex);
if (item == null) return; // Item not ready yet, ignore the click
mSelectionManager.toggle(item.getPath());
mDetailsSource.findIndex(slotIndex);
mSlotView.invalidate();
} else {
// Show pressed-up animation for the single-tap.
mAlbumView.setPressedIndex(slotIndex);
mAlbumView.setPressedUp();
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PICK_PHOTO, slotIndex, 0),
FadeTexture.DURATION);
}
}
private void pickPhoto(int slotIndex) {
if (!mIsActive) return;
MediaItem item = mAlbumDataAdapter.get(slotIndex);
if (item == null) return; // Item not ready yet, ignore the click
if (mGetContent) {
onGetContent(item);
} else {
// Get into the PhotoPage.
// mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
Bundle data = new Bundle();
data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT,
getSlotRect(slotIndex));
data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
mMediaSetPath.toString());
data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
item.getPath().toString());
mActivity.getStateManager().startStateForResult(
PhotoPage.class, REQUEST_PHOTO, data);
}
}
private Rect getSlotRect(int slotIndex) {
// Get slot rectangle relative to this root pane.
Rect offset = new Rect();
mRootPane.getBoundsOf(mSlotView, offset);
Rect r = mSlotView.getSlotRect(slotIndex);
r.offset(offset.left - mSlotView.getScrollX(),
offset.top - mSlotView.getScrollY());
return r;
}
private void onGetContent(final MediaItem item) {
DataManager dm = mActivity.getDataManager();
Activity activity = (Activity) mActivity;
if (mData.getString(Gallery.EXTRA_CROP) != null) {
// TODO: Handle MtpImagew
Uri uri = dm.getContentUri(item.getPath());
Intent intent = new Intent(CropImage.ACTION_CROP, uri)
.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
.putExtras(getData());
if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
intent.putExtra(CropImage.KEY_RETURN_DATA, true);
}
activity.startActivity(intent);
activity.finish();
} else {
activity.setResult(Activity.RESULT_OK,
new Intent(null, item.getContentUri()));
activity.finish();
}
}
public void onLongTap(int slotIndex) {
if (mGetContent) return;
MediaItem item = mAlbumDataAdapter.get(slotIndex);
if (item == null) return;
mSelectionManager.setAutoLeaveSelectionMode(true);
mSelectionManager.toggle(item.getPath());
mDetailsSource.findIndex(slotIndex);
mSlotView.invalidate();
}
@Override
public void doCluster(int clusterType) {
String basePath = mMediaSet.getPath().toString();
String newPath = FilterUtils.newClusterPath(basePath, clusterType);
Bundle data = new Bundle(getData());
data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
if (mShowClusterMenu) {
Context context = mActivity.getAndroidContext();
data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName());
data.putString(AlbumSetPage.KEY_SET_SUBTITLE,
GalleryActionBar.getClusterByTypeString(context, clusterType));
}
// mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
mActivity.getStateManager().startStateForResult(
AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
}
@Override
protected void onCreate(Bundle data, Bundle restoreState) {
mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
initializeViews();
initializeData(data);
mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false);
mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false);
mDetailsSource = new MyDetailsSource();
Context context = mActivity.getAndroidContext();
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
// Enable auto-select-all for mtp album
if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
mSelectionManager.selectAll();
}
// Don't show animation if it is restored
if (restoreState == null && data != null) {
int[] center = data.getIntArray(KEY_SET_CENTER);
if (center != null) {
mOpenCenter.setAbsolutePosition(center[0], center[1]);
mSlotView.startScatteringAnimation(mOpenCenter);
}
}
mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_PICK_PHOTO: {
pickPhoto(message.arg1);
break;
}
default: throw new AssertionError(message.what);
}
}
};
}
@Override
protected void onResume() {
super.onResume();
mIsActive = true;
mResumeEffect = mActivity.getTransitionStore().get(KEY_RESUME_ANIMATION);
if (mResumeEffect != null) {
mAlbumView.setSlotFilter(mResumeEffect);
mResumeEffect.setPositionProvider(mPositionProvider);
mResumeEffect.start();
}
setContentPane(mRootPane);
Path path = mMediaSet.getPath();
boolean enableHomeButton = (mActivity.getStateManager().getStateCount() > 1) |
mParentMediaSetString != null;
mActivity.getGalleryActionBar().setDisplayOptions(enableHomeButton, true);
// Set the reload bit here to prevent it exit this page in clearLoadingBit().
setLoadingBit(BIT_LOADING_RELOAD);
mAlbumDataAdapter.resume();
mAlbumView.resume();
mActionModeHandler.resume();
if (!mInitialSynced) {
setLoadingBit(BIT_LOADING_SYNC);
mSyncTask = mMediaSet.requestSync(this);
}
}
@Override
protected void onPause() {
super.onPause();
mIsActive = false;
mAlbumView.setSlotFilter(null);
mAlbumDataAdapter.pause();
mAlbumView.pause();
DetailsHelper.pause();
if (mSyncTask != null) {
mSyncTask.cancel();
mSyncTask = null;
clearLoadingBit(BIT_LOADING_SYNC);
}
mActionModeHandler.pause();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mAlbumDataAdapter != null) {
mAlbumDataAdapter.setLoadingListener(null);
}
}
private void initializeViews() {
mSelectionManager = new SelectionManager(mActivity, false);
mSelectionManager.setSelectionListener(this);
Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity);
mSlotView = new SlotView(mActivity, config.slotViewSpec);
mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView, mSelectionManager);
mSlotView.setSlotRenderer(mAlbumView);
mRootPane.addComponent(mSlotView);
mSlotView.setListener(new SlotView.SimpleListener() {
@Override
public void onDown(int index) {
AlbumPage.this.onDown(index);
}
@Override
public void onUp(boolean followedByLongPress) {
AlbumPage.this.onUp(followedByLongPress);
}
@Override
public void onSingleTapUp(int slotIndex) {
AlbumPage.this.onSingleTapUp(slotIndex);
}
@Override
public void onLongTap(int slotIndex) {
AlbumPage.this.onLongTap(slotIndex);
}
});
mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
mActionModeHandler.setActionModeListener(new ActionModeListener() {
public boolean onActionItemClicked(MenuItem item) {
return onItemSelected(item);
}
});
}
private void initializeData(Bundle data) {
mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
mParentMediaSetString = data.getString(KEY_PARENT_MEDIA_PATH);
mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
if (mMediaSet == null) {
Utils.fail("MediaSet is null. Path = %s", mMediaSetPath);
}
mSelectionManager.setSourceMediaSet(mMediaSet);
mAlbumDataAdapter = new AlbumDataLoader(mActivity, mMediaSet);
mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
mAlbumView.setModel(mAlbumDataAdapter);
}
private void showDetails() {
mShowDetails = true;
if (mDetailsHelper == null) {
mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
mDetailsHelper.setCloseListener(new CloseListener() {
public void onClose() {
hideDetails();
}
});
}
mDetailsHelper.show();
}
private void hideDetails() {
mShowDetails = false;
mDetailsHelper.hide();
mAlbumView.setHighlightItemPath(null);
mSlotView.invalidate();
}
@Override
protected boolean onCreateActionBar(Menu menu) {
Activity activity = (Activity) mActivity;
GalleryActionBar actionBar = mActivity.getGalleryActionBar();
MenuInflater inflater = activity.getMenuInflater();
if (mGetContent) {
inflater.inflate(R.menu.pickup, menu);
int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
DataManager.INCLUDE_IMAGE);
actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
} else {
inflater.inflate(R.menu.album, menu);
actionBar.setTitle(mMediaSet.getName());
if (mMediaSet instanceof MtpDevice) {
menu.findItem(R.id.action_slideshow).setVisible(false);
} else {
menu.findItem(R.id.action_slideshow).setVisible(true);
}
FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
MenuItem groupBy = menu.findItem(R.id.action_group_by);
if (groupBy != null) {
groupBy.setVisible(mShowClusterMenu);
}
MenuItem switchCamera = menu.findItem(R.id.action_camera);
if (switchCamera != null) {
switchCamera.setVisible(
MediaSetUtils.isCameraSource(mMediaSetPath)
&& GalleryUtils.isCameraAvailable(activity));
}
actionBar.setTitle(mMediaSet.getName());
}
actionBar.setSubtitle(null);
return true;
}
@Override
protected boolean onItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: {
onUpPressed();
return true;
}
case R.id.action_cancel:
mActivity.getStateManager().finishState(this);
return true;
case R.id.action_select:
mSelectionManager.setAutoLeaveSelectionMode(false);
mSelectionManager.enterSelectionMode();
return true;
case R.id.action_group_by: {
mActivity.getGalleryActionBar().showClusterDialog(this);
return true;
}
case R.id.action_slideshow: {
Bundle data = new Bundle();
data.putString(SlideshowPage.KEY_SET_PATH,
mMediaSetPath.toString());
data.putBoolean(SlideshowPage.KEY_REPEAT, true);
mActivity.getStateManager().startStateForResult(
SlideshowPage.class, REQUEST_SLIDESHOW, data);
return true;
}
case R.id.action_details: {
if (mShowDetails) {
hideDetails();
} else {
showDetails();
}
return true;
}
case R.id.action_camera: {
GalleryUtils.startCameraActivity((Activity) mActivity);
return true;
}
default:
return false;
}
}
@Override
protected void onStateResult(int request, int result, Intent data) {
switch (request) {
case REQUEST_SLIDESHOW: {
// data could be null, if there is no images in the album
if (data == null) return;
mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
mSlotView.setCenterIndex(mFocusIndex);
break;
}
case REQUEST_PHOTO: {
if (data == null) return;
mFocusIndex = data.getIntExtra(PhotoPage.KEY_RETURN_INDEX_HINT, 0);
mSlotView.makeSlotVisible(mFocusIndex);
break;
}
case REQUEST_DO_ANIMATION: {
mSlotView.startRisingAnimation();
break;
}
}
}
public void onSelectionModeChange(int mode) {
switch (mode) {
case SelectionManager.ENTER_SELECTION_MODE: {
mActionMode = mActionModeHandler.startActionMode();
mVibrator.vibrate(100);
break;
}
case SelectionManager.LEAVE_SELECTION_MODE: {
mActionMode.finish();
mRootPane.invalidate();
break;
}
case SelectionManager.SELECT_ALL_MODE: {
mActionModeHandler.updateSupportedOperation();
mRootPane.invalidate();
break;
}
}
}
public void onSelectionChange(Path path, boolean selected) {
Utils.assertTrue(mActionMode != null);
int count = mSelectionManager.getSelectedCount();
String format = mActivity.getResources().getQuantityString(
R.plurals.number_of_items_selected, count);
mActionModeHandler.setTitle(String.format(format, count));
mActionModeHandler.updateSupportedOperation(path, selected);
}
@Override
public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
+ resultCode);
((Activity) mActivity).runOnUiThread(new Runnable() {
@Override
public void run() {
GLRoot root = mActivity.getGLRoot();
root.lockRenderThread();
try {
if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
mInitialSynced = true;
}
clearLoadingBit(BIT_LOADING_SYNC);
if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive
&& (mAlbumDataAdapter.size() == 0)) {
// show error toast only if the album is empty
Toast.makeText((Context) mActivity, R.string.sync_album_error,
Toast.LENGTH_LONG).show();
}
} finally {
root.unlockRenderThread();
}
}
});
}
private void setLoadingBit(int loadTaskBit) {
mLoadingBits |= loadTaskBit;
}
private void clearLoadingBit(int loadTaskBit) {
mLoadingBits &= ~loadTaskBit;
if (mLoadingBits == 0 && mIsActive) {
if (mAlbumDataAdapter.size() == 0) {
Toast.makeText((Context) mActivity,
R.string.empty_album, Toast.LENGTH_LONG).show();
mActivity.getStateManager().finishState(AlbumPage.this);
}
}
}
private class MyLoadingListener implements LoadingListener {
@Override
public void onLoadingStarted() {
setLoadingBit(BIT_LOADING_RELOAD);
}
@Override
public void onLoadingFinished() {
clearLoadingBit(BIT_LOADING_RELOAD);
}
}
private class MyDetailsSource implements DetailsHelper.DetailsSource {
private int mIndex;
public int size() {
return mAlbumDataAdapter.size();
}
public int getIndex() {
return mIndex;
}
// If requested index is out of active window, suggest a valid index.
// If there is no valid index available, return -1.
public int findIndex(int indexHint) {
if (mAlbumDataAdapter.isActive(indexHint)) {
mIndex = indexHint;
} else {
mIndex = mAlbumDataAdapter.getActiveStart();
if (!mAlbumDataAdapter.isActive(mIndex)) {
return -1;
}
}
return mIndex;
}
public MediaDetails getDetails() {
MediaObject item = mAlbumDataAdapter.get(mIndex);
if (item != null) {
mAlbumView.setHighlightItemPath(item.getPath());
return item.getDetails();
} else {
return null;
}
}
}
}