/* * 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.net.Uri; import android.os.Bundle; 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.AlbumView; import com.android.gallery3d.ui.DetailsHelper; import com.android.gallery3d.ui.DetailsHelper.CloseListener; import com.android.gallery3d.ui.GLCanvas; import com.android.gallery3d.ui.GLView; import com.android.gallery3d.ui.GridDrawer; import com.android.gallery3d.ui.HighlightDrawer; import com.android.gallery3d.ui.PositionProvider; import com.android.gallery3d.ui.PositionRepository; import com.android.gallery3d.ui.PositionRepository.Position; import com.android.gallery3d.ui.SelectionManager; import com.android.gallery3d.ui.SlotView; import com.android.gallery3d.ui.StaticBackground; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.GalleryUtils; import java.util.Random; public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner, SelectionManager.SelectionListener, MediaSet.SyncListener { @SuppressWarnings("unused") private static final String TAG = "AlbumPage"; public static final String KEY_MEDIA_PATH = "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"; 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 StaticBackground mStaticBackground; private AlbumView mAlbumView; private Path mMediaSetPath; private AlbumDataAdapter mAlbumDataAdapter; protected SelectionManager mSelectionManager; private Vibrator mVibrator; private GridDrawer mGridDrawer; private HighlightDrawer mHighlightDrawer; 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 Future<Integer> mSyncTask = null; private int mLoadingBits = 0; private boolean mInitialSynced = false; private final GLView mRootPane = new GLView() { private final float mMatrix[] = new float[16]; @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom) { mStaticBackground.layout(0, 0, right - left, bottom - top); int slotViewTop = GalleryActionBar.getHeight((Activity) mActivity); int slotViewBottom = bottom - top; int slotViewRight = right - left; if (mShowDetails) { mDetailsHelper.layout(left, slotViewTop, right, bottom); } else { mAlbumView.setSelectionDrawer(mGridDrawer); } mAlbumView.layout(0, slotViewTop, slotViewRight, slotViewBottom); GalleryUtils.setViewPointMatrix(mMatrix, (right - left) / 2, (bottom - top) / 2, -mUserDistance); PositionRepository.getInstance(mActivity).setOffset( 0, slotViewTop); } @Override protected void render(GLCanvas canvas) { canvas.save(GLCanvas.SAVE_FLAG_MATRIX); canvas.multiplyMatrix(mMatrix, 0); super.render(canvas); canvas.restore(); } }; @Override protected void onBackPressed() { if (mShowDetails) { hideDetails(); } else if (mSelectionManager.inSelectionMode()) { mSelectionManager.leaveSelectionMode(); } else { mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); super.onBackPressed(); } } private void onDown(int index) { MediaItem item = mAlbumDataAdapter.get(index); Path path = (item == null) ? null : item.getPath(); mSelectionManager.setPressedPath(path); mAlbumView.invalidate(); } private void onUp() { mSelectionManager.setPressedPath(null); mAlbumView.invalidate(); } public void onSingleTapUp(int slotIndex) { MediaItem item = mAlbumDataAdapter.get(slotIndex); if (item == null) { Log.w(TAG, "item not ready yet, ignore the click"); return; } if (mShowDetails) { mHighlightDrawer.setHighlightItem(item.getPath()); mDetailsHelper.reloadDetails(slotIndex); } else if (!mSelectionManager.inSelectionMode()) { if (mGetContent) { onGetContent(item); } else { // Get into the PhotoPage. Bundle data = new Bundle(); mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); data.putInt(PhotoPage.KEY_INDEX_HINT, 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); } } else { mSelectionManager.toggle(item.getPath()); mDetailsSource.findIndex(slotIndex); mAlbumView.invalidate(); } } 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; if (mShowDetails) { onSingleTapUp(slotIndex); } else { MediaItem item = mAlbumDataAdapter.get(slotIndex); if (item == null) return; mSelectionManager.setAutoLeaveSelectionMode(true); mSelectionManager.toggle(item.getPath()); mDetailsSource.findIndex(slotIndex); mAlbumView.invalidate(); } } 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); } public void doFilter(int filterType) { String basePath = mMediaSet.getPath().toString(); String newPath = FilterUtils.switchFilterPath(basePath, filterType); Bundle data = new Bundle(getData()); data.putString(AlbumPage.KEY_MEDIA_PATH, newPath); mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); mActivity.getStateManager().switchState(this, AlbumPage.class, data); } public void onOperationComplete() { mAlbumView.invalidate(); // TODO: enable animation } @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); startTransition(data); // Enable auto-select-all for mtp album if (data.getBoolean(KEY_AUTO_SELECT_ALL)) { mSelectionManager.selectAll(); } } private void startTransition() { final PositionRepository repository = PositionRepository.getInstance(mActivity); mAlbumView.startTransition(new PositionProvider() { private final Position mTempPosition = new Position(); public Position getPosition(long identity, Position target) { Position p = repository.get(identity); if (p != null) return p; mTempPosition.set(target); mTempPosition.z = 128; return mTempPosition; } }); } private void startTransition(Bundle data) { final PositionRepository repository = PositionRepository.getInstance(mActivity); final int[] center = data == null ? null : data.getIntArray(KEY_SET_CENTER); final Random random = new Random(); mAlbumView.startTransition(new PositionProvider() { private final Position mTempPosition = new Position(); public Position getPosition(long identity, Position target) { Position p = repository.get(identity); if (p != null) return p; if (center != null) { random.setSeed(identity); mTempPosition.set(center[0], center[1], 0, random.nextInt(60) - 30, 0); } else { mTempPosition.set(target); mTempPosition.z = 128; } return mTempPosition; } }); } @Override protected void onResume() { super.onResume(); mIsActive = true; setContentPane(mRootPane); // 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; mAlbumDataAdapter.pause(); mAlbumView.pause(); DetailsHelper.pause(); if (mSyncTask != null) { mSyncTask.cancel(); mSyncTask = null; } mActionModeHandler.pause(); } @Override protected void onDestroy() { super.onDestroy(); if (mAlbumDataAdapter != null) { mAlbumDataAdapter.setLoadingListener(null); } } private void initializeViews() { mStaticBackground = new StaticBackground((Context) mActivity); mRootPane.addComponent(mStaticBackground); mSelectionManager = new SelectionManager(mActivity, false); mSelectionManager.setSelectionListener(this); mGridDrawer = new GridDrawer((Context) mActivity, mSelectionManager); Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity); mAlbumView = new AlbumView(mActivity, config.slotViewSpec, 0 /* don't cache thumbnail */); mAlbumView.setSelectionDrawer(mGridDrawer); mRootPane.addComponent(mAlbumView); mAlbumView.setListener(new SlotView.SimpleListener() { @Override public void onDown(int index) { AlbumPage.this.onDown(index); } @Override public void onUp() { AlbumPage.this.onUp(); } @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); } }); mStaticBackground.setImage(R.drawable.background, R.drawable.background_portrait); } private void initializeData(Bundle data) { mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH)); mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath); Utils.assertTrue(mMediaSet != null, "MediaSet is null. Path = %s", mMediaSetPath); mSelectionManager.setSourceMediaSet(mMediaSet); mAlbumDataAdapter = new AlbumDataAdapter(mActivity, mMediaSet); mAlbumDataAdapter.setLoadingListener(new MyLoadingListener()); mAlbumView.setModel(mAlbumDataAdapter); } private void showDetails() { mShowDetails = true; if (mDetailsHelper == null) { mHighlightDrawer = new HighlightDrawer(mActivity.getAndroidContext(), mSelectionManager); mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource); mDetailsHelper.setCloseListener(new CloseListener() { public void onClose() { hideDetails(); } }); } mAlbumView.setSelectionDrawer(mHighlightDrawer); mDetailsHelper.show(); } private void hideDetails() { mShowDetails = false; mDetailsHelper.hide(); mAlbumView.setSelectionDrawer(mGridDrawer); mAlbumView.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); } MenuItem groupBy = menu.findItem(R.id.action_group_by); FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true); if (groupBy != null) { groupBy.setVisible(mShowClusterMenu); } actionBar.setTitle(mMediaSet.getName()); } actionBar.setSubtitle(null); return true; } @Override protected boolean onItemSelected(MenuItem item) { switch (item.getItemId()) { 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; } 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); mAlbumView.setCenterIndex(mFocusIndex); break; } case REQUEST_PHOTO: { if (data == null) return; mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0); mAlbumView.setCenterIndex(mFocusIndex); startTransition(); break; } case REQUEST_DO_ANIMATION: { startTransition(null); 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() { if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) { mInitialSynced = true; } if (!mIsActive) return; clearLoadingBit(BIT_LOADING_SYNC); if (resultCode == MediaSet.SYNC_RESULT_ERROR) { Toast.makeText((Context) mActivity, R.string.sync_album_error, Toast.LENGTH_LONG).show(); } } }); } private void setLoadingBit(int loadTaskBit) { if (mLoadingBits == 0) { GalleryUtils.setSpinnerVisibility((Activity) mActivity, true); } mLoadingBits |= loadTaskBit; } private void clearLoadingBit(int loadTaskBit) { mLoadingBits &= ~loadTaskBit; if (mLoadingBits == 0) { GalleryUtils.setSpinnerVisibility((Activity) mActivity, false); 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() { if (!mIsActive) return; 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) { mHighlightDrawer.setHighlightItem(item.getPath()); return item.getDetails(); } else { return null; } } } }