diff options
Diffstat (limited to 'src/com/android/gallery3d/app/PhotoDataAdapter.java')
-rw-r--r-- | src/com/android/gallery3d/app/PhotoDataAdapter.java | 794 |
1 files changed, 794 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java new file mode 100644 index 000000000..c05c89a0d --- /dev/null +++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java @@ -0,0 +1,794 @@ +/* + * 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.app; + +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.ContentListener; +import com.android.gallery3d.data.DataManager; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.data.MediaObject; +import com.android.gallery3d.data.MediaSet; +import com.android.gallery3d.data.Path; +import com.android.gallery3d.ui.PhotoView; +import com.android.gallery3d.ui.PhotoView.ImageData; +import com.android.gallery3d.ui.SynchronizedHandler; +import com.android.gallery3d.ui.TileImageViewAdapter; +import com.android.gallery3d.util.Future; +import com.android.gallery3d.util.FutureListener; +import com.android.gallery3d.util.ThreadPool; + +import android.graphics.Bitmap; +import android.graphics.BitmapRegionDecoder; +import android.os.Handler; +import android.os.Message; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +public class PhotoDataAdapter implements PhotoPage.Model { + @SuppressWarnings("unused") + private static final String TAG = "PhotoDataAdapter"; + + private static final int MSG_LOAD_START = 1; + private static final int MSG_LOAD_FINISH = 2; + private static final int MSG_RUN_OBJECT = 3; + + private static final int MIN_LOAD_COUNT = 8; + private static final int DATA_CACHE_SIZE = 32; + private static final int IMAGE_CACHE_SIZE = 5; + + private static final int BIT_SCREEN_NAIL = 1; + private static final int BIT_FULL_IMAGE = 2; + + private static final long VERSION_OUT_OF_RANGE = MediaObject.nextVersionNumber(); + + // sImageFetchSeq is the fetching sequence for images. + // We want to fetch the current screennail first (offset = 0), the next + // screennail (offset = +1), then the previous screennail (offset = -1) etc. + // After all the screennail are fetched, we fetch the full images (only some + // of them because of we don't want to use too much memory). + private static ImageFetch[] sImageFetchSeq; + + private static class ImageFetch { + int indexOffset; + int imageBit; + public ImageFetch(int offset, int bit) { + indexOffset = offset; + imageBit = bit; + } + } + + static { + int k = 0; + sImageFetchSeq = new ImageFetch[1 + (IMAGE_CACHE_SIZE - 1) * 2 + 3]; + sImageFetchSeq[k++] = new ImageFetch(0, BIT_SCREEN_NAIL); + + for (int i = 1; i < IMAGE_CACHE_SIZE; ++i) { + sImageFetchSeq[k++] = new ImageFetch(i, BIT_SCREEN_NAIL); + sImageFetchSeq[k++] = new ImageFetch(-i, BIT_SCREEN_NAIL); + } + + sImageFetchSeq[k++] = new ImageFetch(0, BIT_FULL_IMAGE); + sImageFetchSeq[k++] = new ImageFetch(1, BIT_FULL_IMAGE); + sImageFetchSeq[k++] = new ImageFetch(-1, BIT_FULL_IMAGE); + } + + private final TileImageViewAdapter mTileProvider = new TileImageViewAdapter(); + + // PhotoDataAdapter caches MediaItems (data) and ImageEntries (image). + // + // The MediaItems are stored in the mData array, which has DATA_CACHE_SIZE + // entries. The valid index range are [mContentStart, mContentEnd). We keep + // mContentEnd - mContentStart <= DATA_CACHE_SIZE, so we can use + // (i % DATA_CACHE_SIZE) as index to the array. + // + // The valid MediaItem window size (mContentEnd - mContentStart) may be + // smaller than DATA_CACHE_SIZE because we only update the window and reload + // the MediaItems when there are significant changes to the window position + // (>= MIN_LOAD_COUNT). + private final MediaItem mData[] = new MediaItem[DATA_CACHE_SIZE]; + private int mContentStart = 0; + private int mContentEnd = 0; + + /* + * The ImageCache is a version-to-ImageEntry map. It only holds + * the ImageEntries in the range of [mActiveStart, mActiveEnd). + * We also keep mActiveEnd - mActiveStart <= IMAGE_CACHE_SIZE. + * Besides, the [mActiveStart, mActiveEnd) range must be contained + * within the[mContentStart, mContentEnd) range. + */ + private HashMap<Long, ImageEntry> mImageCache = new HashMap<Long, ImageEntry>(); + private int mActiveStart = 0; + private int mActiveEnd = 0; + + // mCurrentIndex is the "center" image the user is viewing. The change of + // mCurrentIndex triggers the data loading and image loading. + private int mCurrentIndex; + + // mChanges keeps the version number (of MediaItem) about the previous, + // current, and next image. If the version number changes, we invalidate + // the model. This is used after a database reload or mCurrentIndex changes. + private final long mChanges[] = new long[3]; + + private final Handler mMainHandler; + private final ThreadPool mThreadPool; + + private final PhotoView mPhotoView; + private final MediaSet mSource; + private ReloadTask mReloadTask; + + private long mSourceVersion = MediaObject.INVALID_DATA_VERSION; + private int mSize = 0; + private Path mItemPath; + private boolean mIsActive; + + public interface DataListener extends LoadingListener { + public void onPhotoChanged(int index, Path item); + } + + private DataListener mDataListener; + + private final SourceListener mSourceListener = new SourceListener(); + + // The path of the current viewing item will be stored in mItemPath. + // If mItemPath is not null, mCurrentIndex is only a hint for where we + // can find the item. If mItemPath is null, then we use the mCurrentIndex to + // find the image being viewed. + public PhotoDataAdapter(GalleryActivity activity, + PhotoView view, MediaSet mediaSet, Path itemPath, int indexHint) { + mSource = Utils.checkNotNull(mediaSet); + mPhotoView = Utils.checkNotNull(view); + mItemPath = Utils.checkNotNull(itemPath); + mCurrentIndex = indexHint; + mThreadPool = activity.getThreadPool(); + + Arrays.fill(mChanges, MediaObject.INVALID_DATA_VERSION); + + mMainHandler = new SynchronizedHandler(activity.getGLRoot()) { + @SuppressWarnings("unchecked") + @Override + public void handleMessage(Message message) { + switch (message.what) { + case MSG_RUN_OBJECT: + ((Runnable) message.obj).run(); + return; + case MSG_LOAD_START: { + if (mDataListener != null) mDataListener.onLoadingStarted(); + return; + } + case MSG_LOAD_FINISH: { + if (mDataListener != null) mDataListener.onLoadingFinished(); + return; + } + default: throw new AssertionError(); + } + } + }; + + updateSlidingWindow(); + } + + private long getVersion(int index) { + if (index < 0 || index >= mSize) return VERSION_OUT_OF_RANGE; + if (index >= mContentStart && index < mContentEnd) { + MediaItem item = mData[index % DATA_CACHE_SIZE]; + if (item != null) return item.getDataVersion(); + } + return MediaObject.INVALID_DATA_VERSION; + } + + private void fireModelInvalidated() { + for (int i = -1; i <= 1; ++i) { + long current = getVersion(mCurrentIndex + i); + long change = mChanges[i + 1]; + if (current != change) { + mPhotoView.notifyImageInvalidated(i); + mChanges[i + 1] = current; + } + } + } + + public void setDataListener(DataListener listener) { + mDataListener = listener; + } + + private void updateScreenNail(long version, Future<Bitmap> future) { + ImageEntry entry = mImageCache.get(version); + if (entry == null || entry.screenNailTask == null) { + Bitmap screenNail = future.get(); + if (screenNail != null) screenNail.recycle(); + return; + } + entry.screenNailTask = null; + entry.screenNail = future.get(); + + if (entry.screenNail == null) { + entry.failToLoad = true; + } else { + for (int i = -1; i <=1; ++i) { + if (version == getVersion(mCurrentIndex + i)) { + if (i == 0) updateTileProvider(entry); + mPhotoView.notifyImageInvalidated(i); + } + } + } + updateImageRequests(); + } + + private void updateFullImage(long version, Future<BitmapRegionDecoder> future) { + ImageEntry entry = mImageCache.get(version); + if (entry == null || entry.fullImageTask == null) { + BitmapRegionDecoder fullImage = future.get(); + if (fullImage != null) fullImage.recycle(); + return; + } + entry.fullImageTask = null; + entry.fullImage = future.get(); + if (entry.fullImage != null) { + if (version == getVersion(mCurrentIndex)) { + updateTileProvider(entry); + mPhotoView.notifyImageInvalidated(0); + } + } + updateImageRequests(); + } + + public void resume() { + mIsActive = true; + mSource.addContentListener(mSourceListener); + updateImageCache(); + updateImageRequests(); + + mReloadTask = new ReloadTask(); + mReloadTask.start(); + + mPhotoView.notifyModelInvalidated(); + } + + public void pause() { + mIsActive = false; + + mReloadTask.terminate(); + mReloadTask = null; + + mSource.removeContentListener(mSourceListener); + + for (ImageEntry entry : mImageCache.values()) { + if (entry.fullImageTask != null) entry.fullImageTask.cancel(); + if (entry.screenNailTask != null) entry.screenNailTask.cancel(); + } + mImageCache.clear(); + mTileProvider.clear(); + } + + private ImageData getImage(int index) { + if (index < 0 || index >= mSize || !mIsActive) return null; + Utils.assertTrue(index >= mActiveStart && index < mActiveEnd); + + ImageEntry entry = mImageCache.get(getVersion(index)); + Bitmap screennail = entry == null ? null : entry.screenNail; + if (screennail != null) { + return new ImageData(screennail, entry.rotation); + } else { + return new ImageData(null, 0); + } + } + + public ImageData getPreviousImage() { + return getImage(mCurrentIndex - 1); + } + + public ImageData getNextImage() { + return getImage(mCurrentIndex + 1); + } + + private void updateCurrentIndex(int index) { + mCurrentIndex = index; + updateSlidingWindow(); + + MediaItem item = mData[index % DATA_CACHE_SIZE]; + mItemPath = item == null ? null : item.getPath(); + + updateImageCache(); + updateImageRequests(); + updateTileProvider(); + mPhotoView.notifyOnNewImage(); + + if (mDataListener != null) { + mDataListener.onPhotoChanged(index, mItemPath); + } + fireModelInvalidated(); + } + + public void next() { + updateCurrentIndex(mCurrentIndex + 1); + } + + public void previous() { + updateCurrentIndex(mCurrentIndex - 1); + } + + public void jumpTo(int index) { + if (mCurrentIndex == index) return; + updateCurrentIndex(index); + } + + public Bitmap getBackupImage() { + return mTileProvider.getBackupImage(); + } + + public int getImageHeight() { + return mTileProvider.getImageHeight(); + } + + public int getImageWidth() { + return mTileProvider.getImageWidth(); + } + + public int getImageRotation() { + ImageEntry entry = mImageCache.get(getVersion(mCurrentIndex)); + return entry == null ? 0 : entry.rotation; + } + + public int getLevelCount() { + return mTileProvider.getLevelCount(); + } + + public Bitmap getTile(int level, int x, int y, int tileSize) { + return mTileProvider.getTile(level, x, y, tileSize); + } + + public boolean isFailedToLoad() { + return mTileProvider.isFailedToLoad(); + } + + public boolean isEmpty() { + return mSize == 0; + } + + public int getCurrentIndex() { + return mCurrentIndex; + } + + public MediaItem getCurrentMediaItem() { + return mData[mCurrentIndex % DATA_CACHE_SIZE]; + } + + public void setCurrentPhoto(Path path, int indexHint) { + if (mItemPath == path) return; + mItemPath = path; + mCurrentIndex = indexHint; + updateSlidingWindow(); + updateImageCache(); + fireModelInvalidated(); + + // We need to reload content if the path doesn't match. + MediaItem item = getCurrentMediaItem(); + if (item != null && item.getPath() != path) { + if (mReloadTask != null) mReloadTask.notifyDirty(); + } + } + + private void updateTileProvider() { + ImageEntry entry = mImageCache.get(getVersion(mCurrentIndex)); + if (entry == null) { // in loading + mTileProvider.clear(); + } else { + updateTileProvider(entry); + } + } + + private void updateTileProvider(ImageEntry entry) { + Bitmap screenNail = entry.screenNail; + BitmapRegionDecoder fullImage = entry.fullImage; + if (screenNail != null) { + if (fullImage != null) { + mTileProvider.setBackupImage(screenNail, + fullImage.getWidth(), fullImage.getHeight()); + mTileProvider.setRegionDecoder(fullImage); + } else { + int width = screenNail.getWidth(); + int height = screenNail.getHeight(); + mTileProvider.setBackupImage(screenNail, width, height); + } + } else { + mTileProvider.clear(); + if (entry.failToLoad) mTileProvider.setFailedToLoad(); + } + } + + private void updateSlidingWindow() { + // 1. Update the image window + int start = Utils.clamp(mCurrentIndex - IMAGE_CACHE_SIZE / 2, + 0, Math.max(0, mSize - IMAGE_CACHE_SIZE)); + int end = Math.min(mSize, start + IMAGE_CACHE_SIZE); + + if (mActiveStart == start && mActiveEnd == end) return; + + mActiveStart = start; + mActiveEnd = end; + + // 2. Update the data window + start = Utils.clamp(mCurrentIndex - DATA_CACHE_SIZE / 2, + 0, Math.max(0, mSize - DATA_CACHE_SIZE)); + end = Math.min(mSize, start + DATA_CACHE_SIZE); + if (mContentStart > mActiveStart || mContentEnd < mActiveEnd + || Math.abs(start - mContentStart) > MIN_LOAD_COUNT) { + for (int i = mContentStart; i < mContentEnd; ++i) { + if (i < start || i >= end) { + mData[i % DATA_CACHE_SIZE] = null; + } + } + mContentStart = start; + mContentEnd = end; + if (mReloadTask != null) mReloadTask.notifyDirty(); + } + } + + private void updateImageRequests() { + if (!mIsActive) return; + + int currentIndex = mCurrentIndex; + MediaItem item = mData[currentIndex % DATA_CACHE_SIZE]; + if (item == null || item.getPath() != mItemPath) { + // current item mismatch - don't request image + return; + } + + // 1. Find the most wanted request and start it (if not already started). + Future<?> task = null; + for (int i = 0; i < sImageFetchSeq.length; i++) { + int offset = sImageFetchSeq[i].indexOffset; + int bit = sImageFetchSeq[i].imageBit; + task = startTaskIfNeeded(currentIndex + offset, bit); + if (task != null) break; + } + + // 2. Cancel everything else. + for (ImageEntry entry : mImageCache.values()) { + if (entry.screenNailTask != null && entry.screenNailTask != task) { + entry.screenNailTask.cancel(); + entry.screenNailTask = null; + entry.requestedBits &= ~BIT_SCREEN_NAIL; + } + if (entry.fullImageTask != null && entry.fullImageTask != task) { + entry.fullImageTask.cancel(); + entry.fullImageTask = null; + entry.requestedBits &= ~BIT_FULL_IMAGE; + } + } + } + + // Returns the task if we started the task or the task is already started. + private Future<?> startTaskIfNeeded(int index, int which) { + if (index < mActiveStart || index >= mActiveEnd) return null; + + ImageEntry entry = mImageCache.get(getVersion(index)); + if (entry == null) return null; + + if (which == BIT_SCREEN_NAIL && entry.screenNailTask != null) { + return entry.screenNailTask; + } else if (which == BIT_FULL_IMAGE && entry.fullImageTask != null) { + return entry.fullImageTask; + } + + MediaItem item = mData[index % DATA_CACHE_SIZE]; + Utils.assertTrue(item != null); + + if (which == BIT_SCREEN_NAIL + && (entry.requestedBits & BIT_SCREEN_NAIL) == 0) { + entry.requestedBits |= BIT_SCREEN_NAIL; + entry.screenNailTask = mThreadPool.submit( + item.requestImage(MediaItem.TYPE_THUMBNAIL), + new ScreenNailListener(item.getDataVersion())); + // request screen nail + return entry.screenNailTask; + } + if (which == BIT_FULL_IMAGE + && (entry.requestedBits & BIT_FULL_IMAGE) == 0 + && (item.getSupportedOperations() + & MediaItem.SUPPORT_FULL_IMAGE) != 0) { + entry.requestedBits |= BIT_FULL_IMAGE; + entry.fullImageTask = mThreadPool.submit( + item.requestLargeImage(), + new FullImageListener(item.getDataVersion())); + // request full image + return entry.fullImageTask; + } + return null; + } + + private void updateImageCache() { + HashSet<Long> toBeRemoved = new HashSet<Long>(mImageCache.keySet()); + for (int i = mActiveStart; i < mActiveEnd; ++i) { + MediaItem item = mData[i % DATA_CACHE_SIZE]; + long version = item == null + ? MediaObject.INVALID_DATA_VERSION + : item.getDataVersion(); + if (version == MediaObject.INVALID_DATA_VERSION) continue; + ImageEntry entry = mImageCache.get(version); + toBeRemoved.remove(version); + if (entry != null) { + if (Math.abs(i - mCurrentIndex) > 1) { + if (entry.fullImageTask != null) { + entry.fullImageTask.cancel(); + entry.fullImageTask = null; + } + entry.fullImage = null; + entry.requestedBits &= ~BIT_FULL_IMAGE; + } + } else { + entry = new ImageEntry(); + entry.rotation = item.getRotation(); + mImageCache.put(version, entry); + } + } + + // Clear the data and requests for ImageEntries outside the new window. + for (Long version : toBeRemoved) { + ImageEntry entry = mImageCache.remove(version); + if (entry.fullImageTask != null) entry.fullImageTask.cancel(); + if (entry.screenNailTask != null) entry.screenNailTask.cancel(); + } + } + + private class FullImageListener + implements Runnable, FutureListener<BitmapRegionDecoder> { + private final long mVersion; + private Future<BitmapRegionDecoder> mFuture; + + public FullImageListener(long version) { + mVersion = version; + } + + public void onFutureDone(Future<BitmapRegionDecoder> future) { + mFuture = future; + mMainHandler.sendMessage( + mMainHandler.obtainMessage(MSG_RUN_OBJECT, this)); + } + + public void run() { + updateFullImage(mVersion, mFuture); + } + } + + private class ScreenNailListener + implements Runnable, FutureListener<Bitmap> { + private final long mVersion; + private Future<Bitmap> mFuture; + + public ScreenNailListener(long version) { + mVersion = version; + } + + public void onFutureDone(Future<Bitmap> future) { + mFuture = future; + mMainHandler.sendMessage( + mMainHandler.obtainMessage(MSG_RUN_OBJECT, this)); + } + + public void run() { + updateScreenNail(mVersion, mFuture); + } + } + + private static class ImageEntry { + public int requestedBits = 0; + public int rotation; + public BitmapRegionDecoder fullImage; + public Bitmap screenNail; + public Future<Bitmap> screenNailTask; + public Future<BitmapRegionDecoder> fullImageTask; + public boolean failToLoad = false; + } + + private class SourceListener implements ContentListener { + public void onContentDirty() { + if (mReloadTask != null) mReloadTask.notifyDirty(); + } + } + + private <T> T executeAndWait(Callable<T> callable) { + FutureTask<T> task = new FutureTask<T>(callable); + mMainHandler.sendMessage( + mMainHandler.obtainMessage(MSG_RUN_OBJECT, task)); + try { + return task.get(); + } catch (InterruptedException e) { + return null; + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + private static class UpdateInfo { + public long version; + public boolean reloadContent; + public Path target; + public int indexHint; + public int contentStart; + public int contentEnd; + + public int size; + public ArrayList<MediaItem> items; + } + + private class GetUpdateInfo implements Callable<UpdateInfo> { + + private boolean needContentReload() { + for (int i = mContentStart, n = mContentEnd; i < n; ++i) { + if (mData[i % DATA_CACHE_SIZE] == null) return true; + } + MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE]; + return current == null || current.getPath() != mItemPath; + } + + @Override + public UpdateInfo call() throws Exception { + UpdateInfo info = new UpdateInfo(); + info.version = mSourceVersion; + info.reloadContent = needContentReload(); + info.target = mItemPath; + info.indexHint = mCurrentIndex; + info.contentStart = mContentStart; + info.contentEnd = mContentEnd; + info.size = mSize; + return info; + } + } + + private class UpdateContent implements Callable<Void> { + UpdateInfo mUpdateInfo; + + public UpdateContent(UpdateInfo updateInfo) { + mUpdateInfo = updateInfo; + } + + @Override + public Void call() throws Exception { + UpdateInfo info = mUpdateInfo; + mSourceVersion = info.version; + + if (info.size != mSize) { + mSize = info.size; + if (mContentEnd > mSize) mContentEnd = mSize; + if (mActiveEnd > mSize) mActiveEnd = mSize; + } + + if (info.indexHint == MediaSet.INDEX_NOT_FOUND) { + // The image has been deleted, clear mItemPath, the + // mCurrentIndex will be updated in the updateCurrentItem(). + mItemPath = null; + updateCurrentItem(); + } else { + mCurrentIndex = info.indexHint; + } + + updateSlidingWindow(); + + if (info.items != null) { + int start = Math.max(info.contentStart, mContentStart); + int end = Math.min(info.contentStart + info.items.size(), mContentEnd); + int dataIndex = start % DATA_CACHE_SIZE; + for (int i = start; i < end; ++i) { + mData[dataIndex] = info.items.get(i - info.contentStart); + if (++dataIndex == DATA_CACHE_SIZE) dataIndex = 0; + } + } + if (mItemPath == null) { + MediaItem current = mData[mCurrentIndex % DATA_CACHE_SIZE]; + mItemPath = current == null ? null : current.getPath(); + } + updateImageCache(); + updateTileProvider(); + updateImageRequests(); + fireModelInvalidated(); + return null; + } + + private void updateCurrentItem() { + if (mSize == 0) return; + if (mCurrentIndex >= mSize) { + mCurrentIndex = mSize - 1; + mPhotoView.notifyOnNewImage(); + mPhotoView.startSlideInAnimation(PhotoView.TRANS_SLIDE_IN_LEFT); + } else { + mPhotoView.notifyOnNewImage(); + mPhotoView.startSlideInAnimation(PhotoView.TRANS_SLIDE_IN_RIGHT); + } + } + } + + private class ReloadTask extends Thread { + private volatile boolean mActive = true; + private volatile boolean mDirty = true; + + private boolean mIsLoading = false; + + private void updateLoading(boolean loading) { + if (mIsLoading == loading) return; + mIsLoading = loading; + mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH); + } + + @Override + public void run() { + while (mActive) { + synchronized (this) { + if (!mDirty && mActive) { + updateLoading(false); + Utils.waitWithoutInterrupt(this); + continue; + } + } + mDirty = false; + UpdateInfo info = executeAndWait(new GetUpdateInfo()); + synchronized (DataManager.LOCK) { + updateLoading(true); + long version = mSource.reload(); + if (info.version != version) { + info.reloadContent = true; + info.size = mSource.getMediaItemCount(); + } + if (!info.reloadContent) continue; + info.items = mSource.getMediaItem(info.contentStart, info.contentEnd); + MediaItem item = findCurrentMediaItem(info); + if (item == null || item.getPath() != info.target) { + info.indexHint = findIndexOfTarget(info); + } + } + executeAndWait(new UpdateContent(info)); + } + } + + public synchronized void notifyDirty() { + mDirty = true; + notifyAll(); + } + + public synchronized void terminate() { + mActive = false; + notifyAll(); + } + + private MediaItem findCurrentMediaItem(UpdateInfo info) { + ArrayList<MediaItem> items = info.items; + int index = info.indexHint - info.contentStart; + return index < 0 || index >= items.size() ? null : items.get(index); + } + + private int findIndexOfTarget(UpdateInfo info) { + if (info.target == null) return info.indexHint; + ArrayList<MediaItem> items = info.items; + + // First, try to find the item in the data just loaded + if (items != null) { + for (int i = 0, n = items.size(); i < n; ++i) { + if (items.get(i).getPath() == info.target) return i + info.contentStart; + } + } + + // Not found, find it in mSource. + return mSource.getIndexOfItem(info.target, info.indexHint); + } + } +} |