blob: 23ed738ac82e5a4cd0bc39407b8aaad64d7080c2 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.gallery3d.ui;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.Log;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.ui.PositionRepository.Position;
import com.android.gallery3d.util.Future;
import com.android.gallery3d.util.FutureListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Random;
public class MediaSetSlotAdapter implements SlotView.Listener {
private static final String TAG = MediaSetSlotAdapter.class.getSimpleName();
private static final int LENGTH_LIMIT = 180;
private static final double EXPECTED_AREA = LENGTH_LIMIT * LENGTH_LIMIT / 2;
private static final int SLOT_WIDTH = 220;
private static final int SLOT_HEIGHT = 200;
private static final int MARGIN_TO_SLOTSIDE = 10;
private static final int CACHE_CAPACITY = 32;
private static final int INDEX_NONE = -1;
private final MediaSet mRootSet;
private final Texture mWaitLoadingTexture;
private final Map<Integer, MyDisplayItems> mItemsetMap =
new HashMap<Integer, MyDisplayItems>(CACHE_CAPACITY);
private final LinkedHashSet<Integer> mLruSlot =
new LinkedHashSet<Integer>(CACHE_CAPACITY);
private final SlotView mSlotView;
private final SelectionManager mSelectionManager;
private boolean mContentInvalidated = false;
private int mInvalidateIndex = INDEX_NONE;
private int mVisibleStart;
private int mVisibleEnd;
public MediaSetSlotAdapter(Context context,
MediaSet rootSet, SlotView slotView, SelectionManager manager) {
mRootSet = rootSet;
mSelectionManager = manager;
ColorTexture gray = new ColorTexture(Color.GRAY);
gray.setSize(64, 48);
mWaitLoadingTexture = gray;
mSlotView = slotView;
mSlotView.setSlotSize(SLOT_WIDTH, SLOT_HEIGHT);
rootSet.setContentListener(new MyContentListener());
}
private void putSlot(int slotIndex) {
// Get displayItems from mItemsetMap or create them from MediaSet.
MyDisplayItems displayItems = mItemsetMap.get(slotIndex);
if (displayItems == null
|| mContentInvalidated || mInvalidateIndex == slotIndex) {
displayItems = createDisplayItems(slotIndex);
}
// Reclaim the slot
mLruSlot.remove(slotIndex);
Rect rect = mSlotView.getSlotRect(slotIndex);
displayItems.putSlot(mSlotView,
(rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2,
rect.left + MARGIN_TO_SLOTSIDE, rect.right - MARGIN_TO_SLOTSIDE,
slotIndex);
}
private MyDisplayItems createDisplayItems(int slotIndex) {
MediaSet set = mRootSet.getSubMediaSet(slotIndex);
set.setContentListener(new SlotContentListener(slotIndex));
MediaItem[] items = set.getCoverMediaItems();
MyDisplayItems displayItems = new MyDisplayItems(mSelectionManager, items.length);
addSlotToCache(slotIndex, displayItems);
SelectionDrawer drawer = mSelectionManager.getSelectionDrawer();
for (int i = 0; i < items.length; ++i) {
items[i].requestImage(MediaItem.TYPE_MICROTHUMBNAIL,
new MyMediaItemListener(slotIndex, i));
int itemIndex = i == 0 ? slotIndex : INDEX_NONE;
MyDisplayItem item = new MyDisplayItem(
itemIndex, mWaitLoadingTexture, drawer);
displayItems.setDisplayItem(i, item);
}
return displayItems;
}
private void addSlotToCache(int slotIndex, MyDisplayItems displayItems) {
mItemsetMap.put(slotIndex, displayItems);
// Remove an itemset if the size of mItemsetMap is no less than
// INITIAL_CACHE_CAPACITY and there exists a slot in mLruSlot.
Iterator<Integer> iter = mLruSlot.iterator();
for (int i = mItemsetMap.size() - CACHE_CAPACITY;
i >= 0 && iter.hasNext(); --i) {
mItemsetMap.remove(iter.next());
iter.remove();
}
}
public int size() {
return mRootSet.getSubMediaSetCount();
}
private class MyMediaItemListener implements FutureListener<Bitmap> {
private final int mSlotIndex;
private final int mItemIndex;
public MyMediaItemListener(int slotIndex, int itemIndex) {
mSlotIndex = slotIndex;
mItemIndex = itemIndex;
}
public void onFutureDone(Future<? extends Bitmap> future) {
try {
MyDisplayItems items = mItemsetMap.get(mSlotIndex);
items.updateContent(mItemIndex, new BitmapTexture(future.get()));
mSlotView.invalidate();
} catch (Exception e) {
Log.v(TAG, "cannot get image", e);
}
}
}
private class MyDisplayItem extends DisplayItem {
private int mIndex;
private Texture mContent;
private final SelectionDrawer mDrawer;
public MyDisplayItem(int index, Texture content, SelectionDrawer drawer) {
mIndex = index;
mDrawer = drawer;
updateContent(content);
}
public void updateContent(Texture content) {
mContent = content;
Rect p = mDrawer.getFramePadding();
int width = mContent.getWidth();
int height = mContent.getHeight();
float scale = (float) Math.sqrt(EXPECTED_AREA / (width * height));
width = (int) (width * scale + 0.5f);
height = (int) (height * scale + 0.5f);
int widthLimit = LENGTH_LIMIT - p.left - p.right;
int heightLimit = LENGTH_LIMIT - p.top - p.bottom;
if (width > widthLimit || height > heightLimit) {
if (width * heightLimit > height * widthLimit) {
height = height * widthLimit / width;
width = widthLimit;
} else {
width = width * heightLimit / height;
height = heightLimit;
}
}
setSize(width + p.left + p.right, height + p.top + p.bottom);
}
@Override
public void render(GLCanvas canvas) {
boolean topItem = mIndex != INDEX_NONE;
boolean checked = mSelectionManager.isSlotSelected(mIndex);
mDrawer.draw(canvas, mContent, mWidth, mHeight, checked, topItem);
}
@Override
public long getIdentity() {
// TODO: should use item's id
return System.identityHashCode(this);
}
}
private static class MyDisplayItems {
MyDisplayItem[] mDisplayItems;
SelectionManager mSelectionManager;
Random mRandom = new Random();
public MyDisplayItems(SelectionManager manager, int size) {
mSelectionManager = manager;
mDisplayItems = new MyDisplayItem[size];
}
public void setDisplayItem(int itemIndex, MyDisplayItem displayItem) {
mDisplayItems[itemIndex] = displayItem;
}
public void updateContent(int itemIndex, Texture texture) {
mDisplayItems[itemIndex].updateContent(texture);
}
public void putSlot(
SlotView panel, int x, int y, int l, int r, int slotIndex) {
// Put the cover items in reverse order, so that the first item is on
// top of the rest.
for (int i = mDisplayItems.length -1; i > 0; --i) {
int dx = mRandom.nextInt(11) - 5;
int itemX = (i & 0x01) == 0
? l + dx + mDisplayItems[i].getWidth() / 2
: r + dx - mDisplayItems[i].getWidth() / 2;
int dy = mRandom.nextInt(11) - 10;
int theta = mRandom.nextInt(31) - 15;
Position position = new Position();
position.set(itemX, y + dy, 0, theta, 1f);
panel.putDisplayItem(position, mDisplayItems[i]);
}
if (mDisplayItems.length > 0) {
Position position = new Position();
position.set(x, y, 0, 0, 1f);
panel.putDisplayItem(position, mDisplayItems[0]);
}
}
public void removeDisplayItems(SlotView panel) {
for (MyDisplayItem item : mDisplayItems) {
panel.removeDisplayItem(item);
}
}
}
private void freeSlot(int index) {
MyDisplayItems displayItems;
if (mContentInvalidated) {
displayItems = mItemsetMap.remove(index);
} else {
displayItems = mItemsetMap.get(index);
mLruSlot.add(index);
}
displayItems.removeDisplayItems(mSlotView);
}
private void onContentChanged() {
updateVisibleRange(0, 0);
mItemsetMap.clear();
mLruSlot.clear();
mSlotView.setSlotCount(mRootSet.getSubMediaSetCount());
updateVisibleRange(mSlotView.getVisibleStart(), mSlotView.getVisibleEnd());
}
private class SlotContentListener implements MediaSet.MediaSetListener {
private final int mSlotIndex;
public SlotContentListener(int slotIndex) {
mSlotIndex = slotIndex;
}
public void onContentChanged() {
// Update the corresponding itemset to the slot based on whether
// the slot is visible or in mLruSlot.
// Remove the original corresponding itemset from cache if the
// slot was already in mLruSlot. That is the itemset was invisible
// and freed before.
if (mLruSlot.remove(mSlotIndex)) {
mItemsetMap.remove(mSlotIndex);
} else {
// Refresh the corresponding items in the slot if the slot is
// visible. Note that only visible slots are refreshed in
// mSlotView.notifySlotInvalidate(mSlotIndex).
freeSlot(mSlotIndex);
mItemsetMap.remove(mSlotIndex);
putSlot(mSlotIndex);
mSlotView.invalidate();
}
}
}
private class MyContentListener implements MediaSet.MediaSetListener {
public void onContentChanged() {
MediaSetSlotAdapter.this.onContentChanged();
}
}
public void updateVisibleRange(int start, int end) {
if (start == mVisibleStart && end == mVisibleEnd) return;
if (start >= mVisibleEnd || mVisibleStart >= end) {
for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
freeSlot(i);
}
for (int i = start; i < end; ++i) {
putSlot(i);
}
} else {
for (int i = mVisibleStart; i < start; ++i) {
freeSlot(i);
}
for (int i = end, n = mVisibleEnd; i < n; ++i) {
freeSlot(i);
}
for (int i = start, n = mVisibleStart; i < n; ++i) {
putSlot(i);
}
for (int i = mVisibleEnd; i < end; ++i) {
putSlot(i);
}
}
mVisibleEnd = end;
mVisibleStart = start;
}
public void onLayoutChanged(int width, int height) {
updateVisibleRange(0, 0);
updateVisibleRange(mSlotView.getVisibleStart(), mSlotView.getVisibleEnd());
}
public void onScrollPositionChanged(int position) {
updateVisibleRange(mSlotView.getVisibleStart(), mSlotView.getVisibleEnd());
}
}