Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.data;
     18 
     19 import android.net.Uri;
     20 import android.provider.MediaStore;
     21 
     22 import com.android.gallery3d.common.ApiHelper;
     23 
     24 import java.lang.ref.SoftReference;
     25 import java.util.ArrayList;
     26 import java.util.Comparator;
     27 import java.util.NoSuchElementException;
     28 import java.util.SortedMap;
     29 import java.util.TreeMap;
     30 
     31 // MergeAlbum merges items from two or more MediaSets. It uses a Comparator to
     32 // determine the order of items. The items are assumed to be sorted in the input
     33 // media sets (with the same order that the Comparator uses).
     34 //
     35 // This only handles MediaItems, not SubMediaSets.
     36 public class LocalMergeAlbum extends MediaSet implements ContentListener {
     37     @SuppressWarnings("unused")
     38     private static final String TAG = "LocalMergeAlbum";
     39     private static final int PAGE_SIZE = 64;
     40 
     41     private final Comparator<MediaItem> mComparator;
     42     private final MediaSet[] mSources;
     43 
     44     private String mName;
     45     private FetchCache[] mFetcher;
     46     private int mSupportedOperation;
     47     private int mBucketId;
     48 
     49     // mIndex maps global position to the position of each underlying media sets.
     50     private TreeMap<Integer, int[]> mIndex = new TreeMap<Integer, int[]>();
     51 
     52     public LocalMergeAlbum(
     53             Path path, Comparator<MediaItem> comparator, MediaSet[] sources, int bucketId) {
     54         super(path, INVALID_DATA_VERSION);
     55         mComparator = comparator;
     56         mSources = sources;
     57         mName = sources.length == 0 ? "" : sources[0].getName();
     58         mBucketId = bucketId;
     59         for (MediaSet set : mSources) {
     60             set.addContentListener(this);
     61         }
     62         reload();
     63     }
     64 
     65     @Override
     66     public boolean isCameraRoll() {
     67         if (mSources.length == 0) return false;
     68         for(MediaSet set : mSources) {
     69             if (!set.isCameraRoll()) return false;
     70         }
     71         return true;
     72     }
     73 
     74     private void updateData() {
     75         ArrayList<MediaSet> matches = new ArrayList<MediaSet>();
     76         int supported = mSources.length == 0 ? 0 : MediaItem.SUPPORT_ALL;
     77         mFetcher = new FetchCache[mSources.length];
     78         for (int i = 0, n = mSources.length; i < n; ++i) {
     79             mFetcher[i] = new FetchCache(mSources[i]);
     80             supported &= mSources[i].getSupportedOperations();
     81         }
     82         mSupportedOperation = supported;
     83         mIndex.clear();
     84         mIndex.put(0, new int[mSources.length]);
     85         mName = mSources.length == 0 ? "" : mSources[0].getName();
     86     }
     87 
     88     private void invalidateCache() {
     89         for (int i = 0, n = mSources.length; i < n; i++) {
     90             mFetcher[i].invalidate();
     91         }
     92         mIndex.clear();
     93         mIndex.put(0, new int[mSources.length]);
     94     }
     95 
     96     @Override
     97     public Uri getContentUri() {
     98         String bucketId = String.valueOf(mBucketId);
     99         if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) {
    100             return MediaStore.Files.getContentUri("external").buildUpon()
    101                     .appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId)
    102                     .build();
    103         } else {
    104             // We don't have a single URL for a merged image before ICS
    105             // So we used the image's URL as a substitute.
    106             return MediaStore.Images.Media.EXTERNAL_CONTENT_URI.buildUpon()
    107                     .appendQueryParameter(LocalSource.KEY_BUCKET_ID, bucketId)
    108                     .build();
    109         }
    110     }
    111 
    112     @Override
    113     public String getName() {
    114         return mName;
    115     }
    116 
    117     @Override
    118     public int getMediaItemCount() {
    119         return getTotalMediaItemCount();
    120     }
    121 
    122     @Override
    123     public ArrayList<MediaItem> getMediaItem(int start, int count) {
    124 
    125         // First find the nearest mark position <= start.
    126         SortedMap<Integer, int[]> head = mIndex.headMap(start + 1);
    127         int markPos = head.lastKey();
    128         int[] subPos = head.get(markPos).clone();
    129         MediaItem[] slot = new MediaItem[mSources.length];
    130 
    131         int size = mSources.length;
    132 
    133         // fill all slots
    134         for (int i = 0; i < size; i++) {
    135             slot[i] = mFetcher[i].getItem(subPos[i]);
    136         }
    137 
    138         ArrayList<MediaItem> result = new ArrayList<MediaItem>();
    139 
    140         for (int i = markPos; i < start + count; i++) {
    141             int k = -1;  // k points to the best slot up to now.
    142             for (int j = 0; j < size; j++) {
    143                 if (slot[j] != null) {
    144                     if (k == -1 || mComparator.compare(slot[j], slot[k]) < 0) {
    145                         k = j;
    146                     }
    147                 }
    148             }
    149 
    150             // If we don't have anything, all streams are exhausted.
    151             if (k == -1) break;
    152 
    153             // Pick the best slot and refill it.
    154             subPos[k]++;
    155             if (i >= start) {
    156                 result.add(slot[k]);
    157             }
    158             slot[k] = mFetcher[k].getItem(subPos[k]);
    159 
    160             // Periodically leave a mark in the index, so we can come back later.
    161             if ((i + 1) % PAGE_SIZE == 0) {
    162                 mIndex.put(i + 1, subPos.clone());
    163             }
    164         }
    165 
    166         return result;
    167     }
    168 
    169     @Override
    170     public int getTotalMediaItemCount() {
    171         int count = 0;
    172         for (MediaSet set : mSources) {
    173             count += set.getTotalMediaItemCount();
    174         }
    175         return count;
    176     }
    177 
    178     @Override
    179     public long reload() {
    180         boolean changed = false;
    181         for (int i = 0, n = mSources.length; i < n; ++i) {
    182             if (mSources[i].reload() > mDataVersion) changed = true;
    183         }
    184         if (changed) {
    185             mDataVersion = nextVersionNumber();
    186             updateData();
    187             invalidateCache();
    188         }
    189         return mDataVersion;
    190     }
    191 
    192     @Override
    193     public void onContentDirty() {
    194         notifyContentChanged();
    195     }
    196 
    197     @Override
    198     public int getSupportedOperations() {
    199         return mSupportedOperation;
    200     }
    201 
    202     @Override
    203     public void delete() {
    204         for (MediaSet set : mSources) {
    205             set.delete();
    206         }
    207     }
    208 
    209     @Override
    210     public void rotate(int degrees) {
    211         for (MediaSet set : mSources) {
    212             set.rotate(degrees);
    213         }
    214     }
    215 
    216     private static class FetchCache {
    217         private MediaSet mBaseSet;
    218         private SoftReference<ArrayList<MediaItem>> mCacheRef;
    219         private int mStartPos;
    220 
    221         public FetchCache(MediaSet baseSet) {
    222             mBaseSet = baseSet;
    223         }
    224 
    225         public void invalidate() {
    226             mCacheRef = null;
    227         }
    228 
    229         public MediaItem getItem(int index) {
    230             boolean needLoading = false;
    231             ArrayList<MediaItem> cache = null;
    232             if (mCacheRef == null
    233                     || index < mStartPos || index >= mStartPos + PAGE_SIZE) {
    234                 needLoading = true;
    235             } else {
    236                 cache = mCacheRef.get();
    237                 if (cache == null) {
    238                     needLoading = true;
    239                 }
    240             }
    241 
    242             if (needLoading) {
    243                 cache = mBaseSet.getMediaItem(index, PAGE_SIZE);
    244                 mCacheRef = new SoftReference<ArrayList<MediaItem>>(cache);
    245                 mStartPos = index;
    246             }
    247 
    248             if (index < mStartPos || index >= mStartPos + cache.size()) {
    249                 return null;
    250             }
    251 
    252             return cache.get(index - mStartPos);
    253         }
    254     }
    255 
    256     @Override
    257     public boolean isLeafAlbum() {
    258         return true;
    259     }
    260 }
    261