diff options
Diffstat (limited to 'photoviewer/src/com/android/ex/photo')
14 files changed, 0 insertions, 3834 deletions
diff --git a/photoviewer/src/com/android/ex/photo/Intents.java b/photoviewer/src/com/android/ex/photo/Intents.java deleted file mode 100644 index 0e64730..0000000 --- a/photoviewer/src/com/android/ex/photo/Intents.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo; - -import android.content.ContentProvider; -import android.content.Context; -import android.content.Intent; - -import com.android.ex.photo.fragments.PhotoViewFragment; - -/** - * Build intents to start app activities - */ -public class Intents { - // Intent extras - public static final String EXTRA_PHOTO_INDEX = "photo_index"; - public static final String EXTRA_PHOTO_ID = "photo_id"; - public static final String EXTRA_PHOTOS_URI = "photos_uri"; - public static final String EXTRA_RESOLVED_PHOTO_URI = "resolved_photo_uri"; - public static final String EXTRA_PROJECTION = "projection"; - public static final String EXTRA_THUMBNAIL_URI = "thumbnail_uri"; - - /** - * Gets a photo view intent builder to display the photos from phone activity. - * - * @param context The context - * @return The intent builder - */ - public static PhotoViewIntentBuilder newPhotoViewActivityIntentBuilder(Context context) { - return new PhotoViewIntentBuilder(context, PhotoViewActivity.class); - } - - /** - * Gets a photo view intent builder to display the photo view fragment - * - * @param context The context - * @return The intent builder - */ - public static PhotoViewIntentBuilder newPhotoViewFragmentIntentBuilder(Context context) { - return new PhotoViewIntentBuilder(context, PhotoViewFragment.class); - } - - /** Gets a new photo view intent builder */ - public static PhotoViewIntentBuilder newPhotoViewIntentBuilder( - Context context, Class<? extends PhotoViewActivity> cls) { - return new PhotoViewIntentBuilder(context, cls); - } - - /** Builder to create a photo view intent */ - public static class PhotoViewIntentBuilder { - private final Intent mIntent; - - /** The index of the photo to show */ - private Integer mPhotoIndex; - /** The URI of the group of photos to display */ - private String mPhotosUri; - /** The URL of the photo to display */ - private String mResolvedPhotoUri; - /** The projection for the query to use; optional */ - private String[] mProjection; - /** The URI of a thumbnail of the photo to display */ - private String mThumbnailUri; - - private PhotoViewIntentBuilder(Context context, Class<?> cls) { - mIntent = new Intent(context, cls); - } - - /** Sets the photo index */ - public PhotoViewIntentBuilder setPhotoIndex(Integer photoIndex) { - mPhotoIndex = photoIndex; - return this; - } - - /** Sets the photos URI */ - public PhotoViewIntentBuilder setPhotosUri(String photosUri) { - mPhotosUri = photosUri; - return this; - } - - /** Sets the query projection */ - public PhotoViewIntentBuilder setProjection(String[] projection) { - mProjection = projection; - return this; - } - - /** Sets the resolved photo URI. This method is for the case - * where the URI given to {@link PhotoViewActivity} points directly - * to a single image and does not need to be resolved via a query - * to the {@link ContentProvider}. If this value is set, it supersedes - * {@link #setPhotosUri(String)}. */ - public PhotoViewIntentBuilder setResolvedPhotoUri(String resolvedPhotoUri) { - mResolvedPhotoUri = resolvedPhotoUri; - return this; - } - - /** - * Sets the URI for a thumbnail preview of the photo. - */ - public PhotoViewIntentBuilder setThumbnailUri(String thumbnailUri) { - mThumbnailUri = thumbnailUri; - return this; - } - - /** Build the intent */ - public Intent build() { - mIntent.setAction(Intent.ACTION_VIEW); - - mIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - - if (mPhotoIndex != null) { - mIntent.putExtra(EXTRA_PHOTO_INDEX, (int) mPhotoIndex); - } - - if (mPhotosUri != null) { - mIntent.putExtra(EXTRA_PHOTOS_URI, mPhotosUri); - } - - if (mResolvedPhotoUri != null) { - mIntent.putExtra(EXTRA_RESOLVED_PHOTO_URI, mResolvedPhotoUri); - } - - if (mProjection != null) { - mIntent.putExtra(EXTRA_PROJECTION, mProjection); - } - - if (mThumbnailUri != null) { - mIntent.putExtra(EXTRA_THUMBNAIL_URI, mThumbnailUri); - } - - return mIntent; - } - } -} diff --git a/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java b/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java deleted file mode 100644 index 6f126b4..0000000 --- a/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo; - -import android.app.ActionBar; -import android.app.ActionBar.OnMenuVisibilityListener; -import android.app.Activity; -import android.app.ActivityManager; -import android.app.Fragment; -import android.app.LoaderManager.LoaderCallbacks; -import android.content.Intent; -import android.content.Loader; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.view.ViewPager.OnPageChangeListener; -import android.view.View; - -import com.android.ex.photo.PhotoViewPager.InterceptType; -import com.android.ex.photo.PhotoViewPager.OnInterceptTouchListener; -import com.android.ex.photo.adapters.PhotoPagerAdapter; -import com.android.ex.photo.fragments.PhotoViewFragment; -import com.android.ex.photo.loaders.PhotoPagerLoader; -import com.android.ex.photo.provider.PhotoContract; - -import java.util.HashSet; -import java.util.Set; - -/** - * Activity to view the contents of an album. - */ -public class PhotoViewActivity extends Activity implements - LoaderCallbacks<Cursor>, OnPageChangeListener, OnInterceptTouchListener, - OnMenuVisibilityListener { - - /** - * Listener to be invoked for screen events. - */ - public static interface OnScreenListener { - - /** - * The full screen state has changed. - */ - public void onFullScreenChanged(boolean fullScreen); - - /** - * A new view has been activated and the previous view de-activated. - */ - public void onViewActivated(); - - /** - * Called when a right-to-left touch move intercept is about to occur. - * - * @param origX the raw x coordinate of the initial touch - * @param origY the raw y coordinate of the initial touch - * @return {@code true} if the touch should be intercepted. - */ - public boolean onInterceptMoveLeft(float origX, float origY); - - /** - * Called when a left-to-right touch move intercept is about to occur. - * - * @param origX the raw x coordinate of the initial touch - * @param origY the raw y coordinate of the initial touch - * @return {@code true} if the touch should be intercepted. - */ - public boolean onInterceptMoveRight(float origX, float origY); - } - - public static interface CursorChangedListener { - /** - * Called when the cursor that contains the photo list data - * is updated. Note that there is no guarantee that the cursor - * will be at the proper position. - * @param cursor the cursor containing the photo list data - */ - public void onCursorChanged(Cursor cursor); - } - - private final static String STATE_ITEM_KEY = - "com.google.android.apps.plus.PhotoViewFragment.ITEM"; - private final static String STATE_FULLSCREEN_KEY = - "com.google.android.apps.plus.PhotoViewFragment.FULLSCREEN"; - - private static final int LOADER_PHOTO_LIST = 1; - - /** Count used when the real photo count is unknown [but, may be determined] */ - public static final int ALBUM_COUNT_UNKNOWN = -1; - - /** Argument key for the dialog message */ - public static final String KEY_MESSAGE = "dialog_message"; - - public static int sMemoryClass; - - /** The URI of the photos we're viewing; may be {@code null} */ - private String mPhotosUri; - /** The index of the currently viewed photo */ - private int mPhotoIndex; - /** The query projection to use; may be {@code null} */ - private String[] mProjection; - /** The total number of photos; only valid if {@link #mIsEmpty} is {@code false}. */ - private int mAlbumCount = ALBUM_COUNT_UNKNOWN; - /** {@code true} if the view is empty. Otherwise, {@code false}. */ - private boolean mIsEmpty; - /** The main pager; provides left/right swipe between photos */ - private PhotoViewPager mViewPager; - /** Adapter to create pager views */ - private PhotoPagerAdapter mAdapter; - /** Whether or not we're in "full screen" mode */ - private boolean mFullScreen; - /** The set of listeners wanting full screen state */ - private Set<OnScreenListener> mScreenListeners = new HashSet<OnScreenListener>(); - /** The set of listeners wanting full screen state */ - private Set<CursorChangedListener> mCursorListeners = new HashSet<CursorChangedListener>(); - /** When {@code true}, restart the loader when the activity becomes active */ - private boolean mRestartLoader; - /** Whether or not this activity is paused */ - private boolean mIsPaused = true; - private final Handler mHandler = new Handler(); - // TODO Find a better way to do this. We basically want the activity to display the - // "loading..." progress until the fragment takes over and shows it's own "loading..." - // progress [located in photo_header_view.xml]. We could potentially have all status displayed - // by the activity, but, that gets tricky when it comes to screen rotation. For now, we - // track the loading by this variable which is fragile and may cause phantom "loading..." - // text. - private long mActionBarHideDelayTime; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - final ActivityManager mgr = (ActivityManager) getApplicationContext(). - getSystemService(Activity.ACTIVITY_SERVICE); - sMemoryClass = mgr.getMemoryClass(); - - Intent mIntent = getIntent(); - - int currentItem = -1; - if (savedInstanceState != null) { - currentItem = savedInstanceState.getInt(STATE_ITEM_KEY, -1); - mFullScreen = savedInstanceState.getBoolean(STATE_FULLSCREEN_KEY, false); - } - - // uri of the photos to view; optional - if (mIntent.hasExtra(Intents.EXTRA_PHOTOS_URI)) { - mPhotosUri = mIntent.getStringExtra(Intents.EXTRA_PHOTOS_URI); - } - - // projection for the query; optional - // I.f not set, the default projection is used. - // This projection must include the columns from the default projection. - if (mIntent.hasExtra(Intents.EXTRA_PROJECTION)) { - mProjection = mIntent.getStringArrayExtra(Intents.EXTRA_PROJECTION); - } else { - mProjection = null; - } - - // Set the current item from the intent if wasn't in the saved instance - if (mIntent.hasExtra(Intents.EXTRA_PHOTO_INDEX) && currentItem < 0) { - currentItem = mIntent.getIntExtra(Intents.EXTRA_PHOTO_INDEX, -1); - } - mPhotoIndex = currentItem; - - setContentView(R.layout.photo_activity_view); - - // Create the adapter and add the view pager - mAdapter = new PhotoPagerAdapter(this, getFragmentManager(), null); - - final Resources resources = getResources(); - mViewPager = (PhotoViewPager) findViewById(R.id.photo_view_pager); - mViewPager.setAdapter(mAdapter); - mViewPager.setOnPageChangeListener(this); - mViewPager.setOnInterceptTouchListener(this); - mViewPager.setPageMargin(resources.getDimensionPixelSize(R.dimen.photo_page_margin)); - - // Kick off the loader - getLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this); - - final ActionBar actionBar = getActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - mActionBarHideDelayTime = resources.getInteger(R.integer.action_bar_delay_time_in_millis); - actionBar.addOnMenuVisibilityListener(this); - actionBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); - } - - @Override - protected void onResume() { - super.onResume(); - setFullScreen(mFullScreen, false); - - mIsPaused = false; - if (mRestartLoader) { - mRestartLoader = false; - getLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this); - } - } - - @Override - protected void onPause() { - mIsPaused = true; - - super.onPause(); - } - - @Override - public void onBackPressed() { - // If in full screen mode, toggle mode & eat the 'back' - if (mFullScreen) { - toggleFullScreen(); - } else { - super.onBackPressed(); - } - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - outState.putInt(STATE_ITEM_KEY, mViewPager.getCurrentItem()); - outState.putBoolean(STATE_FULLSCREEN_KEY, mFullScreen); - } - - public void addScreenListener(OnScreenListener listener) { - mScreenListeners.add(listener); - } - - public void removeScreenListener(OnScreenListener listener) { - mScreenListeners.remove(listener); - } - - public synchronized void addCursorListener(CursorChangedListener listener) { - mCursorListeners.add(listener); - } - - public synchronized void removeCursorListener(CursorChangedListener listener) { - mCursorListeners.remove(listener); - } - - public boolean isFragmentFullScreen(Fragment fragment) { - if (mViewPager == null || mAdapter == null || mAdapter.getCount() == 0) { - return mFullScreen; - } - return mFullScreen || (mViewPager.getCurrentItem() != mAdapter.getItemPosition(fragment)); - } - - public void toggleFullScreen() { - setFullScreen(!mFullScreen, true); - } - - public void onPhotoRemoved(long photoId) { - final Cursor data = mAdapter.getCursor(); - if (data == null) { - // Huh?! How would this happen? - return; - } - - final int dataCount = data.getCount(); - if (dataCount <= 1) { - finish(); - return; - } - - getLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this); - } - - @Override - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - if (id == LOADER_PHOTO_LIST) { - return new PhotoPagerLoader(this, Uri.parse(mPhotosUri), mProjection); - } - return null; - } - - @Override - public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) { - final int id = loader.getId(); - if (id == LOADER_PHOTO_LIST) { - if (data == null || data.getCount() == 0) { - mIsEmpty = true; - } else { - mAlbumCount = data.getCount(); - - // We're paused; don't do anything now, we'll get re-invoked - // when the activity becomes active again - // TODO(pwestbro): This shouldn't be necessary, as the loader manager should - // restart the loader - if (mIsPaused) { - mRestartLoader = true; - return; - } - mIsEmpty = false; - - // set the selected photo - int itemIndex = mPhotoIndex; - - // Use an index of 0 if the index wasn't specified or couldn't be found - if (itemIndex < 0) { - itemIndex = 0; - } - - mAdapter.swapCursor(data); - notifyCursorListeners(data); - - mViewPager.setCurrentItem(itemIndex, false); - setViewActivated(); - } - // Update the any action items - updateActionItems(); - } - } - - protected void updateActionItems() { - // Do nothing, but allow extending classes to do work - } - - private synchronized void notifyCursorListeners(Cursor data) { - // tell all of the objects listening for cursor changes - // that the cursor has changed - for (CursorChangedListener listener : mCursorListeners) { - listener.onCursorChanged(data); - } - } - - @Override - public void onLoaderReset(Loader<Cursor> loader) { - // If the loader is reset, remove the reference in the adapter to this cursor - // TODO(pwestbro): reenable this when b/7075236 is fixed - // mAdapter.swapCursor(null); - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - } - - @Override - public void onPageSelected(int position) { - mPhotoIndex = position; - setViewActivated(); - } - - @Override - public void onPageScrollStateChanged(int state) { - } - - public boolean isFragmentActive(Fragment fragment) { - if (mViewPager == null || mAdapter == null) { - return false; - } - return mViewPager.getCurrentItem() == mAdapter.getItemPosition(fragment); - } - - public void onFragmentVisible(PhotoViewFragment fragment) { - updateActionBar(fragment); - } - - @Override - public InterceptType onTouchIntercept(float origX, float origY) { - boolean interceptLeft = false; - boolean interceptRight = false; - - for (OnScreenListener listener : mScreenListeners) { - if (!interceptLeft) { - interceptLeft = listener.onInterceptMoveLeft(origX, origY); - } - if (!interceptRight) { - interceptRight = listener.onInterceptMoveRight(origX, origY); - } - listener.onViewActivated(); - } - - if (interceptLeft) { - if (interceptRight) { - return InterceptType.BOTH; - } - return InterceptType.LEFT; - } else if (interceptRight) { - return InterceptType.RIGHT; - } - return InterceptType.NONE; - } - - /** - * Updates the title bar according to the value of {@link #mFullScreen}. - */ - private void setFullScreen(boolean fullScreen, boolean setDelayedRunnable) { - final boolean fullScreenChanged = (fullScreen != mFullScreen); - mFullScreen = fullScreen; - - if (mFullScreen) { - setLightsOutMode(true); - cancelActionBarHideRunnable(); - } else { - setLightsOutMode(false); - if (setDelayedRunnable) { - postActionBarHideRunnableWithDelay(); - } - } - - if (fullScreenChanged) { - for (OnScreenListener listener : mScreenListeners) { - listener.onFullScreenChanged(mFullScreen); - } - } - } - - private void postActionBarHideRunnableWithDelay() { - mHandler.postDelayed(mActionBarHideRunnable, - mActionBarHideDelayTime); - } - - private void cancelActionBarHideRunnable() { - mHandler.removeCallbacks(mActionBarHideRunnable); - } - - private void setLightsOutMode(boolean enabled) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - int flags = enabled - ? View.SYSTEM_UI_FLAG_LOW_PROFILE - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - : View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; - - // using mViewPager since we have it and we need a view - mViewPager.setSystemUiVisibility(flags); - } else { - final ActionBar actionBar = getActionBar(); - if (enabled) { - actionBar.hide(); - } else { - actionBar.show(); - } - int flags = enabled - ? View.SYSTEM_UI_FLAG_LOW_PROFILE - : View.SYSTEM_UI_FLAG_VISIBLE; - mViewPager.setSystemUiVisibility(flags); - } - } - - private Runnable mActionBarHideRunnable = new Runnable() { - @Override - public void run() { - PhotoViewActivity.this.setLightsOutMode(true); - } - }; - - public void setViewActivated() { - for (OnScreenListener listener : mScreenListeners) { - listener.onViewActivated(); - } - } - - /** - * Adjusts the activity title and subtitle to reflect the photo name and count. - */ - protected void updateActionBar(PhotoViewFragment fragment) { - final int position = mViewPager.getCurrentItem() + 1; - final String title; - final String subtitle; - final boolean hasAlbumCount = mAlbumCount >= 0; - - final Cursor cursor = getCursorAtProperPosition(); - - if (cursor != null) { - final int photoNameIndex = cursor.getColumnIndex(PhotoContract.PhotoViewColumns.NAME); - title = cursor.getString(photoNameIndex); - } else { - title = null; - } - - if (mIsEmpty || !hasAlbumCount || position <= 0) { - subtitle = null; - } else { - subtitle = getResources().getString(R.string.photo_view_count, position, mAlbumCount); - } - - final ActionBar actionBar = getActionBar(); - actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE); - actionBar.setTitle(title); - actionBar.setSubtitle(subtitle); - } - - /** - * Utility method that will return the cursor that contains the data - * at the current position so that it refers to the current image on screen. - * @return the cursor at the current position or - * null if no cursor exists or if the {@link PhotoViewPager} is null. - */ - public Cursor getCursorAtProperPosition() { - if (mViewPager == null) { - return null; - } - - final int position = mViewPager.getCurrentItem(); - final Cursor cursor = mAdapter.getCursor(); - - if (cursor == null) { - return null; - } - - cursor.moveToPosition(position); - - return cursor; - } - - public Cursor getCursor() { - return (mAdapter == null) ? null : mAdapter.getCursor(); - } - - @Override - public void onMenuVisibilityChanged(boolean isVisible) { - if (isVisible) { - cancelActionBarHideRunnable(); - } else { - postActionBarHideRunnableWithDelay(); - } - } - - public void onNewPhotoLoaded() { - } -} diff --git a/photoviewer/src/com/android/ex/photo/PhotoViewPager.java b/photoviewer/src/com/android/ex/photo/PhotoViewPager.java deleted file mode 100644 index 65d1d6d..0000000 --- a/photoviewer/src/com/android/ex/photo/PhotoViewPager.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo; - -import android.content.Context; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; - -/** - * View pager for photo view fragments. Define our own class so we can specify the - * view pager in XML. - */ -public class PhotoViewPager extends ViewPager { - /** - * A type of intercept that should be performed - */ - public static enum InterceptType { NONE, LEFT, RIGHT, BOTH } - - /** - * Provides an ability to intercept touch events. - * <p> - * {@link ViewPager} intercepts all touch events and we need to be able to override this - * behavior. Instead, we could perform a similar function by declaring a custom - * {@link ViewGroup} to contain the pager and intercept touch events at a higher level. - */ - public static interface OnInterceptTouchListener { - /** - * Called when a touch intercept is about to occur. - * - * @param origX the raw x coordinate of the initial touch - * @param origY the raw y coordinate of the initial touch - * @return Which type of touch, if any, should should be intercepted. - */ - public InterceptType onTouchIntercept(float origX, float origY); - } - - private static final int INVALID_POINTER = -1; - - private float mLastMotionX; - private int mActivePointerId; - /** The x coordinate where the touch originated */ - private float mActivatedX; - /** The y coordinate where the touch originated */ - private float mActivatedY; - private OnInterceptTouchListener mListener; - - public PhotoViewPager(Context context) { - super(context); - initialize(); - } - - public PhotoViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - private void initialize() { - // Set the page transformer to perform the transition animation - // for each page in the view. - setPageTransformer(true, new PageTransformer() { - @Override - public void transformPage(View page, float position) { - - // The >= 1 is needed so that the page - // (page A) that transitions behind the newly visible - // page (page B) that comes in from the left does not - // get the touch events because it is still on screen - // (page A is still technically on screen despite being - // invisible). This makes sure that when the transition - // has completely finished, we revert it to its default - // behavior and move it off of the screen. - if (position < 0 || position >= 1.f) { - page.setTranslationX(0); - page.setAlpha(1.f); - page.setScaleX(1); - page.setScaleY(1); - } else { - page.setTranslationX(-position * page.getWidth()); - page.setAlpha(Math.max(0,1.f - position)); - final float scale = Math.max(0, 1.f - position * 0.3f); - page.setScaleX(scale); - page.setScaleY(scale); - } - } - }); - } - - /** - * {@inheritDoc} - * <p> - * We intercept touch event intercepts so we can prevent switching views when the - * current view is internally scrollable. - */ - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - final InterceptType intercept = (mListener != null) - ? mListener.onTouchIntercept(mActivatedX, mActivatedY) - : InterceptType.NONE; - final boolean ignoreScrollLeft = - (intercept == InterceptType.BOTH || intercept == InterceptType.LEFT); - final boolean ignoreScrollRight = - (intercept == InterceptType.BOTH || intercept == InterceptType.RIGHT); - - // Only check ability to page if we can't scroll in one / both directions - final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; - - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - mActivePointerId = INVALID_POINTER; - } - - switch (action) { - case MotionEvent.ACTION_MOVE: { - if (ignoreScrollLeft || ignoreScrollRight) { - final int activePointerId = mActivePointerId; - if (activePointerId == INVALID_POINTER) { - // If we don't have a valid id, the touch down wasn't on content. - break; - } - - final int pointerIndex = - MotionEventCompat.findPointerIndex(ev, activePointerId); - final float x = MotionEventCompat.getX(ev, pointerIndex); - - if (ignoreScrollLeft && ignoreScrollRight) { - mLastMotionX = x; - return false; - } else if (ignoreScrollLeft && (x > mLastMotionX)) { - mLastMotionX = x; - return false; - } else if (ignoreScrollRight && (x < mLastMotionX)) { - mLastMotionX = x; - return false; - } - } - break; - } - - case MotionEvent.ACTION_DOWN: { - mLastMotionX = ev.getX(); - // Use the raw x/y as the children can be located anywhere and there isn't a - // single offset that would be meaningful - mActivatedX = ev.getRawX(); - mActivatedY = ev.getRawY(); - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: { - final int pointerIndex = MotionEventCompat.getActionIndex(ev); - final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); - if (pointerId == mActivePointerId) { - // Our active pointer going up; select a new active pointer - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); - mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); - } - break; - } - } - - return super.onInterceptTouchEvent(ev); - } - - /** - * sets the intercept touch listener. - */ - public void setOnInterceptTouchListener(OnInterceptTouchListener l) { - mListener = l; - } -} diff --git a/photoviewer/src/com/android/ex/photo/adapters/BaseCursorPagerAdapter.java b/photoviewer/src/com/android/ex/photo/adapters/BaseCursorPagerAdapter.java deleted file mode 100644 index 848d79a..0000000 --- a/photoviewer/src/com/android/ex/photo/adapters/BaseCursorPagerAdapter.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo.adapters; - -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.Context; -import android.database.Cursor; -import android.util.Log; -import android.util.SparseIntArray; -import android.view.View; - -import com.android.ex.photo.provider.PhotoContract; - -import java.util.HashMap; - -/** - * Page adapter for use with an BaseCursorLoader. Unlike other cursor adapters, this has no - * observers for automatic refresh. Instead, it depends upon external mechanisms to provide - * the update signal. - */ -public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter { - private static final String TAG = "BaseCursorPagerAdapter"; - - Context mContext; - private boolean mDataValid; - private Cursor mCursor; - private int mRowIDColumn; - /** Mapping of row ID to cursor position */ - private SparseIntArray mItemPosition; - /** Mapping of instantiated object to row ID */ - private HashMap<Object, Integer> mObjectRowMap = new HashMap<Object, Integer>(); - - /** - * Constructor that always enables auto-requery. - * - * @param c The cursor from which to get the data. - * @param context The context - */ - public BaseCursorPagerAdapter(Context context, FragmentManager fm, Cursor c) { - super(fm); - init(context, c); - } - - /** - * Makes a fragment for the data pointed to by the cursor - * - * @param context Interface to application's global information - * @param cursor The cursor from which to get the data. The cursor is already - * moved to the correct position. - * @return the newly created fragment. - */ - public abstract Fragment getItem(Context context, Cursor cursor, int position); - - @Override - public Fragment getItem(int position) { - if (mDataValid && moveCursorTo(position)) { - return getItem(mContext, mCursor, position); - } - return null; - } - - @Override - public int getCount() { - if (mDataValid && mCursor != null) { - return mCursor.getCount(); - } else { - return 0; - } - } - - @Override - public Object instantiateItem(View container, int position) { - if (!mDataValid) { - throw new IllegalStateException("this should only be called when the cursor is valid"); - } - - final Integer rowId; - if (moveCursorTo(position)) { - rowId = mCursor.getString(mRowIDColumn).hashCode(); - } else { - rowId = null; - } - - // Create the fragment and bind cursor data - final Object obj = super.instantiateItem(container, position); - if (obj != null) { - mObjectRowMap.put(obj, rowId); - } - return obj; - } - - @Override - public void destroyItem(View container, int position, Object object) { - mObjectRowMap.remove(object); - - super.destroyItem(container, position, object); - } - - @Override - public int getItemPosition(Object object) { - final Integer rowId = mObjectRowMap.get(object); - if (rowId == null || mItemPosition == null) { - return POSITION_NONE; - } - - final int position = mItemPosition.get(rowId, POSITION_NONE); - return position; - } - - /** - * @return true if data is valid - */ - public boolean isDataValid() { - return mDataValid; - } - - /** - * Returns the cursor. - */ - public Cursor getCursor() { - return mCursor; - } - - /** - * Returns the data item associated with the specified position in the data set. - */ - public Object getDataItem(int position) { - if (mDataValid && moveCursorTo(position)) { - return mCursor; - } else { - return null; - } - } - - /** - * Returns the row id associated with the specified position in the list. - */ - public long getItemId(int position) { - if (mDataValid && moveCursorTo(position)) { - return mCursor.getString(mRowIDColumn).hashCode(); - } else { - return 0; - } - } - - /** - * Swap in a new Cursor, returning the old Cursor. - * - * @param newCursor The new cursor to be used. - * @return Returns the previously set Cursor, or null if there was not one. - * If the given new Cursor is the same instance is the previously set - * Cursor, null is also returned. - */ - public Cursor swapCursor(Cursor newCursor) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "swapCursor old=" + (mCursor == null ? -1 : mCursor.getCount()) + - "; new=" + (newCursor == null ? -1 : newCursor.getCount())); - } - - if (newCursor == mCursor) { - return null; - } - Cursor oldCursor = mCursor; - mCursor = newCursor; - if (newCursor != null) { - mRowIDColumn = newCursor.getColumnIndex(PhotoContract.PhotoViewColumns.URI); - mDataValid = true; - } else { - mRowIDColumn = -1; - mDataValid = false; - } - - setItemPosition(); - notifyDataSetChanged(); // notify the observers about the new cursor - return oldCursor; - } - - /** - * Converts the cursor into a CharSequence. Subclasses should override this - * method to convert their results. The default implementation returns an - * empty String for null values or the default String representation of - * the value. - * - * @param cursor the cursor to convert to a CharSequence - * @return a CharSequence representing the value - */ - public CharSequence convertToString(Cursor cursor) { - return cursor == null ? "" : cursor.toString(); - } - - @Override - protected String makeFragmentName(int viewId, int index) { - if (moveCursorTo(index)) { - return "android:pager:" + viewId + ":" + mCursor.getString(mRowIDColumn).hashCode(); - } else { - return super.makeFragmentName(viewId, index); - } - } - - /** - * Moves the cursor to the given position - * - * @return {@code true} if the cursor's position was set. Otherwise, {@code false}. - */ - private boolean moveCursorTo(int position) { - if (mCursor != null && !mCursor.isClosed()) { - return mCursor.moveToPosition(position); - } - return false; - } - - /** - * Initialize the adapter. - */ - private void init(Context context, Cursor c) { - boolean cursorPresent = c != null; - mCursor = c; - mDataValid = cursorPresent; - mContext = context; - mRowIDColumn = cursorPresent - ? mCursor.getColumnIndex(PhotoContract.PhotoViewColumns.URI) : -1; - } - - /** - * Sets the {@link #mItemPosition} instance variable with the current mapping of - * row id to cursor position. - */ - private void setItemPosition() { - if (!mDataValid || mCursor == null || mCursor.isClosed()) { - mItemPosition = null; - return; - } - - SparseIntArray itemPosition = new SparseIntArray(mCursor.getCount()); - - mCursor.moveToPosition(-1); - while (mCursor.moveToNext()) { - final int rowId = mCursor.getString(mRowIDColumn).hashCode(); - final int position = mCursor.getPosition(); - - itemPosition.append(rowId, position); - } - mItemPosition = itemPosition; - } -} diff --git a/photoviewer/src/com/android/ex/photo/adapters/BaseFragmentPagerAdapter.java b/photoviewer/src/com/android/ex/photo/adapters/BaseFragmentPagerAdapter.java deleted file mode 100644 index 9c24575..0000000 --- a/photoviewer/src/com/android/ex/photo/adapters/BaseFragmentPagerAdapter.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo.adapters; - -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.os.Parcelable; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.PagerAdapter; -import android.util.Log; -import android.util.LruCache; -import android.view.View; - -/** - * NOTE: This is a direct copy of {@link FragmentPagerAdapter} with four very important - * modifications. - * <p> - * <ol> - * <li>The method {@link #makeFragmentName(int, int)} is declared "protected" - * in our class. We need to be able to re-define the fragment's name according to data - * only available to sub-classes.</li> - * <li>The method {@link #isViewFromObject(View, Object)} has been reimplemented to search - * the entire view hierarchy for the given view.</li> - * <li>In method {@link #destroyItem(View, int, Object)}, the fragment is detached and - * added to a cache. If the fragment is evicted from the cache, it will be deleted. - * An album may contain thousands of photos and we want to avoid having thousands of - * fragments.</li> - * </ol> - */ -public abstract class BaseFragmentPagerAdapter extends PagerAdapter { - /** The default size of {@link #mFragmentCache} */ - private static final int DEFAULT_CACHE_SIZE = 5; - private static final String TAG = "FragmentPagerAdapter"; - private static final boolean DEBUG = false; - - private final FragmentManager mFragmentManager; - private FragmentTransaction mCurTransaction = null; - private Fragment mCurrentPrimaryItem = null; - /** A cache to store detached fragments before they are removed */ - private LruCache<String, Fragment> mFragmentCache = new FragmentCache(DEFAULT_CACHE_SIZE); - - public BaseFragmentPagerAdapter(FragmentManager fm) { - mFragmentManager = fm; - } - - /** - * Return the Fragment associated with a specified position. - */ - public abstract Fragment getItem(int position); - - @Override - public void startUpdate(View container) { - } - - @Override - public Object instantiateItem(View container, int position) { - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - - // Do we already have this fragment? - String name = makeFragmentName(container.getId(), position); - - // Remove item from the cache - mFragmentCache.remove(name); - - Fragment fragment = mFragmentManager.findFragmentByTag(name); - if (fragment != null) { - if (DEBUG) Log.v(TAG, "Attaching item #" + position + ": f=" + fragment); - mCurTransaction.attach(fragment); - } else { - fragment = getItem(position); - if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); - mCurTransaction.add(container.getId(), fragment, - makeFragmentName(container.getId(), position)); - } - if (fragment != mCurrentPrimaryItem) { - fragment.setMenuVisibility(false); - } - - return fragment; - } - - @Override - public void destroyItem(View container, int position, Object object) { - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - if (DEBUG) Log.v(TAG, "Detaching item #" + position + ": f=" + object - + " v=" + ((Fragment)object).getView()); - - Fragment fragment = (Fragment) object; - String name = fragment.getTag(); - if (name == null) { - // We prefer to get the name directly from the fragment, but, if the fragment is - // detached before the add transaction is committed, this could be 'null'. In - // that case, generate a name so we can still cache the fragment. - name = makeFragmentName(container.getId(), position); - } - - mFragmentCache.put(name, fragment); - mCurTransaction.detach(fragment); - } - - @Override - public void setPrimaryItem(View container, int position, Object object) { - Fragment fragment = (Fragment) object; - if (fragment != mCurrentPrimaryItem) { - if (mCurrentPrimaryItem != null) { - mCurrentPrimaryItem.setMenuVisibility(false); - } - if (fragment != null) { - fragment.setMenuVisibility(true); - } - mCurrentPrimaryItem = fragment; - } - - } - - @Override - public void finishUpdate(View container) { - if (mCurTransaction != null) { - mCurTransaction.commitAllowingStateLoss(); - mCurTransaction = null; - mFragmentManager.executePendingTransactions(); - } - } - - @Override - public boolean isViewFromObject(View view, Object object) { - // Ascend the tree to determine if the view is a child of the fragment - View root = ((Fragment) object).getView(); - for (Object v = view; v instanceof View; v = ((View) v).getParent()) { - if (v == root) { - return true; - } - } - return false; - } - - @Override - public Parcelable saveState() { - return null; - } - - @Override - public void restoreState(Parcelable state, ClassLoader loader) { - } - - /** Creates a name for the fragment */ - protected String makeFragmentName(int viewId, int index) { - return "android:switcher:" + viewId + ":" + index; - } - - /** - * A cache of detached fragments. - */ - private class FragmentCache extends LruCache<String, Fragment> { - public FragmentCache(int size) { - super(size); - } - - @Override - protected void entryRemoved(boolean evicted, String key, - Fragment oldValue, Fragment newValue) { - // remove the fragment if it's evicted OR it's replaced by a new fragment - if (evicted || (newValue != null && oldValue != newValue)) { - mCurTransaction.remove(oldValue); - } - } - } -} diff --git a/photoviewer/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java b/photoviewer/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java deleted file mode 100644 index bf75ecb..0000000 --- a/photoviewer/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo.adapters; - -import android.app.Fragment; -import android.app.FragmentManager; -import android.content.Context; -import android.database.Cursor; - -import com.android.ex.photo.Intents; -import com.android.ex.photo.Intents.PhotoViewIntentBuilder; -import com.android.ex.photo.fragments.PhotoViewFragment; -import com.android.ex.photo.provider.PhotoContract; - -/** - * Pager adapter for the photo view - */ -public class PhotoPagerAdapter extends BaseCursorPagerAdapter { - private int mContentUriIndex; - private int mThumbnailUriIndex; - - public PhotoPagerAdapter(Context context, FragmentManager fm, Cursor c) { - super(context, fm, c); - } - - @Override - public Fragment getItem(Context context, Cursor cursor, int position) { - final String photoUri = cursor.getString(mContentUriIndex); - final String thumbnailUri = cursor.getString(mThumbnailUriIndex); - - // create new PhotoViewFragment - final PhotoViewIntentBuilder builder = - Intents.newPhotoViewFragmentIntentBuilder(mContext); - builder - .setResolvedPhotoUri(photoUri) - .setThumbnailUri(thumbnailUri); - - return new PhotoViewFragment(builder.build(), position, this); - } - - @Override - public Cursor swapCursor(Cursor newCursor) { - if (newCursor != null) { - mContentUriIndex = - newCursor.getColumnIndex(PhotoContract.PhotoViewColumns.CONTENT_URI); - mThumbnailUriIndex = - newCursor.getColumnIndex(PhotoContract.PhotoViewColumns.THUMBNAIL_URI); - } else { - mContentUriIndex = -1; - mThumbnailUriIndex = -1; - } - - return super.swapCursor(newCursor); - } - - public String getPhotoUri(Cursor cursor) { - return cursor.getString(mContentUriIndex); - } -} diff --git a/photoviewer/src/com/android/ex/photo/fragments/PhotoViewFragment.java b/photoviewer/src/com/android/ex/photo/fragments/PhotoViewFragment.java deleted file mode 100644 index efb8145..0000000 --- a/photoviewer/src/com/android/ex/photo/fragments/PhotoViewFragment.java +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo.fragments; - -import android.app.Activity; -import android.app.Fragment; -import android.app.LoaderManager; -import android.app.LoaderManager.LoaderCallbacks; -import android.content.Context; -import android.content.Intent; -import android.content.Loader; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.android.ex.photo.Intents; -import com.android.ex.photo.PhotoViewActivity; -import com.android.ex.photo.PhotoViewActivity.CursorChangedListener; -import com.android.ex.photo.PhotoViewActivity.OnScreenListener; -import com.android.ex.photo.R; -import com.android.ex.photo.adapters.PhotoPagerAdapter; -import com.android.ex.photo.loaders.PhotoBitmapLoader; -import com.android.ex.photo.util.ImageUtils; -import com.android.ex.photo.views.PhotoView; -import com.android.ex.photo.views.ProgressBarWrapper; - -/** - * Displays a photo. - */ -public class PhotoViewFragment extends Fragment implements - LoaderCallbacks<Bitmap>, OnClickListener, OnScreenListener, CursorChangedListener { - /** - * Interface for components that are internally scrollable left-to-right. - */ - public static interface HorizontallyScrollable { - /** - * Return {@code true} if the component needs to receive right-to-left - * touch movements. - * - * @param origX the raw x coordinate of the initial touch - * @param origY the raw y coordinate of the initial touch - */ - - public boolean interceptMoveLeft(float origX, float origY); - - /** - * Return {@code true} if the component needs to receive left-to-right - * touch movements. - * - * @param origX the raw x coordinate of the initial touch - * @param origY the raw y coordinate of the initial touch - */ - public boolean interceptMoveRight(float origX, float origY); - } - - private final static String STATE_INTENT_KEY = - "com.android.mail.photo.fragments.PhotoViewFragment.INTENT"; - - // Loader IDs - private final static int LOADER_ID_PHOTO = 1; - private final static int LOADER_ID_THUMBNAIL = 2; - - /** The size of the photo */ - public static Integer sPhotoSize; - - /** The URL of a photo to display */ - private String mResolvedPhotoUri; - private String mThumbnailUri; - /** The intent we were launched with */ - private Intent mIntent; - private PhotoViewActivity mCallback; - private PhotoPagerAdapter mAdapter; - - private PhotoView mPhotoView; - private ImageView mPhotoPreviewImage; - private TextView mEmptyText; - private ImageView mRetryButton; - private ProgressBarWrapper mPhotoProgressBar; - - private final int mPosition; - - /** Whether or not the fragment should make the photo full-screen */ - private boolean mFullScreen; - - /** Whether or not the progress bar is showing valid information about the progress stated */ - private boolean mProgressBarNeeded = true; - - private View mPhotoPreviewAndProgress; - - public PhotoViewFragment() { - mPosition = -1; - mProgressBarNeeded = true; - } - - public PhotoViewFragment(Intent intent, int position, PhotoPagerAdapter adapter) { - mIntent = intent; - mPosition = position; - mAdapter = adapter; - mProgressBarNeeded = true; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mCallback = (PhotoViewActivity) activity; - if (mCallback == null) { - throw new IllegalArgumentException( - "Activity must be a derived class of PhotoViewActivity"); - } - - if (sPhotoSize == null) { - final DisplayMetrics metrics = new DisplayMetrics(); - final WindowManager wm = - (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE); - final ImageUtils.ImageSize imageSize = ImageUtils.sUseImageSize; - wm.getDefaultDisplay().getMetrics(metrics); - switch (imageSize) { - case EXTRA_SMALL: { - // Use a photo that's 80% of the "small" size - sPhotoSize = (Math.min(metrics.heightPixels, metrics.widthPixels) * 800) / 1000; - break; - } - - case SMALL: - case NORMAL: - default: { - sPhotoSize = Math.min(metrics.heightPixels, metrics.widthPixels); - break; - } - } - } - } - - @Override - public void onDetach() { - mCallback = null; - super.onDetach(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (savedInstanceState != null) { - final Bundle state = savedInstanceState.getBundle(STATE_INTENT_KEY); - if (state != null) { - mIntent = new Intent().putExtras(state); - } - } - - if (mIntent != null) { - mResolvedPhotoUri = mIntent.getStringExtra(Intents.EXTRA_RESOLVED_PHOTO_URI); - mThumbnailUri = mIntent.getStringExtra(Intents.EXTRA_THUMBNAIL_URI); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.photo_fragment_view, container, false); - - mPhotoView = (PhotoView) view.findViewById(R.id.photo_view); - mPhotoView.setOnClickListener(this); - mPhotoView.setFullScreen(mFullScreen, false); - mPhotoView.enableImageTransforms(true); - - mPhotoPreviewAndProgress = view.findViewById(R.id.photo_preview); - mPhotoPreviewImage = (ImageView) view.findViewById(R.id.photo_preview_image); - final ProgressBar indeterminate = - (ProgressBar) view.findViewById(R.id.indeterminate_progress); - final ProgressBar determinate = - (ProgressBar) view.findViewById(R.id.determinate_progress); - mPhotoProgressBar = new ProgressBarWrapper(determinate, indeterminate, true); - mEmptyText = (TextView) view.findViewById(R.id.empty_text); - mRetryButton = (ImageView) view.findViewById(R.id.retry_button); - - // Don't call until we've setup the entire view - setViewVisibility(); - - return view; - } - - @Override - public void onResume() { - mCallback.addScreenListener(this); - mCallback.addCursorListener(this); - - getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this); - - super.onResume(); - } - - @Override - public void onPause() { - super.onPause(); - // Remove listeners - mCallback.removeCursorListener(this); - mCallback.removeScreenListener(this); - resetPhotoView(); - } - - @Override - public void onDestroyView() { - // Clean up views and other components - if (mPhotoView != null) { - mPhotoView.clear(); - mPhotoView = null; - } - - super.onDestroyView(); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - - if (mIntent != null) { - outState.putParcelable(STATE_INTENT_KEY, mIntent.getExtras()); - } - } - - @Override - public Loader<Bitmap> onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_PHOTO: - return new PhotoBitmapLoader(getActivity(), mResolvedPhotoUri); - case LOADER_ID_THUMBNAIL: - return new PhotoBitmapLoader(getActivity(), mThumbnailUri); - default: - return null; - } - } - - @Override - public void onLoadFinished(Loader<Bitmap> loader, Bitmap data) { - // If we don't have a view, the fragment has been paused. We'll get the cursor again later. - if (getView() == null) { - return; - } - - final int id = loader.getId(); - switch (id) { - case LOADER_ID_PHOTO: - if (data != null) { - bindPhoto(data); - mPhotoPreviewAndProgress.setVisibility(View.GONE); - mProgressBarNeeded = false; - } - break; - case LOADER_ID_THUMBNAIL: - if (isPhotoBound()) { - // There is need to do anything with the thumbnail image, as the full size - // image is being shown. - mPhotoPreviewAndProgress.setVisibility(View.GONE); - mProgressBarNeeded = false; - return; - } else { - // Make the preview image view visible - mPhotoPreviewImage.setVisibility(View.VISIBLE); - - if (data == null) { - // no preview, show default - mPhotoPreviewImage.setImageResource(R.drawable.default_image); - mPhotoPreviewImage.setScaleType(ImageView.ScaleType.CENTER); - } else { - // Show the preview - mPhotoPreviewImage.setImageBitmap(data); - } - // Now load the full size image - getLoaderManager().initLoader(LOADER_ID_PHOTO, null, this); - } - break; - default: - break; - } - - if (mProgressBarNeeded == false) { - // Hide the progress bar as it isn't needed anymore. - mPhotoProgressBar.setVisibility(View.GONE); - } - if (data != null) { - mCallback.onNewPhotoLoaded(); - } - - setViewVisibility(); - } - - /** - * Binds an image to the photo view. - */ - private void bindPhoto(Bitmap bitmap) { - if (mPhotoView != null) { - mPhotoView.bindPhoto(bitmap); - } - } - - /** - * Resets the photo view to it's default state w/ no bound photo. - */ - private void resetPhotoView() { - if (mPhotoView != null) { - mPhotoView.bindPhoto(null); - } - } - - @Override - public void onLoaderReset(Loader<Bitmap> loader) { - // Do nothing - } - - @Override - public void onClick(View v) { - mCallback.toggleFullScreen(); - } - - @Override - public void onFullScreenChanged(boolean fullScreen) { - setViewVisibility(); - } - - @Override - public void onViewActivated() { - if (!mCallback.isFragmentActive(this)) { - // we're not in the foreground; reset our view - resetViews(); - } else { - if (!isPhotoBound()) { - // Restart the loader - getLoaderManager().restartLoader(LOADER_ID_THUMBNAIL, null, this); - } - mCallback.onFragmentVisible(this); - } - } - - /** - * Reset the views to their default states - */ - public void resetViews() { - if (mPhotoView != null) { - mPhotoView.resetTransformations(); - } - } - - @Override - public boolean onInterceptMoveLeft(float origX, float origY) { - if (!mCallback.isFragmentActive(this)) { - // we're not in the foreground; don't intercept any touches - return false; - } - - return (mPhotoView != null && mPhotoView.interceptMoveLeft(origX, origY)); - } - - @Override - public boolean onInterceptMoveRight(float origX, float origY) { - if (!mCallback.isFragmentActive(this)) { - // we're not in the foreground; don't intercept any touches - return false; - } - - return (mPhotoView != null && mPhotoView.interceptMoveRight(origX, origY)); - } - - /** - * Returns {@code true} if a photo has been bound. Otherwise, returns {@code false}. - */ - public boolean isPhotoBound() { - return (mPhotoView != null && mPhotoView.isPhotoBound()); - } - - /** - * Sets view visibility depending upon whether or not we're in "full screen" mode. - */ - private void setViewVisibility() { - final boolean fullScreen = mCallback.isFragmentFullScreen(this); - final boolean hide = fullScreen; - - setFullScreen(hide); - } - - /** - * Sets full-screen mode for the views. - */ - public void setFullScreen(boolean fullScreen) { - mFullScreen = fullScreen; - } - - @Override - public void onCursorChanged(Cursor cursor) { - if (cursor.moveToPosition(mPosition) && !isPhotoBound()) { - final LoaderManager manager = getLoaderManager(); - final Loader<Bitmap> fakeLoader = manager.getLoader(LOADER_ID_PHOTO); - if (fakeLoader == null) { - return; - } - - final PhotoBitmapLoader loader = - (PhotoBitmapLoader) fakeLoader; - mResolvedPhotoUri = mAdapter.getPhotoUri(cursor); - loader.setPhotoUri(mResolvedPhotoUri); - loader.forceLoad(); - } - } - - public ProgressBarWrapper getPhotoProgressBar() { - return mPhotoProgressBar; - } - - public TextView getEmptyText() { - return mEmptyText; - } - - public ImageView getRetryButton() { - return mRetryButton; - } - - public boolean isProgressBarNeeded() { - return mProgressBarNeeded; - } -} diff --git a/photoviewer/src/com/android/ex/photo/loaders/PhotoBitmapLoader.java b/photoviewer/src/com/android/ex/photo/loaders/PhotoBitmapLoader.java deleted file mode 100644 index fe42e0d..0000000 --- a/photoviewer/src/com/android/ex/photo/loaders/PhotoBitmapLoader.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo.loaders; - -import android.content.AsyncTaskLoader; -import android.content.ContentResolver; -import android.content.Context; -import android.graphics.Bitmap; -import android.net.Uri; -import android.util.DisplayMetrics; - -import com.android.ex.photo.fragments.PhotoViewFragment; -import com.android.ex.photo.util.ImageUtils; - -/** - * Loader for the bitmap of a photo. - */ -public class PhotoBitmapLoader extends AsyncTaskLoader<Bitmap> { - private String mPhotoUri; - - private Bitmap mBitmap; - - public PhotoBitmapLoader(Context context, String photoUri) { - super(context); - mPhotoUri = photoUri; - } - - public void setPhotoUri(String photoUri) { - mPhotoUri = photoUri; - } - - @Override - public Bitmap loadInBackground() { - Context context = getContext(); - - if (context != null && mPhotoUri != null) { - final ContentResolver resolver = context.getContentResolver(); - Bitmap bitmap = ImageUtils.createLocalBitmap(resolver, Uri.parse(mPhotoUri), - PhotoViewFragment.sPhotoSize); - if (bitmap != null) { - bitmap.setDensity(DisplayMetrics.DENSITY_MEDIUM); - } - return bitmap; - } - - return null; - } - - /** - * Called when there is new data to deliver to the client. The - * super class will take care of delivering it; the implementation - * here just adds a little more logic. - */ - @Override - public void deliverResult(Bitmap bitmap) { - if (isReset()) { - // An async query came in while the loader is stopped. We - // don't need the result. - if (bitmap != null) { - onReleaseResources(bitmap); - } - } - Bitmap oldBitmap = mBitmap; - mBitmap = bitmap; - - if (isStarted()) { - // If the Loader is currently started, we can immediately - // deliver its results. - super.deliverResult(bitmap); - } - - // At this point we can release the resources associated with - // 'oldBitmap' if needed; now that the new result is delivered we - // know that it is no longer in use. - if (oldBitmap != null && oldBitmap != bitmap && !oldBitmap.isRecycled()) { - onReleaseResources(oldBitmap); - } - } - - /** - * Handles a request to start the Loader. - */ - @Override - protected void onStartLoading() { - if (mBitmap != null) { - // If we currently have a result available, deliver it - // immediately. - deliverResult(mBitmap); - } - - if (takeContentChanged() || mBitmap == null) { - // If the data has changed since the last time it was loaded - // or is not currently available, start a load. - forceLoad(); - } - } - - /** - * Handles a request to stop the Loader. - */ - @Override protected void onStopLoading() { - // Attempt to cancel the current load task if possible. - cancelLoad(); - } - - /** - * Handles a request to cancel a load. - */ - @Override - public void onCanceled(Bitmap bitmap) { - super.onCanceled(bitmap); - - // At this point we can release the resources associated with 'bitmap' - // if needed. - onReleaseResources(bitmap); - } - - /** - * Handles a request to completely reset the Loader. - */ - @Override - protected void onReset() { - super.onReset(); - - // Ensure the loader is stopped - onStopLoading(); - - // At this point we can release the resources associated with 'bitmap' - // if needed. - if (mBitmap != null) { - onReleaseResources(mBitmap); - mBitmap = null; - } - } - - /** - * Helper function to take care of releasing resources associated - * with an actively loaded data set. - */ - protected void onReleaseResources(Bitmap bitmap) { - if (bitmap != null && !bitmap.isRecycled()) { - bitmap.recycle(); - } - } -} diff --git a/photoviewer/src/com/android/ex/photo/loaders/PhotoPagerLoader.java b/photoviewer/src/com/android/ex/photo/loaders/PhotoPagerLoader.java deleted file mode 100644 index 7ba932b..0000000 --- a/photoviewer/src/com/android/ex/photo/loaders/PhotoPagerLoader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo.loaders; - -import android.content.Context; -import android.content.CursorLoader; -import android.database.Cursor; -import android.net.Uri; - -import com.android.ex.photo.provider.PhotoContract; - -/** - * Loader for a set of photo IDs. - */ -public class PhotoPagerLoader extends CursorLoader { - private final Uri mPhotosUri; - private final String[] mProjection; - - public PhotoPagerLoader( - Context context, Uri photosUri, String[] projection) { - super(context); - mPhotosUri = photosUri; - mProjection = projection != null ? projection : PhotoContract.PhotoQuery.PROJECTION; - } - - @Override - public Cursor loadInBackground() { - Cursor returnCursor = null; - - final Uri loaderUri = mPhotosUri.buildUpon().appendQueryParameter( - PhotoContract.ContentTypeParameters.CONTENT_TYPE, "image/").build(); - setUri(loaderUri); - setProjection(mProjection); - returnCursor = super.loadInBackground(); - - return returnCursor; - } -} diff --git a/photoviewer/src/com/android/ex/photo/provider/PhotoContract.java b/photoviewer/src/com/android/ex/photo/provider/PhotoContract.java deleted file mode 100644 index 439b68b..0000000 --- a/photoviewer/src/com/android/ex/photo/provider/PhotoContract.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo.provider; - -import android.net.Uri; -import android.provider.OpenableColumns; - -public final class PhotoContract { - /** Columns for the view */ - public static interface PhotoViewColumns { - /** - * This column is a {@link Uri} that can be queried - * for this individual image (resulting cursor has one single row for this image). - */ - public static final String URI = "uri"; - /** - * This column is a {@link String} that can be queried for this - * individual image to return a displayable name. - */ - public static final String NAME = OpenableColumns.DISPLAY_NAME; - /** - * This column is a {@link Uri} that points to the downloaded local file. - * Can be null. - */ - public static final String CONTENT_URI = "contentUri"; - /** - * This column is a {@link Uri} that points to a thumbnail of the image - * that ideally is a local file. - * Can be null. - */ - public static final String THUMBNAIL_URI = "thumbnailUri"; - /** - * This string column is the MIME type. - */ - public static final String CONTENT_TYPE = "contentType"; - - } - - public static interface PhotoQuery { - /** Projection of the returned cursor */ - public final static String[] PROJECTION = { - PhotoViewColumns.URI, - PhotoViewColumns.NAME, - PhotoViewColumns.CONTENT_URI, - PhotoViewColumns.THUMBNAIL_URI, - PhotoViewColumns.CONTENT_TYPE, - }; - } - - public static final class ContentTypeParameters { - /** - * Parameter used to specify which type of content to return. - * Allows multiple types to be specified. - */ - public static final String CONTENT_TYPE = "contentType"; - - private ContentTypeParameters() {} - } -} diff --git a/photoviewer/src/com/android/ex/photo/util/Exif.java b/photoviewer/src/com/android/ex/photo/util/Exif.java deleted file mode 100644 index 743b896..0000000 --- a/photoviewer/src/com/android/ex/photo/util/Exif.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.ex.photo.util; - -import android.util.Log; - -public class Exif { - private static final String TAG = "CameraExif"; - - // Returns the degrees in clockwise. Values are 0, 90, 180, or 270. - public static int getOrientation(byte[] jpeg) { - if (jpeg == null) { - return 0; - } - - int offset = 0; - int length = 0; - - // ISO/IEC 10918-1:1993(E) - while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) { - int marker = jpeg[offset] & 0xFF; - - // Check if the marker is a padding. - if (marker == 0xFF) { - continue; - } - offset++; - - // Check if the marker is SOI or TEM. - if (marker == 0xD8 || marker == 0x01) { - continue; - } - // Check if the marker is EOI or SOS. - if (marker == 0xD9 || marker == 0xDA) { - break; - } - - // Get the length and check if it is reasonable. - length = pack(jpeg, offset, 2, false); - if (length < 2 || offset + length > jpeg.length) { - Log.e(TAG, "Invalid length"); - return 0; - } - - // Break if the marker is EXIF in APP1. - if (marker == 0xE1 && length >= 8 && - pack(jpeg, offset + 2, 4, false) == 0x45786966 && - pack(jpeg, offset + 6, 2, false) == 0) { - offset += 8; - length -= 8; - break; - } - - // Skip other markers. - offset += length; - length = 0; - } - - // JEITA CP-3451 Exif Version 2.2 - if (length > 8) { - // Identify the byte order. - int tag = pack(jpeg, offset, 4, false); - if (tag != 0x49492A00 && tag != 0x4D4D002A) { - Log.e(TAG, "Invalid byte order"); - return 0; - } - boolean littleEndian = (tag == 0x49492A00); - - // Get the offset and check if it is reasonable. - int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; - if (count < 10 || count > length) { - Log.e(TAG, "Invalid offset"); - return 0; - } - offset += count; - length -= count; - - // Get the count and go through all the elements. - count = pack(jpeg, offset - 2, 2, littleEndian); - while (count-- > 0 && length >= 12) { - // Get the tag and check if it is orientation. - tag = pack(jpeg, offset, 2, littleEndian); - if (tag == 0x0112) { - // We do not really care about type and count, do we? - int orientation = pack(jpeg, offset + 8, 2, littleEndian); - switch (orientation) { - case 1: - return 0; - case 3: - return 180; - case 6: - return 90; - case 8: - return 270; - } - Log.i(TAG, "Unsupported orientation"); - return 0; - } - offset += 12; - length -= 12; - } - } - - Log.i(TAG, "Orientation not found"); - return 0; - } - - private static int pack(byte[] bytes, int offset, int length, - boolean littleEndian) { - int step = 1; - if (littleEndian) { - offset += length - 1; - step = -1; - } - - int value = 0; - while (length-- > 0) { - value = (value << 8) | (bytes[offset] & 0xFF); - offset += step; - } - return value; - } -} diff --git a/photoviewer/src/com/android/ex/photo/util/ImageUtils.java b/photoviewer/src/com/android/ex/photo/util/ImageUtils.java deleted file mode 100644 index 7fe971a..0000000 --- a/photoviewer/src/com/android/ex/photo/util/ImageUtils.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo.util; - -import android.content.ContentResolver; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Build; -import android.util.DisplayMetrics; -import android.util.Log; - -import com.android.ex.photo.PhotoViewActivity; -import com.android.ex.photo.util.Exif; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -/** - * Image utilities - */ -public class ImageUtils { - // Logging - private static final String TAG = "ImageUtils"; - - /** Minimum class memory class to use full-res photos */ - private final static long MIN_NORMAL_CLASS = 32; - /** Minimum class memory class to use small photos */ - private final static long MIN_SMALL_CLASS = 24; - - public static enum ImageSize { - EXTRA_SMALL, - SMALL, - NORMAL, - } - - public static final ImageSize sUseImageSize; - static { - // On HC and beyond, assume devices are more capable - if (Build.VERSION.SDK_INT >= 11) { - sUseImageSize = ImageSize.NORMAL; - } else { - if (PhotoViewActivity.sMemoryClass >= MIN_NORMAL_CLASS) { - // We have plenty of memory; use full sized photos - sUseImageSize = ImageSize.NORMAL; - } else if (PhotoViewActivity.sMemoryClass >= MIN_SMALL_CLASS) { - // We have slight less memory; use smaller sized photos - sUseImageSize = ImageSize.SMALL; - } else { - // We have little memory; use very small sized photos - sUseImageSize = ImageSize.EXTRA_SMALL; - } - } - } - - /** - * @return true if the MimeType type is image - */ - public static boolean isImageMimeType(String mimeType) { - return mimeType != null && mimeType.startsWith("image/"); - } - - /** - * Create a bitmap from a local URI - * - * @param resolver The ContentResolver - * @param uri The local URI - * @param maxSize The maximum size (either width or height) - * - * @return The new bitmap or null - */ - public static Bitmap createLocalBitmap(ContentResolver resolver, Uri uri, int maxSize) { - InputStream inputStream = null; - try { - final BitmapFactory.Options opts = new BitmapFactory.Options(); - final Point bounds = getImageBounds(resolver, uri); - - inputStream = resolver.openInputStream(uri); - opts.inSampleSize = Math.max(bounds.x / maxSize, bounds.y / maxSize); - - final Bitmap decodedBitmap = decodeStream(inputStream, null, opts); - - // Correct thumbnail orientation as necessary - // TODO: Fix rotation if it's actually a problem - //return rotateBitmap(resolver, uri, decodedBitmap); - return decodedBitmap; - - } catch (FileNotFoundException exception) { - // Do nothing - the photo will appear to be missing - } catch (IOException exception) { - // Do nothing - the photo will appear to be missing - } catch (IllegalArgumentException exception) { - // Do nothing - the photo will appear to be missing - } finally { - try { - if (inputStream != null) { - inputStream.close(); - } - } catch (IOException ignore) { - } - } - return null; - } - - /** - * Wrapper around {@link BitmapFactory#decodeStream(InputStream, Rect, - * BitmapFactory.Options)} that returns {@code null} on {@link - * OutOfMemoryError}. - * - * @param is The input stream that holds the raw data to be decoded into a - * bitmap. - * @param outPadding If not null, return the padding rect for the bitmap if - * it exists, otherwise set padding to [-1,-1,-1,-1]. If - * no bitmap is returned (null) then padding is - * unchanged. - * @param opts null-ok; Options that control downsampling and whether the - * image should be completely decoded, or just is size returned. - * @return The decoded bitmap, or null if the image data could not be - * decoded, or, if opts is non-null, if opts requested only the - * size be returned (in opts.outWidth and opts.outHeight) - */ - public static Bitmap decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts) { - ByteArrayOutputStream out = null; - try { - out = new ByteArrayOutputStream(); - final byte[] buffer = new byte[4096]; - int n = is.read(buffer); - while (n >= 0) { - out.write(buffer, 0, n); - n = is.read(buffer); - } - final byte[] bitmapBytes = out.toByteArray(); - - // Determine the orientation for this image - final int orientation = Exif.getOrientation(bitmapBytes); - final Bitmap originalBitmap = - BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length, opts); - - if (originalBitmap != null && orientation != 0) { - final Matrix matrix = new Matrix(); - matrix.postRotate(orientation); - return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), - originalBitmap.getHeight(), matrix, true); - } - return originalBitmap; - } catch (OutOfMemoryError oome) { - Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an OOME", oome); - return null; - } catch (IOException ioe) { - Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an IOE", ioe); - return null; - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - // Do nothing - } - } - } - } - - /** - * Gets the image bounds - * - * @param resolver The ContentResolver - * @param uri The uri - * - * @return The image bounds - */ - private static Point getImageBounds(ContentResolver resolver, Uri uri) - throws IOException { - final BitmapFactory.Options opts = new BitmapFactory.Options(); - InputStream inputStream = null; - - try { - opts.inJustDecodeBounds = true; - inputStream = resolver.openInputStream(uri); - decodeStream(inputStream, null, opts); - - return new Point(opts.outWidth, opts.outHeight); - } finally { - try { - if (inputStream != null) { - inputStream.close(); - } - } catch (IOException ignore) { - } - } - } -} diff --git a/photoviewer/src/com/android/ex/photo/views/PhotoView.java b/photoviewer/src/com/android/ex/photo/views/PhotoView.java deleted file mode 100644 index 8575bb1..0000000 --- a/photoviewer/src/com/android/ex/photo/views/PhotoView.java +++ /dev/null @@ -1,1288 +0,0 @@ -/* - * Copyright (C) 2011 Google Inc. - * Licensed to 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.ex.photo.views; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Paint.Style; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.drawable.BitmapDrawable; -import android.support.v4.view.GestureDetectorCompat; -import android.util.AttributeSet; -import android.view.GestureDetector.OnGestureListener; -import android.view.GestureDetector.OnDoubleTapListener; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.View; - -import com.android.ex.photo.R; -import com.android.ex.photo.fragments.PhotoViewFragment.HorizontallyScrollable; - -/** - * Layout for the photo list view header. - */ -public class PhotoView extends View implements OnGestureListener, - OnDoubleTapListener, ScaleGestureDetector.OnScaleGestureListener, - HorizontallyScrollable { - /** Zoom animation duration; in milliseconds */ - private final static long ZOOM_ANIMATION_DURATION = 300L; - /** Rotate animation duration; in milliseconds */ - private final static long ROTATE_ANIMATION_DURATION = 500L; - /** Snap animation duration; in milliseconds */ - private static final long SNAP_DURATION = 100L; - /** Amount of time to wait before starting snap animation; in milliseconds */ - private static final long SNAP_DELAY = 250L; - /** By how much to scale the image when double click occurs */ - private final static float DOUBLE_TAP_SCALE_FACTOR = 1.5f; - /** Amount of translation needed before starting a snap animation */ - private final static float SNAP_THRESHOLD = 20.0f; - /** The width & height of the bitmap returned by {@link #getCroppedPhoto()} */ - private final static float CROPPED_SIZE = 256.0f; - - /** If {@code true}, the static values have been initialized */ - private static boolean sInitialized; - - // Various dimensions - /** Width & height of the crop region */ - private static int sCropSize; - - // Bitmaps - /** Video icon */ - private static Bitmap sVideoImage; - /** Video icon */ - private static Bitmap sVideoNotReadyImage; - - // Features - private static boolean sHasMultitouchDistinct; - - // Paints - /** Paint to partially dim the photo during crop */ - private static Paint sCropDimPaint; - /** Paint to highlight the cropped portion of the photo */ - private static Paint sCropPaint; - - /** The photo to display */ - private BitmapDrawable mDrawable; - /** The matrix used for drawing; this may be {@code null} */ - private Matrix mDrawMatrix; - /** A matrix to apply the scaling of the photo */ - private Matrix mMatrix = new Matrix(); - /** The original matrix for this image; used to reset any transformations applied by the user */ - private Matrix mOriginalMatrix = new Matrix(); - - /** The fixed height of this view. If {@code -1}, calculate the height */ - private int mFixedHeight = -1; - /** When {@code true}, the view has been laid out */ - private boolean mHaveLayout; - /** Whether or not the photo is full-screen */ - private boolean mFullScreen; - /** Whether or not this is a still image of a video */ - private byte[] mVideoBlob; - /** Whether or not this is a still image of a video */ - private boolean mVideoReady; - - /** Whether or not crop is allowed */ - private boolean mAllowCrop; - /** The crop region */ - private Rect mCropRect = new Rect(); - /** Actual crop size; may differ from {@link #sCropSize} if the screen is smaller */ - private int mCropSize; - - /** Gesture detector */ - private GestureDetectorCompat mGestureDetector; - /** Gesture detector that detects pinch gestures */ - private ScaleGestureDetector mScaleGetureDetector; - /** An external click listener */ - private OnClickListener mExternalClickListener; - /** When {@code true}, allows gestures to scale / pan the image */ - private boolean mTransformsEnabled; - - // To support zooming - /** When {@code true}, a double tap scales the image by {@link #DOUBLE_TAP_SCALE_FACTOR} */ - private boolean mDoubleTapToZoomEnabled = true; - /** When {@code true}, prevents scale end gesture from falsely triggering a double click. */ - private boolean mDoubleTapDebounce; - /** When {@code false}, event is a scale gesture. Otherwise, event is a double touch. */ - private boolean mIsDoubleTouch; - /** Runnable that scales the image */ - private ScaleRunnable mScaleRunnable; - /** Minimum scale the image can have. */ - private float mMinScale; - /** Maximum scale to limit scaling to, 0 means no limit. */ - private float mMaxScale; - - // To support translation [i.e. panning] - /** Runnable that can move the image */ - private TranslateRunnable mTranslateRunnable; - private SnapRunnable mSnapRunnable; - - // To support rotation - /** The rotate runnable used to animate rotations of the image */ - private RotateRunnable mRotateRunnable; - /** The current rotation amount, in degrees */ - private float mRotation; - - // Convenience fields - // These are declared here not because they are important properties of the view. Rather, we - // declare them here to avoid object allocation during critical graphics operations; such as - // layout or drawing. - /** Source (i.e. the photo size) bounds */ - private RectF mTempSrc = new RectF(); - /** Destination (i.e. the display) bounds. The image is scaled to this size. */ - private RectF mTempDst = new RectF(); - /** Rectangle to handle translations */ - private RectF mTranslateRect = new RectF(); - /** Array to store a copy of the matrix values */ - private float[] mValues = new float[9]; - - public PhotoView(Context context) { - super(context); - initialize(); - } - - public PhotoView(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public PhotoView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - initialize(); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (mScaleGetureDetector == null || mGestureDetector == null) { - // We're being destroyed; ignore any touch events - return true; - } - - mScaleGetureDetector.onTouchEvent(event); - mGestureDetector.onTouchEvent(event); - final int action = event.getAction(); - - switch (action) { - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (!mTranslateRunnable.mRunning) { - snap(); - } - break; - } - - return true; - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - if (mDoubleTapToZoomEnabled && mTransformsEnabled) { - if (!mDoubleTapDebounce) { - float currentScale = getScale(); - float targetScale = currentScale * DOUBLE_TAP_SCALE_FACTOR; - - // Ensure the target scale is within our bounds - targetScale = Math.max(mMinScale, targetScale); - targetScale = Math.min(mMaxScale, targetScale); - - mScaleRunnable.start(currentScale, targetScale, e.getX(), e.getY()); - } - mDoubleTapDebounce = false; - } - return true; - } - - @Override - public boolean onDoubleTapEvent(MotionEvent e) { - return true; - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - if (mExternalClickListener != null && !mIsDoubleTouch) { - mExternalClickListener.onClick(this); - } - mIsDoubleTouch = false; - return true; - } - - @Override - public boolean onSingleTapUp(MotionEvent e) { - return false; - } - - @Override - public void onLongPress(MotionEvent e) { - } - - @Override - public void onShowPress(MotionEvent e) { - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (mTransformsEnabled) { - translate(-distanceX, -distanceY); - } - return true; - } - - @Override - public boolean onDown(MotionEvent e) { - if (mTransformsEnabled) { - mTranslateRunnable.stop(); - mSnapRunnable.stop(); - } - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (mTransformsEnabled) { - mTranslateRunnable.start(velocityX, velocityY); - } - return true; - } - - @Override - public boolean onScale(ScaleGestureDetector detector) { - if (mTransformsEnabled) { - mIsDoubleTouch = false; - float currentScale = getScale(); - float newScale = currentScale * detector.getScaleFactor(); - scale(newScale, detector.getFocusX(), detector.getFocusY()); - } - return true; - } - - @Override - public boolean onScaleBegin(ScaleGestureDetector detector) { - if (mTransformsEnabled) { - mScaleRunnable.stop(); - mIsDoubleTouch = true; - } - return true; - } - - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - if (mTransformsEnabled && mIsDoubleTouch) { - mDoubleTapDebounce = true; - resetTransformations(); - } - } - - @Override - public void setOnClickListener(OnClickListener listener) { - mExternalClickListener = listener; - } - - @Override - public boolean interceptMoveLeft(float origX, float origY) { - if (!mTransformsEnabled) { - // Allow intercept if we're not in transform mode - return false; - } else if (mTranslateRunnable.mRunning) { - // Don't allow touch intercept until we've stopped flinging - return true; - } else { - mMatrix.getValues(mValues); - mTranslateRect.set(mTempSrc); - mMatrix.mapRect(mTranslateRect); - - final float viewWidth = getWidth(); - final float transX = mValues[Matrix.MTRANS_X]; - final float drawWidth = mTranslateRect.right - mTranslateRect.left; - - if (!mTransformsEnabled || drawWidth <= viewWidth) { - // Allow intercept if not in transform mode or the image is smaller than the view - return false; - } else if (transX == 0) { - // We're at the left-side of the image; allow intercepting movements to the right - return false; - } else if (viewWidth >= drawWidth + transX) { - // We're at the right-side of the image; allow intercepting movements to the left - return true; - } else { - // We're in the middle of the image; don't allow touch intercept - return true; - } - } - } - - @Override - public boolean interceptMoveRight(float origX, float origY) { - if (!mTransformsEnabled) { - // Allow intercept if we're not in transform mode - return false; - } else if (mTranslateRunnable.mRunning) { - // Don't allow touch intercept until we've stopped flinging - return true; - } else { - mMatrix.getValues(mValues); - mTranslateRect.set(mTempSrc); - mMatrix.mapRect(mTranslateRect); - - final float viewWidth = getWidth(); - final float transX = mValues[Matrix.MTRANS_X]; - final float drawWidth = mTranslateRect.right - mTranslateRect.left; - - if (!mTransformsEnabled || drawWidth <= viewWidth) { - // Allow intercept if not in transform mode or the image is smaller than the view - return false; - } else if (transX == 0) { - // We're at the left-side of the image; allow intercepting movements to the right - return true; - } else if (viewWidth >= drawWidth + transX) { - // We're at the right-side of the image; allow intercepting movements to the left - return false; - } else { - // We're in the middle of the image; don't allow touch intercept - return true; - } - } - } - - /** - * Free all resources held by this view. - * The view is on its way to be collected and will not be reused. - */ - public void clear() { - mGestureDetector = null; - mScaleGetureDetector = null; - mDrawable = null; - mScaleRunnable.stop(); - mScaleRunnable = null; - mTranslateRunnable.stop(); - mTranslateRunnable = null; - mSnapRunnable.stop(); - mSnapRunnable = null; - mRotateRunnable.stop(); - mRotateRunnable = null; - setOnClickListener(null); - mExternalClickListener = null; - } - - /** - * Binds a bitmap to the view. - * - * @param photoBitmap the bitmap to bind. - */ - public void bindPhoto(Bitmap photoBitmap) { - boolean changed = false; - if (mDrawable != null) { - final Bitmap drawableBitmap = mDrawable.getBitmap(); - if (photoBitmap == drawableBitmap) { - // setting the same bitmap; do nothing - return; - } - - changed = photoBitmap != null && - (mDrawable.getIntrinsicWidth() != photoBitmap.getWidth() || - mDrawable.getIntrinsicHeight() != photoBitmap.getHeight()); - - // Reset mMinScale to ensure the bounds / matrix are recalculated - mMinScale = 0f; - mDrawable = null; - } - - if (mDrawable == null && photoBitmap != null) { - mDrawable = new BitmapDrawable(getResources(), photoBitmap); - } - - configureBounds(changed); - invalidate(); - } - - /** - * Returns the bound photo data if set. Otherwise, {@code null}. - */ - public Bitmap getPhoto() { - if (mDrawable != null) { - return mDrawable.getBitmap(); - } - return null; - } - - /** - * Gets video data associated with this item. Returns {@code null} if this is not a video. - */ - public byte[] getVideoData() { - return mVideoBlob; - } - - /** - * Returns {@code true} if the photo represents a video. Otherwise, {@code false}. - */ - public boolean isVideo() { - return mVideoBlob != null; - } - - /** - * Returns {@code true} if the video is ready to play. Otherwise, {@code false}. - */ - public boolean isVideoReady() { - return mVideoBlob != null && mVideoReady; - } - - /** - * Returns {@code true} if a photo has been bound. Otherwise, {@code false}. - */ - public boolean isPhotoBound() { - return mDrawable != null; - } - - /** - * Hides the photo info portion of the header. As a side effect, this automatically enables - * or disables image transformations [eg zoom, pan, etc...] depending upon the value of - * fullScreen. If this is not desirable, enable / disable image transformations manually. - */ - public void setFullScreen(boolean fullScreen, boolean animate) { - if (fullScreen != mFullScreen) { - mFullScreen = fullScreen; - requestLayout(); - invalidate(); - } - } - - /** - * Enable or disable cropping of the displayed image. Cropping can only be enabled - * <em>before</em> the view has been laid out. Additionally, once cropping has been - * enabled, it cannot be disabled. - */ - public void enableAllowCrop(boolean allowCrop) { - if (allowCrop && mHaveLayout) { - throw new IllegalArgumentException("Cannot set crop after view has been laid out"); - } - if (!allowCrop && mAllowCrop) { - throw new IllegalArgumentException("Cannot unset crop mode"); - } - mAllowCrop = allowCrop; - } - - /** - * Gets a bitmap of the cropped region. If cropping is not enabled, returns {@code null}. - */ - public Bitmap getCroppedPhoto() { - if (!mAllowCrop) { - return null; - } - - final Bitmap croppedBitmap = Bitmap.createBitmap( - (int) CROPPED_SIZE, (int) CROPPED_SIZE, Bitmap.Config.ARGB_8888); - final Canvas croppedCanvas = new Canvas(croppedBitmap); - - // scale for the final dimensions - final int cropWidth = mCropRect.right - mCropRect.left; - final float scaleWidth = CROPPED_SIZE / cropWidth; - final float scaleHeight = CROPPED_SIZE / cropWidth; - - // translate to the origin & scale - final Matrix matrix = new Matrix(mDrawMatrix); - matrix.postTranslate(-mCropRect.left, -mCropRect.top); - matrix.postScale(scaleWidth, scaleHeight); - - // draw the photo - if (mDrawable != null) { - croppedCanvas.concat(matrix); - mDrawable.draw(croppedCanvas); - } - return croppedBitmap; - } - - /** - * Resets the image transformation to its original value. - */ - public void resetTransformations() { - // snap transformations; we don't animate - mMatrix.set(mOriginalMatrix); - - // Invalidate the view because if you move off this PhotoView - // to another one and come back, you want it to draw from scratch - // in case you were zoomed in or translated (since those settings - // are not preserved and probably shouldn't be). - invalidate(); - } - - /** - * Rotates the image 90 degrees, clockwise. - */ - public void rotateClockwise() { - rotate(90, true); - } - - /** - * Rotates the image 90 degrees, counter clockwise. - */ - public void rotateCounterClockwise() { - rotate(-90, true); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // draw the photo - if (mDrawable != null) { - int saveCount = canvas.getSaveCount(); - canvas.save(); - - if (mDrawMatrix != null) { - canvas.concat(mDrawMatrix); - } - mDrawable.draw(canvas); - - canvas.restoreToCount(saveCount); - - if (mVideoBlob != null) { - final Bitmap videoImage = (mVideoReady ? sVideoImage : sVideoNotReadyImage); - final int drawLeft = (getWidth() - videoImage.getWidth()) / 2; - final int drawTop = (getHeight() - videoImage.getHeight()) / 2; - canvas.drawBitmap(videoImage, drawLeft, drawTop, null); - } - - // Extract the drawable's bounds (in our own copy, to not alter the image) - mTranslateRect.set(mDrawable.getBounds()); - if (mDrawMatrix != null) { - mDrawMatrix.mapRect(mTranslateRect); - } - - if (mAllowCrop) { - int previousSaveCount = canvas.getSaveCount(); - canvas.drawRect(0, 0, getWidth(), getHeight(), sCropDimPaint); - canvas.save(); - canvas.clipRect(mCropRect); - - if (mDrawMatrix != null) { - canvas.concat(mDrawMatrix); - } - - mDrawable.draw(canvas); - canvas.restoreToCount(previousSaveCount); - canvas.drawRect(mCropRect, sCropPaint); - } - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - mHaveLayout = true; - final int layoutWidth = getWidth(); - final int layoutHeight = getHeight(); - - if (mAllowCrop) { - mCropSize = Math.min(sCropSize, Math.min(layoutWidth, layoutHeight)); - final int cropLeft = (layoutWidth - mCropSize) / 2; - final int cropTop = (layoutHeight - mCropSize) / 2; - final int cropRight = cropLeft + mCropSize; - final int cropBottom = cropTop + mCropSize; - - // Create a crop region overlay. We need a separate canvas to be able to "punch - // a hole" through to the underlying image. - mCropRect.set(cropLeft, cropTop, cropRight, cropBottom); - } - configureBounds(changed); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mFixedHeight != -1) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(mFixedHeight, - MeasureSpec.AT_MOST)); - setMeasuredDimension(getMeasuredWidth(), mFixedHeight); - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - - /** - * Forces a fixed height for this view. - * - * @param fixedHeight The height. If {@code -1}, use the measured height. - */ - public void setFixedHeight(int fixedHeight) { - final boolean adjustBounds = (fixedHeight != mFixedHeight); - mFixedHeight = fixedHeight; - setMeasuredDimension(getMeasuredWidth(), mFixedHeight); - if (adjustBounds) { - configureBounds(true); - requestLayout(); - } - } - - /** - * Enable or disable image transformations. When transformations are enabled, this view - * consumes all touch events. - */ - public void enableImageTransforms(boolean enable) { - mTransformsEnabled = enable; - if (!mTransformsEnabled) { - resetTransformations(); - } - } - - /** - * Configures the bounds of the photo. The photo will always be scaled to fit center. - */ - private void configureBounds(boolean changed) { - if (mDrawable == null || !mHaveLayout) { - return; - } - final int dwidth = mDrawable.getIntrinsicWidth(); - final int dheight = mDrawable.getIntrinsicHeight(); - - final int vwidth = getWidth(); - final int vheight = getHeight(); - - final boolean fits = (dwidth < 0 || vwidth == dwidth) && - (dheight < 0 || vheight == dheight); - - // We need to do the scaling ourself, so have the drawable use its native size. - mDrawable.setBounds(0, 0, dwidth, dheight); - - // Create a matrix with the proper transforms - if (changed || (mMinScale == 0 && mDrawable != null && mHaveLayout)) { - generateMatrix(); - generateScale(); - } - - if (fits || mMatrix.isIdentity()) { - // The bitmap fits exactly, no transform needed. - mDrawMatrix = null; - } else { - mDrawMatrix = mMatrix; - } - } - - /** - * Generates the initial transformation matrix for drawing. Additionally, it sets the - * minimum and maximum scale values. - */ - private void generateMatrix() { - final int dwidth = mDrawable.getIntrinsicWidth(); - final int dheight = mDrawable.getIntrinsicHeight(); - - final int vwidth = mAllowCrop ? sCropSize : getWidth(); - final int vheight = mAllowCrop ? sCropSize : getHeight(); - - final boolean fits = (dwidth < 0 || vwidth == dwidth) && - (dheight < 0 || vheight == dheight); - - if (fits && !mAllowCrop) { - mMatrix.reset(); - } else { - // Generate the required transforms for the photo - mTempSrc.set(0, 0, dwidth, dheight); - if (mAllowCrop) { - mTempDst.set(mCropRect); - } else { - mTempDst.set(0, 0, vwidth, vheight); - } - - if (dwidth < vwidth && dheight < vheight && !mAllowCrop) { - mMatrix.setTranslate(vwidth / 2 - dwidth / 2, vheight / 2 - dheight / 2); - } else { - mMatrix.setRectToRect(mTempSrc, mTempDst, Matrix.ScaleToFit.CENTER); - } - } - mOriginalMatrix.set(mMatrix); - } - - private void generateScale() { - final int dwidth = mDrawable.getIntrinsicWidth(); - final int dheight = mDrawable.getIntrinsicHeight(); - - final int vwidth = mAllowCrop ? getCropSize() : getWidth(); - final int vheight = mAllowCrop ? getCropSize() : getHeight(); - - if (dwidth < vwidth && dheight < vheight && !mAllowCrop) { - mMinScale = 1.0f; - } else { - mMinScale = getScale(); - } - mMaxScale = Math.max(mMinScale * 8, 8); - } - - /** - * @return the size of the crop regions - */ - private int getCropSize() { - return mCropSize > 0 ? mCropSize : sCropSize; - } - - /** - * Returns the currently applied scale factor for the image. - * <p> - * NOTE: This method overwrites any values stored in {@link #mValues}. - */ - private float getScale() { - mMatrix.getValues(mValues); - return mValues[Matrix.MSCALE_X]; - } - - /** - * Scales the image while keeping the aspect ratio. - * - * The given scale is capped so that the resulting scale of the image always remains - * between {@link #mMinScale} and {@link #mMaxScale}. - * - * The scaled image is never allowed to be outside of the viewable area. If the image - * is smaller than the viewable area, it will be centered. - * - * @param newScale the new scale - * @param centerX the center horizontal point around which to scale - * @param centerY the center vertical point around which to scale - */ - private void scale(float newScale, float centerX, float centerY) { - // rotate back to the original orientation - mMatrix.postRotate(-mRotation, getWidth() / 2, getHeight() / 2); - - // ensure that mMixScale <= newScale <= mMaxScale - newScale = Math.max(newScale, mMinScale); - newScale = Math.min(newScale, mMaxScale); - - float currentScale = getScale(); - float factor = newScale / currentScale; - - // apply the scale factor - mMatrix.postScale(factor, factor, centerX, centerY); - - // ensure the image is within the view bounds - snap(); - - // re-apply any rotation - mMatrix.postRotate(mRotation, getWidth() / 2, getHeight() / 2); - - invalidate(); - } - - /** - * Translates the image. - * - * This method will not allow the image to be translated outside of the visible area. - * - * @param tx how many pixels to translate horizontally - * @param ty how many pixels to translate vertically - * @return {@code true} if the translation was applied as specified. Otherwise, {@code false} - * if the translation was modified. - */ - private boolean translate(float tx, float ty) { - mTranslateRect.set(mTempSrc); - mMatrix.mapRect(mTranslateRect); - - final float maxLeft = mAllowCrop ? mCropRect.left : 0.0f; - final float maxRight = mAllowCrop ? mCropRect.right : getWidth(); - float l = mTranslateRect.left; - float r = mTranslateRect.right; - - final float translateX; - if (mAllowCrop) { - // If we're cropping, allow the image to scroll off the edge of the screen - translateX = Math.max(maxLeft - mTranslateRect.right, - Math.min(maxRight - mTranslateRect.left, tx)); - } else { - // Otherwise, ensure the image never leaves the screen - if (r - l < maxRight - maxLeft) { - translateX = maxLeft + ((maxRight - maxLeft) - (r + l)) / 2; - } else { - translateX = Math.max(maxRight - r, Math.min(maxLeft - l, tx)); - } - } - - float maxTop = mAllowCrop ? mCropRect.top: 0.0f; - float maxBottom = mAllowCrop ? mCropRect.bottom : getHeight(); - float t = mTranslateRect.top; - float b = mTranslateRect.bottom; - - final float translateY; - - if (mAllowCrop) { - // If we're cropping, allow the image to scroll off the edge of the screen - translateY = Math.max(maxTop - mTranslateRect.bottom, - Math.min(maxBottom - mTranslateRect.top, ty)); - } else { - // Otherwise, ensure the image never leaves the screen - if (b - t < maxBottom - maxTop) { - translateY = maxTop + ((maxBottom - maxTop) - (b + t)) / 2; - } else { - translateY = Math.max(maxBottom - b, Math.min(maxTop - t, ty)); - } - } - - // Do the translation - mMatrix.postTranslate(translateX, translateY); - invalidate(); - - return (translateX == tx) && (translateY == ty); - } - - /** - * Snaps the image so it touches all edges of the view. - */ - private void snap() { - mTranslateRect.set(mTempSrc); - mMatrix.mapRect(mTranslateRect); - - // Determine how much to snap in the horizontal direction [if any] - float maxLeft = mAllowCrop ? mCropRect.left : 0.0f; - float maxRight = mAllowCrop ? mCropRect.right : getWidth(); - float l = mTranslateRect.left; - float r = mTranslateRect.right; - - final float translateX; - if (r - l < maxRight - maxLeft) { - // Image is narrower than view; translate to the center of the view - translateX = maxLeft + ((maxRight - maxLeft) - (r + l)) / 2; - } else if (l > maxLeft) { - // Image is off right-edge of screen; bring it into view - translateX = maxLeft - l; - } else if (r < maxRight) { - // Image is off left-edge of screen; bring it into view - translateX = maxRight - r; - } else { - translateX = 0.0f; - } - - // Determine how much to snap in the vertical direction [if any] - float maxTop = mAllowCrop ? mCropRect.top : 0.0f; - float maxBottom = mAllowCrop ? mCropRect.bottom : getHeight(); - float t = mTranslateRect.top; - float b = mTranslateRect.bottom; - - final float translateY; - if (b - t < maxBottom - maxTop) { - // Image is shorter than view; translate to the bottom edge of the view - translateY = maxTop + ((maxBottom - maxTop) - (b + t)) / 2; - } else if (t > maxTop) { - // Image is off bottom-edge of screen; bring it into view - translateY = maxTop - t; - } else if (b < maxBottom) { - // Image is off top-edge of screen; bring it into view - translateY = maxBottom - b; - } else { - translateY = 0.0f; - } - - if (Math.abs(translateX) > SNAP_THRESHOLD || Math.abs(translateY) > SNAP_THRESHOLD) { - mSnapRunnable.start(translateX, translateY); - } else { - mMatrix.postTranslate(translateX, translateY); - invalidate(); - } - } - - /** - * Rotates the image, either instantly or gradually - * - * @param degrees how many degrees to rotate the image, positive rotates clockwise - * @param animate if {@code true}, animate during the rotation. Otherwise, snap rotate. - */ - private void rotate(float degrees, boolean animate) { - if (animate) { - mRotateRunnable.start(degrees); - } else { - mRotation += degrees; - mMatrix.postRotate(degrees, getWidth() / 2, getHeight() / 2); - invalidate(); - } - } - - /** - * Initializes the header and any static values - */ - private void initialize() { - Context context = getContext(); - - if (!sInitialized) { - sInitialized = true; - - Resources resources = context.getApplicationContext().getResources(); - - sCropSize = resources.getDimensionPixelSize(R.dimen.photo_crop_width); - - sCropDimPaint = new Paint(); - sCropDimPaint.setAntiAlias(true); - sCropDimPaint.setColor(resources.getColor(R.color.photo_crop_dim_color)); - sCropDimPaint.setStyle(Style.FILL); - - sCropPaint = new Paint(); - sCropPaint.setAntiAlias(true); - sCropPaint.setColor(resources.getColor(R.color.photo_crop_highlight_color)); - sCropPaint.setStyle(Style.STROKE); - sCropPaint.setStrokeWidth(resources.getDimension(R.dimen.photo_crop_stroke_width)); - } - - mGestureDetector = new GestureDetectorCompat(context, this, null); - mScaleGetureDetector = new ScaleGestureDetector(context, this); - mScaleRunnable = new ScaleRunnable(this); - mTranslateRunnable = new TranslateRunnable(this); - mSnapRunnable = new SnapRunnable(this); - mRotateRunnable = new RotateRunnable(this); - } - - /** - * Runnable that animates an image scale operation. - */ - private static class ScaleRunnable implements Runnable { - - private final PhotoView mHeader; - - private float mCenterX; - private float mCenterY; - - private boolean mZoomingIn; - - private float mTargetScale; - private float mStartScale; - private float mVelocity; - private long mStartTime; - - private boolean mRunning; - private boolean mStop; - - public ScaleRunnable(PhotoView header) { - mHeader = header; - } - - /** - * Starts the animation. There is no target scale bounds check. - */ - public boolean start(float startScale, float targetScale, float centerX, float centerY) { - if (mRunning) { - return false; - } - - mCenterX = centerX; - mCenterY = centerY; - - // Ensure the target scale is within the min/max bounds - mTargetScale = targetScale; - mStartTime = System.currentTimeMillis(); - mStartScale = startScale; - mZoomingIn = mTargetScale > mStartScale; - mVelocity = (mTargetScale - mStartScale) / ZOOM_ANIMATION_DURATION; - mRunning = true; - mStop = false; - mHeader.post(this); - return true; - } - - /** - * Stops the animation in place. It does not snap the image to its final zoom. - */ - public void stop() { - mRunning = false; - mStop = true; - } - - @Override - public void run() { - if (mStop) { - return; - } - - // Scale - long now = System.currentTimeMillis(); - long ellapsed = now - mStartTime; - float newScale = (mStartScale + mVelocity * ellapsed); - mHeader.scale(newScale, mCenterX, mCenterY); - - // Stop when done - if (newScale == mTargetScale || (mZoomingIn == (newScale > mTargetScale))) { - mHeader.scale(mTargetScale, mCenterX, mCenterY); - stop(); - } - - if (!mStop) { - mHeader.post(this); - } - } - } - - /** - * Runnable that animates an image translation operation. - */ - private static class TranslateRunnable implements Runnable { - - private static final float DECELERATION_RATE = 1000f; - private static final long NEVER = -1L; - - private final PhotoView mHeader; - - private float mVelocityX; - private float mVelocityY; - - private long mLastRunTime; - private boolean mRunning; - private boolean mStop; - - public TranslateRunnable(PhotoView header) { - mLastRunTime = NEVER; - mHeader = header; - } - - /** - * Starts the animation. - */ - public boolean start(float velocityX, float velocityY) { - if (mRunning) { - return false; - } - mLastRunTime = NEVER; - mVelocityX = velocityX; - mVelocityY = velocityY; - mStop = false; - mRunning = true; - mHeader.post(this); - return true; - } - - /** - * Stops the animation in place. It does not snap the image to its final translation. - */ - public void stop() { - mRunning = false; - mStop = true; - } - - @Override - public void run() { - // See if we were told to stop: - if (mStop) { - return; - } - - // Translate according to current velocities and time delta: - long now = System.currentTimeMillis(); - float delta = (mLastRunTime != NEVER) ? (now - mLastRunTime) / 1000f : 0f; - final boolean didTranslate = mHeader.translate(mVelocityX * delta, mVelocityY * delta); - mLastRunTime = now; - // Slow down: - float slowDown = DECELERATION_RATE * delta; - if (mVelocityX > 0f) { - mVelocityX -= slowDown; - if (mVelocityX < 0f) { - mVelocityX = 0f; - } - } else { - mVelocityX += slowDown; - if (mVelocityX > 0f) { - mVelocityX = 0f; - } - } - if (mVelocityY > 0f) { - mVelocityY -= slowDown; - if (mVelocityY < 0f) { - mVelocityY = 0f; - } - } else { - mVelocityY += slowDown; - if (mVelocityY > 0f) { - mVelocityY = 0f; - } - } - - // Stop when done - if ((mVelocityX == 0f && mVelocityY == 0f) || !didTranslate) { - stop(); - mHeader.snap(); - } - - // See if we need to continue flinging: - if (mStop) { - return; - } - mHeader.post(this); - } - } - - /** - * Runnable that animates an image translation operation. - */ - private static class SnapRunnable implements Runnable { - - private static final long NEVER = -1L; - - private final PhotoView mHeader; - - private float mTranslateX; - private float mTranslateY; - - private long mStartRunTime; - private boolean mRunning; - private boolean mStop; - - public SnapRunnable(PhotoView header) { - mStartRunTime = NEVER; - mHeader = header; - } - - /** - * Starts the animation. - */ - public boolean start(float translateX, float translateY) { - if (mRunning) { - return false; - } - mStartRunTime = NEVER; - mTranslateX = translateX; - mTranslateY = translateY; - mStop = false; - mRunning = true; - mHeader.postDelayed(this, SNAP_DELAY); - return true; - } - - /** - * Stops the animation in place. It does not snap the image to its final translation. - */ - public void stop() { - mRunning = false; - mStop = true; - } - - @Override - public void run() { - // See if we were told to stop: - if (mStop) { - return; - } - - // Translate according to current velocities and time delta: - long now = System.currentTimeMillis(); - float delta = (mStartRunTime != NEVER) ? (now - mStartRunTime) : 0f; - - if (mStartRunTime == NEVER) { - mStartRunTime = now; - } - - float transX; - float transY; - if (delta >= SNAP_DURATION) { - transX = mTranslateX; - transY = mTranslateY; - } else { - transX = (mTranslateX / (SNAP_DURATION - delta)) * 10f; - transY = (mTranslateY / (SNAP_DURATION - delta)) * 10f; - if (Math.abs(transX) > Math.abs(mTranslateX) || transX == Float.NaN) { - transX = mTranslateX; - } - if (Math.abs(transY) > Math.abs(mTranslateY) || transY == Float.NaN) { - transY = mTranslateY; - } - } - - mHeader.translate(transX, transY); - mTranslateX -= transX; - mTranslateY -= transY; - - if (mTranslateX == 0 && mTranslateY == 0) { - stop(); - } - - // See if we need to continue flinging: - if (mStop) { - return; - } - mHeader.post(this); - } - } - - /** - * Runnable that animates an image rotation operation. - */ - private static class RotateRunnable implements Runnable { - - private static final long NEVER = -1L; - - private final PhotoView mHeader; - - private float mTargetRotation; - private float mAppliedRotation; - private float mVelocity; - private long mLastRuntime; - - private boolean mRunning; - private boolean mStop; - - public RotateRunnable(PhotoView header) { - mHeader = header; - } - - /** - * Starts the animation. - */ - public void start(float rotation) { - if (mRunning) { - return; - } - - mTargetRotation = rotation; - mVelocity = mTargetRotation / ROTATE_ANIMATION_DURATION; - mAppliedRotation = 0f; - mLastRuntime = NEVER; - mStop = false; - mRunning = true; - mHeader.post(this); - } - - /** - * Stops the animation in place. It does not snap the image to its final rotation. - */ - public void stop() { - mRunning = false; - mStop = true; - } - - @Override - public void run() { - if (mStop) { - return; - } - - if (mAppliedRotation != mTargetRotation) { - long now = System.currentTimeMillis(); - long delta = mLastRuntime != NEVER ? now - mLastRuntime : 0L; - float rotationAmount = mVelocity * delta; - if (mAppliedRotation < mTargetRotation - && mAppliedRotation + rotationAmount > mTargetRotation - || mAppliedRotation > mTargetRotation - && mAppliedRotation + rotationAmount < mTargetRotation) { - rotationAmount = mTargetRotation - mAppliedRotation; - } - mHeader.rotate(rotationAmount, false); - mAppliedRotation += rotationAmount; - if (mAppliedRotation == mTargetRotation) { - stop(); - } - mLastRuntime = now; - } - - if (mStop) { - return; - } - mHeader.post(this); - } - } -} diff --git a/photoviewer/src/com/android/ex/photo/views/ProgressBarWrapper.java b/photoviewer/src/com/android/ex/photo/views/ProgressBarWrapper.java deleted file mode 100644 index 2669273..0000000 --- a/photoviewer/src/com/android/ex/photo/views/ProgressBarWrapper.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * Licensed to 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.ex.photo.views; - -import android.view.View; -import android.widget.ProgressBar; - -/** - * This class wraps around two progress bars and is solely designed to fix - * a bug in the framework (b/6928449) that prevents a progress bar from - * gracefully switching back and forth between indeterminate and determinate - * modes. - */ -public class ProgressBarWrapper { - private final ProgressBar mDeterminate; - private final ProgressBar mIndeterminate; - private boolean mIsIndeterminate; - - public ProgressBarWrapper(ProgressBar determinate, - ProgressBar indeterminate, boolean isIndeterminate) { - mDeterminate = determinate; - mIndeterminate = indeterminate; - setIndeterminate(isIndeterminate); - } - - public void setIndeterminate(boolean isIndeterminate) { - mIsIndeterminate = isIndeterminate; - - setVisibility(mIsIndeterminate); - } - - public void setVisibility(int visibility) { - if (visibility == View.INVISIBLE || visibility == View.GONE) { - mIndeterminate.setVisibility(visibility); - mDeterminate.setVisibility(visibility); - } else { - setVisibility(mIsIndeterminate); - } - } - - private void setVisibility(boolean isIndeterminate) { - mIndeterminate.setVisibility(isIndeterminate ? View.VISIBLE : View.GONE); - mDeterminate.setVisibility(isIndeterminate ? View.GONE : View.VISIBLE); - } - - public void setMax(int max) { - mDeterminate.setMax(max); - } - - public void setProgress(int progress) { - mDeterminate.setProgress(progress); - } -} |