android:paddingLeft="8dp"
android:paddingRight="8dp" >
- <com.android.photos.views.GalleryThumbnailView
+ <GridView
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
- android:drawSelectorOnTop="true" />
+ android:drawSelectorOnTop="true"
+ android:numColumns="auto_fit"
+ android:stretchMode="columnWidth"
+ android:columnWidth="200dip"
+ android:horizontalSpacing="4dip"
+ android:verticalSpacing="4dip"
+ android:padding="4dip" />
<TextView
android:id="@id/android:empty"
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="200dip"
+ android:id="@+id/thumbnail">
+
+</ImageView>
\ No newline at end of file
import android.app.Fragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.Context;
-import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
-import android.net.Uri;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.widget.CursorAdapter;
+import android.widget.GridView;
import android.widget.ImageView;
import com.android.gallery3d.R;
import com.android.photos.data.PhotoSetLoader;
-import com.android.photos.drawables.DataUriThumbnailDrawable;
-import com.android.photos.views.GalleryThumbnailView;
+import com.android.photos.drawables.DrawableFactory;
+import com.android.photos.shims.MediaItemsLoader;
import com.android.photos.views.GalleryThumbnailView.GalleryThumbnailAdapter;
private static final int LOADER_PHOTOSET = 1;
- private GalleryThumbnailView mPhotoSetView;
+ private GridView mPhotoSetView;
private View mEmptyView;
private ThumbnailAdapter mAdapter;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.photo_set, container, false);
- mPhotoSetView = (GalleryThumbnailView) root.findViewById(android.R.id.list);
+ mPhotoSetView = (GridView) root.findViewById(android.R.id.list);
+ // TODO: Remove once UI stabilizes
+ mPhotoSetView.setColumnWidth(MediaItemsLoader.getThumbnailSize());
mEmptyView = root.findViewById(android.R.id.empty);
mEmptyView.setVisibility(View.GONE);
mAdapter = new ThumbnailAdapter(getActivity());
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return new PhotoSetLoader(getActivity());
+ // TODO: Switch to PhotoSetLoader
+ MediaItemsLoader loader = new MediaItemsLoader(getActivity());
+ mAdapter.setDrawableFactory(loader);
+ return loader;
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
- private static class ShowFullScreen implements OnClickListener {
-
- @Override
- public void onClick(View view) {
- String path = (String) view.getTag();
- Intent intent = new Intent(view.getContext(), FullscreenViewer.class);
- intent.setAction(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(path));
- view.getContext().startActivity(intent);
- }
-
- }
-
private static class ThumbnailAdapter extends CursorAdapter implements GalleryThumbnailAdapter {
- private static ShowFullScreen sShowFullscreenClickListener = new ShowFullScreen();
+ private LayoutInflater mInflater;
+ private DrawableFactory<Cursor> mDrawableFactory;
public ThumbnailAdapter(Context context) {
super(context, null, false);
+ mInflater = LayoutInflater.from(context);
+ }
+
+ public void setDrawableFactory(DrawableFactory<Cursor> factory) {
+ mDrawableFactory = factory;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ImageView iv = (ImageView) view;
- DataUriThumbnailDrawable drawable = (DataUriThumbnailDrawable) iv.getDrawable();
- int width = cursor.getInt(PhotoSetLoader.INDEX_WIDTH);
- int height = cursor.getInt(PhotoSetLoader.INDEX_HEIGHT);
- String path = cursor.getString(PhotoSetLoader.INDEX_DATA);
- drawable.setImage(path, width, height);
- iv.setTag(path);
+ Drawable recycle = iv.getDrawable();
+ Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
+ if (recycle != drawable) {
+ iv.setImageDrawable(drawable);
+ }
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- ImageView iv = new ImageView(context);
- DataUriThumbnailDrawable drawable = new DataUriThumbnailDrawable();
- iv.setImageDrawable(drawable);
- int padding = (int) Math.ceil(2 * context.getResources().getDisplayMetrics().density);
- iv.setPadding(padding, padding, padding, padding);
- iv.setOnClickListener(sShowFullscreenClickListener);
- return iv;
+ View view = mInflater.inflate(R.layout.photo_set_item, parent, false);
+ LayoutParams params = view.getLayoutParams();
+ int columnWidth = ((GridView) parent).getColumnWidth();
+ params.height = columnWidth;
+ view.setLayoutParams(params);
+ return view;
}
@Override
import android.content.Context;
import android.content.CursorLoader;
import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.Files.FileColumns;
-public class PhotoSetLoader extends CursorLoader {
+import com.android.photos.drawables.DataUriThumbnailDrawable;
+import com.android.photos.drawables.DrawableFactory;
+
+public class PhotoSetLoader extends CursorLoader implements DrawableFactory<Cursor> {
private static final Uri CONTENT_URI = Files.getContentUri("external");
- private static final String[] PROJECTION = new String[] {
+ public static final String[] PROJECTION = new String[] {
FileColumns._ID,
FileColumns.DATA,
FileColumns.WIDTH,
super.onReset();
getContext().getContentResolver().unregisterContentObserver(mGlobalObserver);
}
+
+ @Override
+ public Drawable drawableForItem(Cursor item, Drawable recycle) {
+ DataUriThumbnailDrawable drawable = null;
+ if (recycle == null || !(recycle instanceof DataUriThumbnailDrawable)) {
+ drawable = new DataUriThumbnailDrawable();
+ } else {
+ drawable = (DataUriThumbnailDrawable) recycle;
+ }
+ drawable.setImage(item.getString(INDEX_DATA),
+ item.getInt(INDEX_WIDTH), item.getInt(INDEX_HEIGHT));
+ return drawable;
+ }
}
--- /dev/null
+/*
+ * Copyright (C) 2013 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.photos.drawables;
+
+import android.graphics.drawable.Drawable;
+
+
+public interface DrawableFactory<T> {
+ Drawable drawableForItem(T item, Drawable recycle);
+}
--- /dev/null
+package com.android.photos.shims;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.ui.BitmapLoader;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.ThreadPool;
+import com.android.photos.drawables.AutoThumbnailDrawable;
+
+
+public class BitmapJobDrawable extends Drawable implements Runnable {
+
+ private ThumbnailLoader mLoader;
+ private MediaItem mItem;
+ private Bitmap mBitmap;
+ private Paint mPaint = new Paint();
+ private Matrix mDrawMatrix = new Matrix();
+
+ public BitmapJobDrawable() {
+ }
+
+ public void setMediaItem(MediaItem item) {
+ if (mLoader != null) {
+ mLoader.cancelLoad();
+ }
+ mItem = item;
+ mBitmap = null;
+ // TODO: Figure out why ThumbnailLoader doesn't like to be re-used
+ mLoader = new ThumbnailLoader(this);
+ mLoader.startLoad();
+ invalidateSelf();
+ }
+
+ @Override
+ public void run() {
+ Bitmap bitmap = mLoader.getBitmap();
+ if (bitmap != null) {
+ mBitmap = bitmap;
+ updateDrawMatrix();
+ }
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ updateDrawMatrix();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ Rect bounds = getBounds();
+ if (mBitmap != null) {
+ canvas.save();
+ canvas.clipRect(bounds);
+ canvas.concat(mDrawMatrix);
+ canvas.drawBitmap(mBitmap, 0, 0, mPaint);
+ canvas.restore();
+ } else {
+ mPaint.setColor(0xFFCCCCCC);
+ canvas.drawRect(bounds, mPaint);
+ }
+ }
+
+ private void updateDrawMatrix() {
+ Rect bounds = getBounds();
+ if (mBitmap == null || bounds.isEmpty()) {
+ mDrawMatrix.reset();
+ return;
+ }
+
+ float scale;
+ float dx = 0, dy = 0;
+
+ int dwidth = mBitmap.getWidth();
+ int dheight = mBitmap.getHeight();
+ int vwidth = bounds.width();
+ int vheight = bounds.height();
+
+ // Calculates a matrix similar to ScaleType.CENTER_CROP
+ if (dwidth * vheight > vwidth * dheight) {
+ scale = (float) vheight / (float) dheight;
+ dx = (vwidth - dwidth * scale) * 0.5f;
+ } else {
+ scale = (float) vwidth / (float) dwidth;
+ dy = (vheight - dheight * scale) * 0.5f;
+ }
+
+ mDrawMatrix.setScale(scale, scale);
+ mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
+ invalidateSelf();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+ @Override
+ public int getOpacity() {
+ Bitmap bm = mBitmap;
+ return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ?
+ PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ int oldAlpha = mPaint.getAlpha();
+ if (alpha != oldAlpha) {
+ mPaint.setAlpha(alpha);
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ mPaint.setColorFilter(cf);
+ invalidateSelf();
+ }
+
+ private static class ThumbnailLoader extends BitmapLoader {
+ private static final ThreadPool sThreadPool = new ThreadPool(0, 2);
+ private BitmapJobDrawable mParent;
+
+ public ThumbnailLoader(BitmapJobDrawable parent) {
+ mParent = parent;
+ }
+
+ @Override
+ protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
+ return sThreadPool.submit(
+ mParent.mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
+ }
+
+ @Override
+ protected void onLoadComplete(Bitmap bitmap) {
+ mParent.scheduleSelf(mParent, 0);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2013 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.photos.shims;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.drawable.Drawable;
+import android.provider.MediaStore.Files.FileColumns;
+
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.MediaSet.ItemConsumer;
+import com.android.gallery3d.data.MediaSet.SyncListener;
+import com.android.gallery3d.util.Future;
+import com.android.photos.data.PhotoSetLoader;
+import com.android.photos.drawables.DrawableFactory;
+
+import java.util.ArrayList;
+
+/**
+ * Returns all MediaItems in a MediaSet, wrapping them in a cursor to appear
+ * like a PhotoSetLoader
+ */
+public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements DrawableFactory<Cursor> {
+
+ private static final SyncListener sNullListener = new SyncListener() {
+ @Override
+ public void onSyncDone(MediaSet mediaSet, int resultCode) {
+ }
+ };
+
+ private MediaSet mMediaSet;
+ private Future<Integer> mSyncTask = null;
+ private ContentListener mObserver = new ContentListener() {
+ @Override
+ public void onContentDirty() {
+ onContentChanged();
+ }
+ };
+ private ArrayList<MediaItem> mMediaItems = new ArrayList<MediaItem>();
+
+ public MediaItemsLoader(Context context) {
+ super(context);
+ DataManager dm = DataManager.from(context);
+ String path = dm.getTopSetPath(DataManager.INCLUDE_ALL);
+ mMediaSet = dm.getMediaSet(path);
+ }
+
+ public MediaItemsLoader(Context context, String parentPath) {
+ super(context);
+ mMediaSet = DataManager.from(getContext()).getMediaSet(parentPath);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ mMediaSet.addContentListener(mObserver);
+ mSyncTask = mMediaSet.requestSync(sNullListener);
+ forceLoad();
+ }
+
+ @Override
+ protected boolean onCancelLoad() {
+ if (mSyncTask != null) {
+ mSyncTask.cancel();
+ mSyncTask = null;
+ }
+ return super.onCancelLoad();
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ mMediaSet.removeContentListener(mObserver);
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ onStopLoading();
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+ mMediaSet.loadIfDirty();
+ final MatrixCursor cursor = new MatrixCursor(PhotoSetLoader.PROJECTION);
+ final Object[] row = new Object[PhotoSetLoader.PROJECTION.length];
+ mMediaSet.enumerateTotalMediaItems(new ItemConsumer() {
+ @Override
+ public void consume(int index, MediaItem item) {
+ row[PhotoSetLoader.INDEX_ID] = index;
+ row[PhotoSetLoader.INDEX_DATA] = item.getContentUri().toString();
+ row[PhotoSetLoader.INDEX_DATE_ADDED] = item.getDateInMs();
+ row[PhotoSetLoader.INDEX_HEIGHT] = item.getHeight();
+ row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth();
+ row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth();
+ int rawMediaType = item.getMediaType();
+ int mappedMediaType = FileColumns.MEDIA_TYPE_NONE;
+ if (rawMediaType == MediaItem.MEDIA_TYPE_IMAGE) {
+ mappedMediaType = FileColumns.MEDIA_TYPE_IMAGE;
+ } else if (rawMediaType == MediaItem.MEDIA_TYPE_VIDEO) {
+ mappedMediaType = FileColumns.MEDIA_TYPE_VIDEO;
+ }
+ row[PhotoSetLoader.INDEX_MEDIA_TYPE] = mappedMediaType;
+ cursor.addRow(row);
+ mMediaItems.add(item);
+ }
+ });
+ return cursor;
+ }
+
+ @Override
+ public Drawable drawableForItem(Cursor item, Drawable recycle) {
+ BitmapJobDrawable drawable = null;
+ if (recycle == null || !(recycle instanceof BitmapJobDrawable)) {
+ drawable = new BitmapJobDrawable();
+ } else {
+ drawable = (BitmapJobDrawable) recycle;
+ }
+ int index = item.getInt(PhotoSetLoader.INDEX_ID);
+ drawable.setMediaItem(mMediaItems.get(index));
+ return drawable;
+ }
+
+ public static int getThumbnailSize() {
+ return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+ }
+
+}
* limitations under the License.
*/
-package com.android.photos.data;
+package com.android.photos.shims;
import android.content.AsyncTaskLoader;
import android.content.Context;