summaryrefslogtreecommitdiffstats
path: root/photoviewer
diff options
context:
space:
mode:
Diffstat (limited to 'photoviewer')
-rw-r--r--photoviewer/.gitignore8
-rw-r--r--photoviewer/AndroidManifest.xml3
-rw-r--r--photoviewer/src/com/android/ex/photo/Intents.java15
-rw-r--r--photoviewer/src/com/android/ex/photo/PhotoViewActivity.java39
-rw-r--r--photoviewer/src/com/android/ex/photo/adapters/BaseCursorPagerAdapter.java22
-rw-r--r--photoviewer/src/com/android/ex/photo/adapters/BaseFragmentPagerAdapter.java4
-rw-r--r--photoviewer/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java23
-rw-r--r--photoviewer/src/com/android/ex/photo/fragments/PhotoViewFragment.java24
-rw-r--r--photoviewer/src/com/android/ex/photo/provider/PhotoContract.java5
-rw-r--r--photoviewer/src/com/android/ex/photo/util/ImageUtils.java362
-rw-r--r--photoviewer/src/com/android/ex/photo/views/PhotoView.java16
11 files changed, 317 insertions, 204 deletions
diff --git a/photoviewer/.gitignore b/photoviewer/.gitignore
new file mode 100644
index 0000000..ff7ef7d
--- /dev/null
+++ b/photoviewer/.gitignore
@@ -0,0 +1,8 @@
+*~
+*.bak
+*.class
+bin/
+gen/
+*.properties
+.classpath
+.project
diff --git a/photoviewer/AndroidManifest.xml b/photoviewer/AndroidManifest.xml
index 485e044..e4e9101 100644
--- a/photoviewer/AndroidManifest.xml
+++ b/photoviewer/AndroidManifest.xml
@@ -18,4 +18,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.ex.photo"
android:versionCode="1">
+
+ <uses-sdk
+ android:minSdkVersion="11"/>
</manifest> \ No newline at end of file
diff --git a/photoviewer/src/com/android/ex/photo/Intents.java b/photoviewer/src/com/android/ex/photo/Intents.java
index 0e64730..e1e77d3 100644
--- a/photoviewer/src/com/android/ex/photo/Intents.java
+++ b/photoviewer/src/com/android/ex/photo/Intents.java
@@ -34,6 +34,7 @@ public class Intents {
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";
+ public static final String EXTRA_MAX_INITIAL_SCALE = "max_scale";
/**
* Gets a photo view intent builder to display the photos from phone activity.
@@ -75,6 +76,8 @@ public class Intents {
private String[] mProjection;
/** The URI of a thumbnail of the photo to display */
private String mThumbnailUri;
+ /** The maximum scale to display images at before */
+ private Float mMaxInitialScale;
private PhotoViewIntentBuilder(Context context, Class<?> cls) {
mIntent = new Intent(context, cls);
@@ -116,6 +119,14 @@ public class Intents {
return this;
}
+ /**
+ * Sets the maximum scale which an image is initially displayed at
+ */
+ public PhotoViewIntentBuilder setMaxInitialScale(float maxScale) {
+ mMaxInitialScale = maxScale;
+ return this;
+ }
+
/** Build the intent */
public Intent build() {
mIntent.setAction(Intent.ACTION_VIEW);
@@ -142,6 +153,10 @@ public class Intents {
mIntent.putExtra(EXTRA_THUMBNAIL_URI, mThumbnailUri);
}
+ if (mMaxInitialScale != null) {
+ mIntent.putExtra(EXTRA_MAX_INITIAL_SCALE, mMaxInitialScale);
+ }
+
return mIntent;
}
}
diff --git a/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java b/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java
index 5231ca1..5e45f5e 100644
--- a/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java
+++ b/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java
@@ -31,6 +31,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.view.MenuItem;
import android.view.View;
import com.android.ex.photo.PhotoViewPager.InterceptType;
@@ -133,6 +134,8 @@ public class PhotoViewActivity extends Activity implements
private boolean mRestartLoader;
/** Whether or not this activity is paused */
private boolean mIsPaused = true;
+ /** The maximum scale factor applied to images when they are initially displayed */
+ private float mMaxInitialScale;
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..."
@@ -176,12 +179,16 @@ public class PhotoViewActivity extends Activity implements
if (mIntent.hasExtra(Intents.EXTRA_PHOTO_INDEX) && currentItem < 0) {
currentItem = mIntent.getIntExtra(Intents.EXTRA_PHOTO_INDEX, -1);
}
+
+ // Set the max initial scale, defaulting to 1x
+ mMaxInitialScale = mIntent.getFloatExtra(Intents.EXTRA_MAX_INITIAL_SCALE, 1.0f);
+
mPhotoIndex = currentItem;
setContentView(R.layout.photo_activity_view);
// Create the adapter and add the view pager
- mAdapter = new PhotoPagerAdapter(this, getFragmentManager(), null);
+ mAdapter = new PhotoPagerAdapter(this, getFragmentManager(), null, mMaxInitialScale);
mViewPager = (PhotoViewPager) findViewById(R.id.photo_view_pager);
mViewPager.setAdapter(mAdapter);
@@ -236,6 +243,16 @@ public class PhotoViewActivity extends Activity implements
outState.putBoolean(STATE_FULLSCREEN_KEY, mFullScreen);
}
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
public void addScreenListener(OnScreenListener listener) {
mScreenListeners.add(listener);
}
@@ -306,6 +323,9 @@ public class PhotoViewActivity extends Activity implements
}
mIsEmpty = false;
+ mAdapter.swapCursor(data);
+ notifyCursorListeners(data);
+
// set the selected photo
int itemIndex = mPhotoIndex;
@@ -314,9 +334,6 @@ public class PhotoViewActivity extends Activity implements
itemIndex = 0;
}
- mAdapter.swapCursor(data);
- notifyCursorListeners(data);
-
mViewPager.setCurrentItem(itemIndex, false);
setViewActivated();
}
@@ -398,7 +415,7 @@ public class PhotoViewActivity extends Activity implements
/**
* Updates the title bar according to the value of {@link #mFullScreen}.
*/
- private void setFullScreen(boolean fullScreen, boolean setDelayedRunnable) {
+ protected void setFullScreen(boolean fullScreen, boolean setDelayedRunnable) {
final boolean fullScreenChanged = (fullScreen != mFullScreen);
mFullScreen = fullScreen;
@@ -428,7 +445,7 @@ public class PhotoViewActivity extends Activity implements
mHandler.removeCallbacks(mActionBarHideRunnable);
}
- private void setLightsOutMode(boolean enabled) {
+ protected void setLightsOutMode(boolean enabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
int flags = enabled
? View.SYSTEM_UI_FLAG_LOW_PROFILE
@@ -457,7 +474,7 @@ public class PhotoViewActivity extends Activity implements
private Runnable mActionBarHideRunnable = new Runnable() {
@Override
public void run() {
- PhotoViewActivity.this.setLightsOutMode(true);
+ setFullScreen(true, true);
}
};
@@ -532,4 +549,12 @@ public class PhotoViewActivity extends Activity implements
postActionBarHideRunnableWithDelay();
}
}
+
+ protected boolean isFullScreen() {
+ return mFullScreen;
+ }
+
+ protected void setPhotoIndex(int index) {
+ mPhotoIndex = index;
+ }
}
diff --git a/photoviewer/src/com/android/ex/photo/adapters/BaseCursorPagerAdapter.java b/photoviewer/src/com/android/ex/photo/adapters/BaseCursorPagerAdapter.java
index 848d79a..ae2c92e 100644
--- a/photoviewer/src/com/android/ex/photo/adapters/BaseCursorPagerAdapter.java
+++ b/photoviewer/src/com/android/ex/photo/adapters/BaseCursorPagerAdapter.java
@@ -38,13 +38,12 @@ 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>();
+ private final HashMap<Object, Integer> mObjectRowMap = new HashMap<Object, Integer>();
/**
* Constructor that always enables auto-requery.
@@ -67,9 +66,11 @@ public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
*/
public abstract Fragment getItem(Context context, Cursor cursor, int position);
+ // TODO: This shouldn't just return null - maybe it needs to wait for a cursor to be supplied?
+ // See b/7103023
@Override
public Fragment getItem(int position) {
- if (mDataValid && moveCursorTo(position)) {
+ if (mCursor != null && moveCursorTo(position)) {
return getItem(mContext, mCursor, position);
}
return null;
@@ -77,7 +78,7 @@ public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
@Override
public int getCount() {
- if (mDataValid && mCursor != null) {
+ if (mCursor != null) {
return mCursor.getCount();
} else {
return 0;
@@ -86,7 +87,7 @@ public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
@Override
public Object instantiateItem(View container, int position) {
- if (!mDataValid) {
+ if (mCursor == null) {
throw new IllegalStateException("this should only be called when the cursor is valid");
}
@@ -127,7 +128,7 @@ public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
* @return true if data is valid
*/
public boolean isDataValid() {
- return mDataValid;
+ return mCursor != null;
}
/**
@@ -141,7 +142,7 @@ public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
* Returns the data item associated with the specified position in the data set.
*/
public Object getDataItem(int position) {
- if (mDataValid && moveCursorTo(position)) {
+ if (mCursor != null && moveCursorTo(position)) {
return mCursor;
} else {
return null;
@@ -152,7 +153,7 @@ public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
* Returns the row id associated with the specified position in the list.
*/
public long getItemId(int position) {
- if (mDataValid && moveCursorTo(position)) {
+ if (mCursor != null && moveCursorTo(position)) {
return mCursor.getString(mRowIDColumn).hashCode();
} else {
return 0;
@@ -180,10 +181,8 @@ public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
mCursor = newCursor;
if (newCursor != null) {
mRowIDColumn = newCursor.getColumnIndex(PhotoContract.PhotoViewColumns.URI);
- mDataValid = true;
} else {
mRowIDColumn = -1;
- mDataValid = false;
}
setItemPosition();
@@ -231,7 +230,6 @@ public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
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;
@@ -242,7 +240,7 @@ public abstract class BaseCursorPagerAdapter extends BaseFragmentPagerAdapter {
* row id to cursor position.
*/
private void setItemPosition() {
- if (!mDataValid || mCursor == null || mCursor.isClosed()) {
+ if (mCursor == null || mCursor.isClosed()) {
mItemPosition = null;
return;
}
diff --git a/photoviewer/src/com/android/ex/photo/adapters/BaseFragmentPagerAdapter.java b/photoviewer/src/com/android/ex/photo/adapters/BaseFragmentPagerAdapter.java
index 9c24575..2065b2a 100644
--- a/photoviewer/src/com/android/ex/photo/adapters/BaseFragmentPagerAdapter.java
+++ b/photoviewer/src/com/android/ex/photo/adapters/BaseFragmentPagerAdapter.java
@@ -86,6 +86,10 @@ public abstract class BaseFragmentPagerAdapter extends PagerAdapter {
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
+ if(fragment == null) {
+ if (DEBUG) Log.e(TAG, "NPE workaround for getItem(). See b/7103023");
+ return null;
+ }
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), position));
diff --git a/photoviewer/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java b/photoviewer/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java
index bf75ecb..4536432 100644
--- a/photoviewer/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java
+++ b/photoviewer/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java
@@ -33,24 +33,38 @@ import com.android.ex.photo.provider.PhotoContract;
public class PhotoPagerAdapter extends BaseCursorPagerAdapter {
private int mContentUriIndex;
private int mThumbnailUriIndex;
+ private int mLoadingIndex;
+ private final float mMaxScale;
- public PhotoPagerAdapter(Context context, FragmentManager fm, Cursor c) {
+ public PhotoPagerAdapter(Context context, FragmentManager fm, Cursor c, float maxScale) {
super(context, fm, c);
+ mMaxScale = maxScale;
}
@Override
public Fragment getItem(Context context, Cursor cursor, int position) {
final String photoUri = cursor.getString(mContentUriIndex);
final String thumbnailUri = cursor.getString(mThumbnailUriIndex);
+ boolean loading;
+ if(mLoadingIndex != -1) {
+ loading = Boolean.valueOf(cursor.getString(mLoadingIndex));
+ } else {
+ loading = false;
+ }
+ boolean onlyShowSpinner = false;
+ if(photoUri == null && loading) {
+ onlyShowSpinner = true;
+ }
// create new PhotoViewFragment
final PhotoViewIntentBuilder builder =
Intents.newPhotoViewFragmentIntentBuilder(mContext);
builder
.setResolvedPhotoUri(photoUri)
- .setThumbnailUri(thumbnailUri);
+ .setThumbnailUri(thumbnailUri)
+ .setMaxInitialScale(mMaxScale);
- return new PhotoViewFragment(builder.build(), position, this);
+ return new PhotoViewFragment(builder.build(), position, this, onlyShowSpinner);
}
@Override
@@ -60,9 +74,12 @@ public class PhotoPagerAdapter extends BaseCursorPagerAdapter {
newCursor.getColumnIndex(PhotoContract.PhotoViewColumns.CONTENT_URI);
mThumbnailUriIndex =
newCursor.getColumnIndex(PhotoContract.PhotoViewColumns.THUMBNAIL_URI);
+ mLoadingIndex =
+ newCursor.getColumnIndex(PhotoContract.PhotoViewColumns.LOADING_INDICATOR);
} else {
mContentUriIndex = -1;
mThumbnailUriIndex = -1;
+ mLoadingIndex = -1;
}
return super.swapCursor(newCursor);
diff --git a/photoviewer/src/com/android/ex/photo/fragments/PhotoViewFragment.java b/photoviewer/src/com/android/ex/photo/fragments/PhotoViewFragment.java
index 429c403..a721b26 100644
--- a/photoviewer/src/com/android/ex/photo/fragments/PhotoViewFragment.java
+++ b/photoviewer/src/com/android/ex/photo/fragments/PhotoViewFragment.java
@@ -106,6 +106,9 @@ public class PhotoViewFragment extends Fragment implements
/** Whether or not the fragment should make the photo full-screen */
private boolean mFullScreen;
+ /** Whether or not this fragment will only show the loading spinner */
+ private final boolean mOnlyShowSpinner;
+
/** Whether or not the progress bar is showing valid information about the progress stated */
private boolean mProgressBarNeeded = true;
@@ -113,13 +116,16 @@ public class PhotoViewFragment extends Fragment implements
public PhotoViewFragment() {
mPosition = -1;
+ mOnlyShowSpinner = false;
mProgressBarNeeded = true;
}
- public PhotoViewFragment(Intent intent, int position, PhotoPagerAdapter adapter) {
+ public PhotoViewFragment(Intent intent, int position, PhotoPagerAdapter adapter,
+ boolean onlyShowSpinner) {
mIntent = intent;
mPosition = position;
mAdapter = adapter;
+ mOnlyShowSpinner = onlyShowSpinner;
mProgressBarNeeded = true;
}
@@ -184,6 +190,7 @@ public class PhotoViewFragment extends Fragment implements
final View view = inflater.inflate(R.layout.photo_fragment_view, container, false);
mPhotoView = (PhotoView) view.findViewById(R.id.photo_view);
+ mPhotoView.setMaxInitialScale(mIntent.getFloatExtra(Intents.EXTRA_MAX_INITIAL_SCALE, 1));
mPhotoView.setOnClickListener(this);
mPhotoView.setFullScreen(mFullScreen, false);
mPhotoView.enableImageTransforms(true);
@@ -209,7 +216,7 @@ public class PhotoViewFragment extends Fragment implements
mCallback.addScreenListener(this);
mCallback.addCursorListener(this);
- getLoaderManager().initLoader(LOADER_ID_PHOTO, null, this);
+ getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this);
super.onResume();
}
@@ -245,6 +252,9 @@ public class PhotoViewFragment extends Fragment implements
@Override
public Loader<Bitmap> onCreateLoader(int id, Bundle args) {
+ if(mOnlyShowSpinner) {
+ return null;
+ }
switch (id) {
case LOADER_ID_PHOTO:
return new PhotoBitmapLoader(getActivity(), mResolvedPhotoUri);
@@ -276,23 +286,19 @@ public class PhotoViewFragment extends Fragment implements
}
break;
case LOADER_ID_THUMBNAIL:
+ mProgressBarNeeded = false;
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 if (data == null) {
// no preview, show default
mPhotoPreviewImage.setVisibility(View.VISIBLE);
mPhotoPreviewImage.setImageResource(R.drawable.default_image);
-
- mProgressBarNeeded = false;
} else {
- mPhotoPreviewImage.setVisibility(View.VISIBLE);
- mPhotoPreviewImage.setImageBitmap(data);
-
- mProgressBarNeeded = false;
+ bindPhoto(data);
+ getLoaderManager().initLoader(LOADER_ID_PHOTO, null, this);
}
break;
default:
diff --git a/photoviewer/src/com/android/ex/photo/provider/PhotoContract.java b/photoviewer/src/com/android/ex/photo/provider/PhotoContract.java
index 439b68b..8483620 100644
--- a/photoviewer/src/com/android/ex/photo/provider/PhotoContract.java
+++ b/photoviewer/src/com/android/ex/photo/provider/PhotoContract.java
@@ -48,6 +48,11 @@ public final class PhotoContract {
* This string column is the MIME type.
*/
public static final String CONTENT_TYPE = "contentType";
+ /**
+ * This boolean column indicates that a loading indicator should display permenantly
+ * if no image urls are provided.
+ */
+ public static final String LOADING_INDICATOR = "loadingIndicator";
}
diff --git a/photoviewer/src/com/android/ex/photo/util/ImageUtils.java b/photoviewer/src/com/android/ex/photo/util/ImageUtils.java
index d852d65..a9790eb 100644
--- a/photoviewer/src/com/android/ex/photo/util/ImageUtils.java
+++ b/photoviewer/src/com/android/ex/photo/util/ImageUtils.java
@@ -1,175 +1,197 @@
-/*
- * 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.Point;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Build;
-import android.util.Log;
-
-import com.android.ex.photo.PhotoViewActivity;
-
-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
+/*
+ * 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.Point;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.ex.photo.PhotoViewActivity;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * 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) {
+ // TODO: make this method not download the image for both getImageBounds and decodeStream
+ InputStream inputStream = null;
+ try {
+ final BitmapFactory.Options opts = new BitmapFactory.Options();
+ final Point bounds = getImageBounds(resolver, uri);
+
+ inputStream = openInputStream(resolver, 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 (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) {
- try {
- return BitmapFactory.decodeStream(is, outPadding, opts);
- } catch (OutOfMemoryError oome) {
- Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an OOME", oome);
- return null;
- }
- }
-
- /**
- * 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) {
- }
- }
- }
-}
+ } 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) {
+ try {
+ return BitmapFactory.decodeStream(is, outPadding, opts);
+ } catch (OutOfMemoryError oome) {
+ Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an OOME", oome);
+ return null;
+ }
+ }
+
+ /**
+ * 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;
+ String scheme = uri.getScheme();
+ try {
+ opts.inJustDecodeBounds = true;
+ inputStream = openInputStream(resolver, uri);
+ decodeStream(inputStream, null, opts);
+
+ return new Point(opts.outWidth, opts.outHeight);
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ private static InputStream openInputStream(ContentResolver resolver, Uri uri) throws
+ FileNotFoundException {
+ String scheme = uri.getScheme();
+ if("http".equals(scheme) || "https".equals(scheme)) {
+ try {
+ return new URL(uri.toString()).openStream();
+ } catch (MalformedURLException e) {
+ // Fall-back to the previous behaviour, just in case
+ Log.w(TAG, "Could not convert the uri to url: " + uri.toString());
+ return resolver.openInputStream(uri);
+ } catch (IOException e) {
+ Log.w(TAG, "Could not open input stream for uri: " + uri.toString());
+ return null;
+ }
+ }
+ return resolver.openInputStream(uri);
+ }
+}
+
diff --git a/photoviewer/src/com/android/ex/photo/views/PhotoView.java b/photoviewer/src/com/android/ex/photo/views/PhotoView.java
index 8575bb1..838a60d 100644
--- a/photoviewer/src/com/android/ex/photo/views/PhotoView.java
+++ b/photoviewer/src/com/android/ex/photo/views/PhotoView.java
@@ -108,6 +108,8 @@ public class PhotoView extends View implements OnGestureListener,
private Rect mCropRect = new Rect();
/** Actual crop size; may differ from {@link #sCropSize} if the screen is smaller */
private int mCropSize;
+ /** The maximum amount of scaling to apply to images */
+ private float mMaxInitialScaleFactor;
/** Gesture detector */
private GestureDetectorCompat mGestureDetector;
@@ -699,9 +701,13 @@ public class PhotoView extends View implements OnGestureListener,
} else {
mTempDst.set(0, 0, vwidth, vheight);
}
-
- if (dwidth < vwidth && dheight < vheight && !mAllowCrop) {
- mMatrix.setTranslate(vwidth / 2 - dwidth / 2, vheight / 2 - dheight / 2);
+ RectF scaledDestination = new RectF(
+ (vwidth / 2) - (dwidth * mMaxInitialScaleFactor / 2),
+ (vheight / 2) - (dheight * mMaxInitialScaleFactor / 2),
+ (vwidth / 2) + (dwidth * mMaxInitialScaleFactor / 2),
+ (vheight / 2) + (dheight * mMaxInitialScaleFactor / 2));
+ if(mTempDst.contains(scaledDestination)) {
+ mMatrix.setRectToRect(mTempSrc, scaledDestination, Matrix.ScaleToFit.CENTER);
} else {
mMatrix.setRectToRect(mTempSrc, mTempDst, Matrix.ScaleToFit.CENTER);
}
@@ -1285,4 +1291,8 @@ public class PhotoView extends View implements OnGestureListener,
mHeader.post(this);
}
}
+
+ public void setMaxInitialScale(float f) {
+ mMaxInitialScaleFactor = f;
+ }
}