/* * Copyright (C) 5163 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.camera; import java.util.Random; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Matrix; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.util.AttributeSet; import android.util.Config; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.ViewGroup.LayoutParams; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Scroller; import android.widget.Toast; import android.widget.ZoomControls; import com.android.camera.ImageManager.IImage; public class ViewImage extends Activity implements View.OnClickListener { static final String TAG = "ViewImage"; private ImageGetter mGetter; static final boolean sSlideShowHidesStatusBar = true; // Choices for what adjacents to load. static private final int[] sOrder_adjacents = new int[] { 0, 1, -1 }; static private final int[] sOrder_slideshow = new int[] { 0 }; Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { } }; private Random mRandom = new Random(System.currentTimeMillis()); private int [] mShuffleOrder; private boolean mUseShuffleOrder = false; private boolean mSlideShowLoop = false; private static final int MODE_NORMAL = 1; private static final int MODE_SLIDESHOW = 2; private int mMode = MODE_NORMAL; private boolean mFullScreenInNormalMode; private boolean mShowActionIcons; private View mActionIconPanel; private View mShutterButton; private boolean mSortAscending = false; private int mSlideShowInterval; private int mLastSlideShowImage; private boolean mFirst = true; private int mCurrentPosition = 0; private boolean mLayoutComplete = false; // represents which style animation to use private int mAnimationIndex; private Animation [] mSlideShowInAnimation; private Animation [] mSlideShowOutAnimation; private SharedPreferences mPrefs; private View mNextImageView, mPrevImageView; private Animation mHideNextImageViewAnimation = new AlphaAnimation(1F, 0F); private Animation mHidePrevImageViewAnimation = new AlphaAnimation(1F, 0F); private Animation mShowNextImageViewAnimation = new AlphaAnimation(0F, 1F); private Animation mShowPrevImageViewAnimation = new AlphaAnimation(0F, 1F); static final int sPadding = 20; static final int sHysteresis = sPadding * 2; static final int sBaseScrollDuration = 1000; // ms private ImageManager.IImageList mAllImages; private int mSlideShowImageCurrent = 0; private ImageViewTouch [] mSlideShowImageViews = new ImageViewTouch[2]; // Array of image views. The center view is the one the user is focused // on. The one at the zeroth position and the second position reflect // the images to the left/right of the center image. private ImageViewTouch[] mImageViews = new ImageViewTouch[3]; // Container for the three image views. This guy can be "scrolled" // to reveal the image prior to and after the center image. private ScrollHandler mScroller; private MenuHelper.MenuItemsResult mImageMenuRunnable; private Runnable mDismissOnScreenControlsRunnable; private ZoomControls mZoomControls; private boolean mCameraReviewMode; public ViewImage() { } private void updateNextPrevControls() { boolean showPrev = mCurrentPosition > 0; boolean showNext = mCurrentPosition < mAllImages.getCount() - 1; boolean prevIsVisible = mPrevImageView.getVisibility() == View.VISIBLE; boolean nextIsVisible = mNextImageView.getVisibility() == View.VISIBLE; if (showPrev && !prevIsVisible) { Animation a = mShowPrevImageViewAnimation; a.setDuration(500); a.startNow(); mPrevImageView.setAnimation(a); mPrevImageView.setVisibility(View.VISIBLE); } else if (!showPrev && prevIsVisible) { Animation a = mHidePrevImageViewAnimation; a.setDuration(500); a.startNow(); mPrevImageView.setAnimation(a); mPrevImageView.setVisibility(View.GONE); } if (showNext && !nextIsVisible) { Animation a = mShowNextImageViewAnimation; a.setDuration(500); a.startNow(); mNextImageView.setAnimation(a); mNextImageView.setVisibility(View.VISIBLE); } else if (!showNext && nextIsVisible) { Animation a = mHideNextImageViewAnimation; a.setDuration(500); a.startNow(); mNextImageView.setAnimation(a); mNextImageView.setVisibility(View.GONE); } } private void showOnScreenControls() { if (mZoomControls != null) { if (mZoomControls.getVisibility() == View.GONE) { mZoomControls.show(); if (! mShowActionIcons) { mZoomControls.requestFocus(); // this shouldn't be necessary } } updateNextPrevControls(); scheduleDismissOnScreenControls(); } } @Override public boolean dispatchTouchEvent(MotionEvent m) { boolean sup = super.dispatchTouchEvent(m); if (sup == false) { if (mMode == MODE_SLIDESHOW) { mSlideShowImageViews[mSlideShowImageCurrent].handleTouchEvent(m); } else if (mMode == MODE_NORMAL){ mImageViews[1].handleTouchEvent(m); } return true; } return true; } private void scheduleDismissOnScreenControls() { mHandler.removeCallbacks(mDismissOnScreenControlsRunnable); mHandler.postDelayed(mDismissOnScreenControlsRunnable, 1500); } public View getZoomControls() { if (mZoomControls == null) { mZoomControls = new ZoomControls(this); mZoomControls.setVisibility(View.GONE); mZoomControls.setZoomSpeed(0); mDismissOnScreenControlsRunnable = new Runnable() { public void run() { mZoomControls.hide(); if (!mShowActionIcons) { if (mNextImageView.getVisibility() == View.VISIBLE) { Animation a = mHideNextImageViewAnimation; a.setDuration(500); a.startNow(); mNextImageView.setAnimation(a); mNextImageView.setVisibility(View.INVISIBLE); } if (mPrevImageView.getVisibility() == View.VISIBLE) { Animation a = mHidePrevImageViewAnimation; a.setDuration(500); a.startNow(); mPrevImageView.setAnimation(a); mPrevImageView.setVisibility(View.INVISIBLE); } } } }; mZoomControls.setOnZoomInClickListener(new OnClickListener() { public void onClick(View v) { mHandler.removeCallbacks(mDismissOnScreenControlsRunnable); mImageViews[1].zoomIn(); scheduleDismissOnScreenControls(); } }); mZoomControls.setOnZoomOutClickListener(new OnClickListener() { public void onClick(View v) { mHandler.removeCallbacks(mDismissOnScreenControlsRunnable); mImageViews[1].zoomOut(); scheduleDismissOnScreenControls(); } }); } return mZoomControls; } private boolean isPickIntent() { String action = getIntent().getAction(); return (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)); } private static final boolean sUseBounce = false; private static final boolean sAnimateTransitions = false; static public class ImageViewTouch extends ImageViewTouchBase { private ViewImage mViewImage; private boolean mEnableTrackballScroll; private static int TOUCH_STATE_REST = 0; private static int TOUCH_STATE_LEFT_PRESS = 1; private static int TOUCH_STATE_RIGHT_PRESS = 2; private static int TOUCH_STATE_PANNING = 3; private static int TOUCH_AREA_WIDTH = 60; private int mTouchState = TOUCH_STATE_REST; public ImageViewTouch(Context context) { super(context); mViewImage = (ViewImage) context; } public ImageViewTouch(Context context, AttributeSet attrs) { super(context, attrs); mViewImage = (ViewImage) context; } public void setEnableTrackballScroll(boolean enable) { mEnableTrackballScroll = enable; } protected void postTranslate(float dx, float dy, boolean bounceOK) { super.postTranslate(dx, dy); if (dx != 0F || dy != 0F) mViewImage.showOnScreenControls(); if (!sUseBounce) { center(true, false, false); } } protected ScrollHandler scrollHandler() { return mViewImage.mScroller; } public boolean handleTouchEvent(MotionEvent m) { int viewWidth = getWidth(); ViewImage viewImage = mViewImage; int x = (int) m.getX(); int y = (int) m.getY(); switch (m.getAction()) { case MotionEvent.ACTION_DOWN: viewImage.setMode(MODE_NORMAL); viewImage.showOnScreenControls(); mLastXTouchPos = x; mLastYTouchPos = y; mTouchState = TOUCH_STATE_REST; break; case MotionEvent.ACTION_MOVE: if (x < TOUCH_AREA_WIDTH) { if (mTouchState == TOUCH_STATE_REST) { mTouchState = TOUCH_STATE_LEFT_PRESS; } if (mTouchState == TOUCH_STATE_LEFT_PRESS) { viewImage.mPrevImageView.setPressed(true); viewImage.mNextImageView.setPressed(false); } mLastXTouchPos = x; mLastYTouchPos = y; } else if (x > viewWidth - TOUCH_AREA_WIDTH) { if (mTouchState == TOUCH_STATE_REST) { mTouchState = TOUCH_STATE_RIGHT_PRESS; } if (mTouchState == TOUCH_STATE_RIGHT_PRESS) { viewImage.mPrevImageView.setPressed(false); viewImage.mNextImageView.setPressed(true); } mLastXTouchPos = x; mLastYTouchPos = y; } else { mTouchState = TOUCH_STATE_PANNING; viewImage.mPrevImageView.setPressed(false); viewImage.mNextImageView.setPressed(false); int deltaX; int deltaY; if (mLastXTouchPos == -1) { deltaX = 0; deltaY = 0; } else { deltaX = x - mLastXTouchPos; deltaY = y - mLastYTouchPos; } mLastXTouchPos = x; mLastYTouchPos = y; if (mBitmapDisplayed == null) return true; if (deltaX != 0) { // Second. Pan to whatever degree is possible. if (getScale() > 1F) { postTranslate(deltaX, deltaY, sUseBounce); ImageViewTouch.this.center(true, true, false); } } setImageMatrix(getImageViewMatrix()); } break; case MotionEvent.ACTION_UP: int nextImagePos = -1; if (mTouchState == TOUCH_STATE_LEFT_PRESS && x < TOUCH_AREA_WIDTH) { nextImagePos = viewImage.mCurrentPosition - 1; } else if (mTouchState == TOUCH_STATE_RIGHT_PRESS && x > viewWidth - TOUCH_AREA_WIDTH) { nextImagePos = viewImage.mCurrentPosition + 1; } if (nextImagePos >= 0 && nextImagePos < viewImage.mAllImages.getCount()) { synchronized (viewImage) { viewImage.setMode(MODE_NORMAL); viewImage.setImage(nextImagePos); } } viewImage.scheduleDismissOnScreenControls(); viewImage.mPrevImageView.setPressed(false); viewImage.mNextImageView.setPressed(false); mTouchState = TOUCH_STATE_REST; break; case MotionEvent.ACTION_CANCEL: viewImage.mPrevImageView.setPressed(false); viewImage.mNextImageView.setPressed(false); mTouchState = TOUCH_STATE_REST; break; } return true; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // Don't respond to arrow keys if trackball scrolling is not enabled if (!mEnableTrackballScroll) { if ((keyCode >= KeyEvent.KEYCODE_DPAD_UP) && (keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT)) { return super.onKeyDown(keyCode, event); } } int current = mViewImage.mCurrentPosition; int nextImagePos = -2; // default no next image try { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: { if (mViewImage.isPickIntent()) { ImageManager.IImage img = mViewImage.mAllImages.getImageAt(mViewImage.mCurrentPosition); mViewImage.setResult(RESULT_OK, new Intent().setData(img.fullSizeImageUri())); mViewImage.finish(); } break; } case KeyEvent.KEYCODE_DPAD_LEFT: { panBy(sPanRate, 0); int maxOffset = (current == 0) ? 0 : sHysteresis; if (getScale() <= 1F || isShiftedToNextImage(true, maxOffset)) { nextImagePos = current - 1; } else { center(true, false, true); } return true; } case KeyEvent.KEYCODE_DPAD_RIGHT: { panBy(-sPanRate, 0); int maxOffset = (current == mViewImage.mAllImages.getCount()-1) ? 0 : sHysteresis; if (getScale() <= 1F || isShiftedToNextImage(false, maxOffset)) { nextImagePos = current + 1; } else { center(true, false, true); } return true; } case KeyEvent.KEYCODE_DPAD_UP: { panBy(0, sPanRate); center(true, false, false); return true; } case KeyEvent.KEYCODE_DPAD_DOWN: { panBy(0, -sPanRate); center(true, false, false); return true; } case KeyEvent.KEYCODE_DEL: MenuHelper.deletePhoto(mViewImage, mViewImage.mDeletePhotoRunnable); break; } } finally { if (nextImagePos >= 0 && nextImagePos < mViewImage.mAllImages.getCount()) { synchronized (mViewImage) { mViewImage.setMode(MODE_NORMAL); mViewImage.setImage(nextImagePos); } } else if (nextImagePos != -2) { center(true, true, false); } } return super.onKeyDown(keyCode, event); } protected boolean isShiftedToNextImage(boolean left, int maxOffset) { boolean retval; Bitmap bitmap = mBitmapDisplayed; Matrix m = getImageViewMatrix(); if (left) { float [] t1 = new float[] { 0, 0 }; m.mapPoints(t1); retval = t1[0] > maxOffset; } else { int width = bitmap != null ? bitmap.getWidth() : getWidth(); float [] t1 = new float[] { width, 0 }; m.mapPoints(t1); retval = t1[0] + maxOffset < getWidth(); } return retval; } protected void scrollX(int deltaX) { scrollHandler().scrollBy(deltaX, 0); } protected int getScrollOffset() { return scrollHandler().getScrollX(); } } static class ScrollHandler extends LinearLayout { private Runnable mFirstLayoutCompletedCallback = null; private Scroller mScrollerHelper; private int mWidth = -1; public ScrollHandler(Context context) { super(context); mScrollerHelper = new Scroller(context); } public ScrollHandler(Context context, AttributeSet attrs) { super(context, attrs); mScrollerHelper = new Scroller(context); } public void setLayoutCompletedCallback(Runnable r) { mFirstLayoutCompletedCallback = r; } public void startScrollTo(int newX, int newY) { int oldX = getScrollX(); int oldY = getScrollY(); int deltaX = newX - oldX; int deltaY = newY - oldY; if (mWidth == -1) { mWidth = findViewById(R.id.image2).getWidth(); } int viewWidth = mWidth; int duration = viewWidth > 0 ? sBaseScrollDuration * Math.abs(deltaX) / viewWidth : 0; mScrollerHelper.startScroll(oldX, oldY, deltaX, deltaY, duration); invalidate(); } @Override public void computeScroll() { if (mScrollerHelper.computeScrollOffset()) { scrollTo(mScrollerHelper.getCurrX(), mScrollerHelper.getCurrY()); postInvalidate(); // So we draw again } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int width = right - left; int x = 0; for (View v : new View[] { findViewById(R.id.image1), findViewById(R.id.image2), findViewById(R.id.image3) }) { v.layout(x, 0, x + width, bottom); x += (width + sPadding); } findViewById(R.id.padding1).layout(width, 0, width + sPadding, bottom); findViewById(R.id.padding2).layout(width+sPadding+width, 0, width+sPadding+width+sPadding, bottom); if (changed) { if (mFirstLayoutCompletedCallback != null) { mFirstLayoutCompletedCallback.run(); } } } } private void animateScrollTo(int xNew, int yNew) { mScroller.startScrollTo(xNew, yNew); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); if (! mCameraReviewMode) { MenuItem item = menu.add(Menu.CATEGORY_SECONDARY, 203, 0, R.string.slide_show); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { setMode(MODE_SLIDESHOW); mLastSlideShowImage = mCurrentPosition; loadNextImage(mCurrentPosition, 0, true); return true; } }); item.setIcon(android.R.drawable.ic_menu_slideshow); } final SelectedImageGetter selectedImageGetter = new SelectedImageGetter() { public ImageManager.IImage getCurrentImage() { return mAllImages.getImageAt(mCurrentPosition); } public Uri getCurrentImageUri() { return mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri(); } }; mImageMenuRunnable = MenuHelper.addImageMenuItems( menu, MenuHelper.INCLUDE_ALL, true, ViewImage.this, mHandler, mDeletePhotoRunnable, new MenuHelper.MenuInvoker() { public void run(MenuHelper.MenuCallback cb) { setMode(MODE_NORMAL); cb.run(selectedImageGetter.getCurrentImageUri(), selectedImageGetter.getCurrentImage()); for (ImageViewTouchBase iv: mImageViews) { iv.recycleBitmaps(); iv.setImageBitmap(null, true); } setImage(mCurrentPosition); } }); if (true) { MenuItem item = menu.add(Menu.CATEGORY_SECONDARY, 203, 1000, R.string.camerasettings); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { Intent preferences = new Intent(); preferences.setClass(ViewImage.this, GallerySettings.class); startActivity(preferences); return true; } }); item.setAlphabeticShortcut('p'); item.setIcon(android.R.drawable.ic_menu_preferences); } // Hidden menu just so the shortcut will bring up the zoom controls menu.add(Menu.CATEGORY_SECONDARY, 203, 0, R.string.camerasettings) // the string resource is a placeholder .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { showOnScreenControls(); return true; } }) .setAlphabeticShortcut('z') .setVisible(false); return true; } protected Runnable mDeletePhotoRunnable = new Runnable() { public void run() { mAllImages.removeImageAt(mCurrentPosition); if (mAllImages.getCount() == 0) { finish(); } else { if (mCurrentPosition == mAllImages.getCount()) { mCurrentPosition -= 1; } } for (ImageViewTouchBase iv: mImageViews) { iv.setImageBitmapResetBase(null, true, true); } setImage(mCurrentPosition); } }; @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); setMode(MODE_NORMAL); if (mImageMenuRunnable != null) { mImageMenuRunnable.gettingReadyToOpen(menu, mAllImages.getImageAt(mCurrentPosition)); } menu.findItem(MenuHelper.MENU_IMAGE_SHARE).setEnabled(isCurrentImageShareable()); return true; } private boolean isCurrentImageShareable() { IImage image = mAllImages.getImageAt(mCurrentPosition); if (image != null){ Uri uri = image.fullSizeImageUri(); String fullUri = uri.toString(); return fullUri.startsWith(MediaStore.Images.Media.INTERNAL_CONTENT_URI.toString()) || fullUri.startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString()); } return true; } @Override public void onConfigurationChanged(android.content.res.Configuration newConfig) { super.onConfigurationChanged(newConfig); if (newConfig.orientation != getResources().getConfiguration().orientation) { for (ImageViewTouchBase iv: mImageViews) { iv.setImageBitmapResetBase(null, false, true); } MenuHelper.requestOrientation(this, mPrefs); } } @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { boolean b = super.onMenuItemSelected(featureId, item); if (mImageMenuRunnable != null) mImageMenuRunnable.aboutToCall(item, mAllImages.getImageAt(mCurrentPosition)); return b; } /* * Here's the loading strategy. For any given image, load the thumbnail * into memory and post a callback to display the resulting bitmap. * * Then proceed to load the full image bitmap. Three things can * happen at this point: * * 1. the image fails to load because the UI thread decided * to move on to a different image. This "cancellation" happens * by virtue of the UI thread closing the stream containing the * image being decoded. BitmapFactory.decodeStream returns null * in this case. * * 2. the image loaded successfully. At that point we post * a callback to the UI thread to actually show the bitmap. * * 3. when the post runs it checks to see if the image that was * loaded is still the one we want. The UI may have moved on * to some other image and if so we just drop the newly loaded * bitmap on the floor. */ interface ImageGetterCallback { public void imageLoaded(int pos, int offset, Bitmap bitmap, boolean isThumb); public boolean wantsThumbnail(int pos, int offset); public boolean wantsFullImage(int pos, int offset); public int fullImageSizeToUse(int pos, int offset); public void completed(boolean wasCanceled); public int [] loadOrder(); } class ImageGetter { // The thread which does the work. private Thread mGetterThread; // The base position that's being retrieved. The actual images retrieved // are this base plus each of the offets. private int mCurrentPosition = -1; // The callback to invoke for each image. private ImageGetterCallback mCB; // This is the loader cancelable that gets set while we're loading an image. // If we change position we can cancel the current load using this. private ImageManager.IGetBitmap_cancelable mLoad; // True if we're canceling the current load. private boolean mCancelCurrent = false; // True when the therad should exit. private boolean mDone = false; // True when the loader thread is waiting for work. private boolean mReady = false; private void cancelCurrent() { synchronized (this) { if (!mReady) { mCancelCurrent = true; ImageManager.IGetBitmap_cancelable load = mLoad; if (load != null) { if (Config.LOGV) Log.v(TAG, "canceling load object"); load.cancel(); } mCancelCurrent = false; } } } public ImageGetter() { mGetterThread = new Thread(new Runnable() { private Runnable callback(final int position, final int offset, final boolean isThumb, final Bitmap bitmap) { return new Runnable() { public void run() { // check for inflight callbacks that aren't applicable any longer // before delivering them if (!isCanceled() && position == mCurrentPosition) { mCB.imageLoaded(position, offset, bitmap, isThumb); } else { if (bitmap != null) bitmap.recycle(); } } }; } private Runnable completedCallback(final boolean wasCanceled) { return new Runnable() { public void run() { mCB.completed(wasCanceled); } }; } public void run() { int lastPosition = -1; while (!mDone) { synchronized (ImageGetter.this) { mReady = true; ImageGetter.this.notify(); if (mCurrentPosition == -1 || lastPosition == mCurrentPosition) { try { ImageGetter.this.wait(); } catch (InterruptedException ex) { continue; } } lastPosition = mCurrentPosition; mReady = false; } if (lastPosition != -1) { int imageCount = mAllImages.getCount(); int [] order = mCB.loadOrder(); for (int i = 0; i < order.length; i++) { int offset = order[i]; int imageNumber = lastPosition + offset; if (imageNumber >= 0 && imageNumber < imageCount) { ImageManager.IImage image = mAllImages.getImageAt(lastPosition + offset); if (image == null || isCanceled()) { break; } if (mCB.wantsThumbnail(lastPosition, offset)) { if (Config.LOGV) Log.v(TAG, "starting THUMBNAIL load at offset " + offset); Bitmap b = image.thumbBitmap(); mHandler.post(callback(lastPosition, offset, true, b)); } } } for (int i = 0; i < order.length; i++) { int offset = order[i]; int imageNumber = lastPosition + offset; if (imageNumber >= 0 && imageNumber < imageCount) { ImageManager.IImage image = mAllImages.getImageAt(lastPosition + offset); if (mCB.wantsFullImage(lastPosition, offset)) { if (Config.LOGV) Log.v(TAG, "starting FULL IMAGE load at offset " + offset); int sizeToUse = mCB.fullImageSizeToUse(lastPosition, offset); if (image != null && !isCanceled()) { mLoad = image.fullSizeBitmap_cancelable(sizeToUse); } if (mLoad != null) { long t1; if (Config.LOGV) t1 = System.currentTimeMillis(); Bitmap b = null; try { b = mLoad.get(); } catch (OutOfMemoryError e) { Log.e(TAG, "couldn't load full size bitmap for " + ""); } if (Config.LOGV && b != null) { long t2 = System.currentTimeMillis(); Log.v(TAG, "loading full image for " + image.fullSizeImageUri() + " with requested size " + sizeToUse + " took " + (t2-t1) + " and returned a bitmap with size " + b.getWidth() + " / " + b.getHeight()); } mLoad = null; if (b != null) { if (isCanceled()) { b.recycle(); } else { mHandler.post(callback(lastPosition, offset, false, b)); } } } } } } mHandler.post(completedCallback(isCanceled())); } } } }); mGetterThread.setName("ImageGettter"); mGetterThread.start(); } private boolean isCanceled() { synchronized (this) { return mCancelCurrent; } } public void setPosition(int position, ImageGetterCallback cb) { synchronized (this) { if (!mReady) { try { mCancelCurrent = true; ImageManager.IGetBitmap_cancelable load = mLoad; if (load != null) { load.cancel(); } // if the thread is waiting before loading the full size // image then this will free it up ImageGetter.this.notify(); ImageGetter.this.wait(); mCancelCurrent = false; } catch (InterruptedException ex) { // not sure what to do here } } } mCurrentPosition = position; mCB = cb; synchronized (this) { ImageGetter.this.notify(); } } public void stop() { synchronized (this) { mDone = true; ImageGetter.this.notify(); } try { mGetterThread.join(); } catch (InterruptedException ex) { } } } private void setImage(int pos) { if (!mLayoutComplete) { return; } final boolean left = mCurrentPosition > pos; mCurrentPosition = pos; ImageViewTouchBase current = mImageViews[1]; current.mSuppMatrix.reset(); current.setImageMatrix(current.getImageViewMatrix()); if (false) { Log.v(TAG, "before..."); for (ImageViewTouchBase ivtb : mImageViews) ivtb.dump(); } if (!mFirst) { if (left) { mImageViews[2].copyFrom(mImageViews[1]); mImageViews[1].copyFrom(mImageViews[0]); } else { mImageViews[0].copyFrom(mImageViews[1]); mImageViews[1].copyFrom(mImageViews[2]); } } if (false) { Log.v(TAG, "after copy..."); for (ImageViewTouchBase ivtb : mImageViews) ivtb.dump(); } for (ImageViewTouchBase ivt: mImageViews) { ivt.mIsZooming = false; } int width = mImageViews[1].getWidth(); int from; int to = width + sPadding; if (mFirst) { from = to; mFirst = false; } else { from = left ? (width + sPadding) + mScroller.getScrollX() : mScroller.getScrollX() - (width + sPadding); } if (sAnimateTransitions) { mScroller.scrollTo(from, 0); animateScrollTo(to, 0); } else { mScroller.scrollTo(to, 0); } ImageGetterCallback cb = new ImageGetterCallback() { public void completed(boolean wasCanceled) { if (!mShowActionIcons) { mImageViews[1].setFocusableInTouchMode(true); mImageViews[1].requestFocus(); } } public boolean wantsThumbnail(int pos, int offset) { ImageViewTouchBase ivt = mImageViews[1 + offset]; return ivt.mThumbBitmap == null; } public boolean wantsFullImage(int pos, int offset) { ImageViewTouchBase ivt = mImageViews[1 + offset]; if (ivt.mBitmapDisplayed != null && !ivt.mBitmapIsThumbnail) { return false; } if (offset != 0) { return false; } return true; } public int fullImageSizeToUse(int pos, int offset) { // TODO // this number should be bigger so that we can zoom. we may need to // get fancier and read in the fuller size image as the user starts // to zoom. use -1 to get the full full size image. // for now use 480 so we don't run out of memory final int imageViewSize = 480; return imageViewSize; } public int [] loadOrder() { return sOrder_adjacents; } public void imageLoaded(int pos, int offset, Bitmap bitmap, boolean isThumb) { ImageViewTouchBase ivt = mImageViews[1 + offset]; ivt.setImageBitmapResetBase(bitmap, isThumb, isThumb); } }; // Could be null if we're stopping a slide show in the course of pausing if (mGetter != null) { mGetter.setPosition(pos, cb); } showOnScreenControls(); } @Override public void onCreate(Bundle instanceState) { super.onCreate(instanceState); Intent intent = getIntent(); mCameraReviewMode = intent.getBooleanExtra("com.android.camera.ReviewMode", false); mFullScreenInNormalMode = intent.getBooleanExtra(MediaStore.EXTRA_FULL_SCREEN, true); mShowActionIcons = intent.getBooleanExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, false); mPrefs = PreferenceManager.getDefaultSharedPreferences(this); setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.viewimage); mImageViews[0] = (ImageViewTouch) findViewById(R.id.image1); mImageViews[1] = (ImageViewTouch) findViewById(R.id.image2); mImageViews[2] = (ImageViewTouch) findViewById(R.id.image3); for(ImageViewTouch v : mImageViews) { v.setEnableTrackballScroll(!mShowActionIcons); } mScroller = (ScrollHandler)findViewById(R.id.scroller); makeGetter(); mAnimationIndex = -1; mSlideShowInAnimation = new Animation[] { makeInAnimation(R.anim.transition_in), makeInAnimation(R.anim.slide_in), makeInAnimation(R.anim.slide_in_vertical), }; mSlideShowOutAnimation = new Animation[] { makeOutAnimation(R.anim.transition_out), makeOutAnimation(R.anim.slide_out), makeOutAnimation(R.anim.slide_out_vertical), }; mSlideShowImageViews[0] = (ImageViewTouch) findViewById(R.id.image1_slideShow); mSlideShowImageViews[1] = (ImageViewTouch) findViewById(R.id.image2_slideShow); for (ImageViewTouch v : mSlideShowImageViews) { v.setImageBitmapResetBase(null, true, true); v.setVisibility(View.INVISIBLE); v.setEnableTrackballScroll(!mShowActionIcons); } mActionIconPanel = findViewById(R.id.action_icon_panel); { int[] pickIds = {R.id.attach, R.id.cancel}; int[] normalIds = {R.id.gallery, R.id.setas, R.id.share, R.id.discard}; int[] hideIds = pickIds; int[] connectIds = normalIds; if (isPickIntent()) { hideIds = normalIds; connectIds = pickIds; } for(int id : hideIds) { mActionIconPanel.findViewById(id).setVisibility(View.GONE); } for(int id : connectIds) { View view = mActionIconPanel.findViewById(id); view.setOnClickListener(this); Animation animation = new AlphaAnimation(0F, 1F); animation.setDuration(500); view.setAnimation(animation); } } mShutterButton = findViewById(R.id.shutter_button); mShutterButton.setOnClickListener(this); Uri uri = getIntent().getData(); if (Config.LOGV) Log.v(TAG, "uri is " + uri); if (instanceState != null) { if (instanceState.containsKey("uri")) { uri = Uri.parse(instanceState.getString("uri")); } } if (uri == null) { finish(); return; } init(uri); Bundle b = getIntent().getExtras(); boolean slideShow = b != null ? b.getBoolean("slideshow", false) : false; if (slideShow) { setMode(MODE_SLIDESHOW); loadNextImage(mCurrentPosition, 0, true); } else { if (mFullScreenInNormalMode) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } if (mShowActionIcons) { mActionIconPanel.setVisibility(View.VISIBLE); mShutterButton.setVisibility(View.VISIBLE); } } // Get the zoom controls and add them to the bottom of the map View zoomControls = getZoomControls(); RelativeLayout root = (RelativeLayout) findViewById(R.id.rootLayout); RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); p.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); p.addRule(RelativeLayout.CENTER_HORIZONTAL); root.addView(zoomControls, p); mNextImageView = findViewById(R.id.next_image); mPrevImageView = findViewById(R.id.prev_image); if (mShowActionIcons) { mNextImageView.setOnClickListener(this); mNextImageView.setFocusable(true); mPrevImageView.setOnClickListener(this); mPrevImageView.setFocusable(true); } setOrientation(); } private void setOrientation() { Intent intent = getIntent(); if (intent.hasExtra(MediaStore.EXTRA_SCREEN_ORIENTATION)) { int orientation = intent.getIntExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); if (orientation != getRequestedOrientation()) { setRequestedOrientation(orientation); } } else { MenuHelper.requestOrientation(this, mPrefs); } } private Animation makeInAnimation(int id) { Animation inAnimation = AnimationUtils.loadAnimation(this, id); return inAnimation; } private Animation makeOutAnimation(int id) { Animation outAnimation = AnimationUtils.loadAnimation(this, id); return outAnimation; } private void setMode(int mode) { if (mMode == mode) { return; } findViewById(R.id.slideShowContainer).setVisibility(mode == MODE_SLIDESHOW ? View.VISIBLE : View.GONE); findViewById(R.id.abs) .setVisibility(mode == MODE_NORMAL ? View.VISIBLE : View.GONE); Window win = getWindow(); mMode = mode; if (mode == MODE_SLIDESHOW) { win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); if (sSlideShowHidesStatusBar) { win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } for (ImageViewTouchBase ivt: mImageViews) { ivt.clear(); } mActionIconPanel.setVisibility(View.GONE); mShutterButton.setVisibility(View.GONE); if (false) { Log.v(TAG, "current is " + this.mSlideShowImageCurrent); this.mSlideShowImageViews[0].dump(); this.mSlideShowImageViews[0].dump(); } findViewById(R.id.slideShowContainer).getRootView().requestLayout(); mUseShuffleOrder = mPrefs.getBoolean("pref_gallery_slideshow_shuffle_key", false); mSlideShowLoop = mPrefs.getBoolean("pref_gallery_slideshow_repeat_key", false); try { mAnimationIndex = Integer.parseInt(mPrefs.getString("pref_gallery_slideshow_transition_key", "0")); } catch (Exception ex) { Log.e(TAG, "couldn't parse preference: " + ex.toString()); mAnimationIndex = 0; } try { mSlideShowInterval = Integer.parseInt(mPrefs.getString("pref_gallery_slideshow_interval_key", "3")) * 1000; } catch (Exception ex) { Log.e(TAG, "couldn't parse preference: " + ex.toString()); mSlideShowInterval = 3000; } if (Config.LOGV) { Log.v(TAG, "read prefs... shuffle: " + mUseShuffleOrder); Log.v(TAG, "read prefs... loop: " + mSlideShowLoop); Log.v(TAG, "read prefs... animidx: " + mAnimationIndex); Log.v(TAG, "read prefs... interval: " + mSlideShowInterval); } if (mUseShuffleOrder) { generateShuffleOrder(); } } else { if (Config.LOGV) Log.v(TAG, "slide show mode off, mCurrentPosition == " + mCurrentPosition); win.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); if (mFullScreenInNormalMode) { win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } else { win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } if (mGetter != null) mGetter.cancelCurrent(); if (sSlideShowHidesStatusBar) { win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } if (mShowActionIcons) { mActionIconPanel.setVisibility(View.VISIBLE); mShutterButton.setVisibility(View.VISIBLE); } ImageViewTouchBase dst = mImageViews[1]; dst.mLastXTouchPos = -1; dst.mLastYTouchPos = -1; for (ImageViewTouchBase ivt: mSlideShowImageViews) { ivt.clear(); } mShuffleOrder = null; // mGetter null is a proxy for being paused if (mGetter != null) { mFirst = true; // don't animate setImage(mCurrentPosition); } } // this line shouldn't be necessary but the view hierarchy doesn't // seem to realize that the window layout changed mScroller.requestLayout(); } private void generateShuffleOrder() { if (mShuffleOrder == null || mShuffleOrder.length != mAllImages.getCount()) { mShuffleOrder = new int[mAllImages.getCount()]; } for (int i = 0; i < mShuffleOrder.length; i++) { mShuffleOrder[i] = i; } for (int i = mShuffleOrder.length - 1; i > 0; i--) { int r = mRandom.nextInt(i); int tmp = mShuffleOrder[r]; mShuffleOrder[r] = mShuffleOrder[i]; mShuffleOrder[i] = tmp; } } private void loadNextImage(final int requestedPos, final long delay, final boolean firstCall) { if (firstCall && mUseShuffleOrder) { generateShuffleOrder(); } final long targetDisplayTime = System.currentTimeMillis() + delay; ImageGetterCallback cb = new ImageGetterCallback() { public void completed(boolean wasCanceled) { } public boolean wantsThumbnail(int pos, int offset) { return true; } public boolean wantsFullImage(int pos, int offset) { return false; } public int [] loadOrder() { return sOrder_slideshow; } public int fullImageSizeToUse(int pos, int offset) { return 480; // TODO compute this } public void imageLoaded(final int pos, final int offset, final Bitmap bitmap, final boolean isThumb) { long timeRemaining = Math.max(0, targetDisplayTime - System.currentTimeMillis()); mHandler.postDelayed(new Runnable() { public void run() { if (mMode == MODE_NORMAL) { return; } ImageViewTouchBase oldView = mSlideShowImageViews[mSlideShowImageCurrent]; if (++mSlideShowImageCurrent == mSlideShowImageViews.length) { mSlideShowImageCurrent = 0; } ImageViewTouchBase newView = mSlideShowImageViews[mSlideShowImageCurrent]; newView.setVisibility(View.VISIBLE); newView.setImageBitmapResetBase(bitmap, isThumb, isThumb); newView.bringToFront(); int animation = 0; if (mAnimationIndex == -1) { int n = mRandom.nextInt(mSlideShowInAnimation.length); animation = n; } else { animation = mAnimationIndex; } Animation aIn = mSlideShowInAnimation[animation]; newView.setAnimation(aIn); newView.setVisibility(View.VISIBLE); aIn.startNow(); Animation aOut = mSlideShowOutAnimation[animation]; oldView.setVisibility(View.INVISIBLE); oldView.setAnimation(aOut); aOut.startNow(); mCurrentPosition = requestedPos; mHandler.post(new Runnable() { public void run() { if (mCurrentPosition == mLastSlideShowImage && !firstCall) { if (mSlideShowLoop) { if (mUseShuffleOrder) { generateShuffleOrder(); } } else { setMode(MODE_NORMAL); return; } } if (Config.LOGV) Log.v(TAG, "mCurrentPosition is now " + mCurrentPosition); loadNextImage((mCurrentPosition + 1) % mAllImages.getCount(), mSlideShowInterval, false); } }); } }, timeRemaining); } }; // Could be null if we're stopping a slide show in the course of pausing if (mGetter != null) { int pos = requestedPos; if (mShuffleOrder != null) { pos = mShuffleOrder[pos]; } mGetter.setPosition(pos, cb); } } private void makeGetter() { mGetter = new ImageGetter(); } private boolean desiredSortOrder() { String sortOrder = mPrefs.getString("pref_gallery_sort_key", null); boolean sortAscending = false; if (sortOrder != null) { sortAscending = sortOrder.equals("ascending"); } if (mCameraReviewMode) { // Force left-arrow older pictures, right-arrow newer pictures. sortAscending = true; } return sortAscending; } private void init(Uri uri) { mSortAscending = desiredSortOrder(); int sort = mSortAscending ? ImageManager.SORT_ASCENDING : ImageManager.SORT_DESCENDING; mAllImages = ImageManager.makeImageList(uri, this, sort); uri = uri.buildUpon().query(null).build(); // TODO smarter/faster here please for (int i = 0; i < mAllImages.getCount(); i++) { ImageManager.IImage image = mAllImages.getImageAt(i); if (image.fullSizeImageUri().equals(uri)) { mCurrentPosition = i; mLastSlideShowImage = mCurrentPosition; break; } } } @Override public void onSaveInstanceState(Bundle b) { super.onSaveInstanceState(b); ImageManager.IImage image = mAllImages.getImageAt(mCurrentPosition); if (image != null){ Uri uri = image.fullSizeImageUri(); String bucket = null; if(getIntent()!= null && getIntent().getData()!=null) bucket = getIntent().getData().getQueryParameter("bucketId"); if(bucket!=null) uri = uri.buildUpon().appendQueryParameter("bucketId", bucket).build(); b.putString("uri", uri.toString()); } if (mMode == MODE_SLIDESHOW) b.putBoolean("slideshow", true); } @Override public void onResume() { super.onResume(); // normally this will never be zero but if one "backs" into this // activity after removing the sdcard it could be zero. in that // case just "finish" since there's nothing useful that can happen. if (mAllImages.getCount() == 0) { finish(); } ImageManager.IImage image = mAllImages.getImageAt(mCurrentPosition); if (desiredSortOrder() != mSortAscending) { init(image.fullSizeImageUri()); } if (mGetter == null) { makeGetter(); } for (ImageViewTouchBase iv: mImageViews) { iv.setImageBitmap(null, true); } mFirst = true; mScroller.setLayoutCompletedCallback(new Runnable() { public void run() { mLayoutComplete = true; setImage(mCurrentPosition); } }); setImage(mCurrentPosition); setOrientation(); } @Override public void onPause() { super.onPause(); mGetter.cancelCurrent(); mGetter.stop(); mGetter = null; setMode(MODE_NORMAL); mAllImages.deactivate(); for (ImageViewTouchBase iv: mImageViews) { iv.recycleBitmaps(); iv.setImageBitmap(null, true); } for (ImageViewTouchBase iv: mSlideShowImageViews) { iv.recycleBitmaps(); iv.setImageBitmap(null, true); } } @Override public void onStop() { super.onStop(); } public void onClick(View v) { switch (v.getId()) { case R.id.shutter_button: { if (mCameraReviewMode) { finish(); } else { MenuHelper.gotoStillImageCapture(this); } } break; case R.id.gallery: { MenuHelper.gotoCameraImageGallery(this); } break; case R.id.discard: { if (mCameraReviewMode) { mDeletePhotoRunnable.run(); } else { MenuHelper.deletePhoto(this, mDeletePhotoRunnable); } } break; case R.id.share: { Uri u = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri(); Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); intent.setType("image/jpeg"); intent.putExtra(Intent.EXTRA_STREAM, u); try { startActivity(Intent.createChooser(intent, getText(R.string.sendImage))); } catch (android.content.ActivityNotFoundException ex) { Toast.makeText(this, R.string.no_way_to_share_image, Toast.LENGTH_SHORT).show(); } } break; case R.id.setas: { Uri u = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri(); Intent intent = new Intent(Intent.ACTION_ATTACH_DATA, u); try { startActivity(Intent.createChooser(intent, getText(R.string.setImage))); } catch (android.content.ActivityNotFoundException ex) { Toast.makeText(this, R.string.no_way_to_share_video, Toast.LENGTH_SHORT).show(); } } break; case R.id.next_image: { moveNextOrPrevious(1); } break; case R.id.prev_image: { moveNextOrPrevious(-1); } break; } } private void moveNextOrPrevious(int delta) { int nextImagePos = mCurrentPosition + delta; if ((0 <= nextImagePos) && (nextImagePos < mAllImages.getCount())) { setImage(nextImagePos); } } }