Home | History | Annotate | Download | only in phototable
      1 /*
      2  * Copyright (C) 2012 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 package com.android.dreams.phototable;
     17 
     18 import android.content.Context;
     19 import android.content.SharedPreferences;
     20 import android.database.Cursor;
     21 import android.net.Uri;
     22 import android.provider.MediaStore;
     23 
     24 import java.io.FileInputStream;
     25 import java.io.InputStream;
     26 import java.util.Collection;
     27 import java.util.HashMap;
     28 import java.util.LinkedList;
     29 import java.util.Set;
     30 
     31 /**
     32  * Loads images from the local store.
     33  */
     34 public class LocalSource extends CursorPhotoSource {
     35     private static final String TAG = "PhotoTable.LocalSource";
     36 
     37     private final String mUnknownAlbumName;
     38     private final String mLocalSourceName;
     39     private Set<String> mFoundAlbumIds;
     40     private int mLastPosition;
     41 
     42     public LocalSource(Context context, SharedPreferences settings) {
     43         super(context, settings);
     44         mLocalSourceName = mResources.getString(R.string.local_source_name, "Photos on Device");
     45         mUnknownAlbumName = mResources.getString(R.string.unknown_album_name, "Unknown");
     46         mSourceName = TAG;
     47         mLastPosition = INVALID;
     48         fillQueue();
     49     }
     50 
     51     private Set<String> getFoundAlbums() {
     52         if (mFoundAlbumIds == null) {
     53             findAlbums();
     54         }
     55         return mFoundAlbumIds;
     56     }
     57 
     58     @Override
     59     public Collection<AlbumData> findAlbums() {
     60         log(TAG, "finding albums");
     61         HashMap<String, AlbumData> foundAlbums = new HashMap<String, AlbumData>();
     62         findAlbums(false, foundAlbums);
     63         findAlbums(true, foundAlbums);
     64 
     65         log(TAG, "found " + foundAlbums.size() + " items.");
     66         mFoundAlbumIds = foundAlbums.keySet();
     67         return foundAlbums.values();
     68     }
     69 
     70     public void findAlbums(boolean internal, HashMap<String, AlbumData> foundAlbums) {
     71         Uri uri = internal ? MediaStore.Images.Media.INTERNAL_CONTENT_URI
     72             : MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
     73         String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.BUCKET_ID,
     74                 MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.DATE_TAKEN};
     75         // This is a horrible hack that closes the where clause and injects a grouping clause.
     76         Cursor cursor = mResolver.query(uri, projection, null, null, null);
     77         if (cursor != null) {
     78             cursor.moveToPosition(-1);
     79 
     80             int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
     81             int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID);
     82             int nameIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
     83             int updatedIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN);
     84 
     85             if (bucketIndex < 0) {
     86                 log(TAG, "can't find the ID column!");
     87             } else {
     88                 while (cursor.moveToNext()) {
     89                     String id = constructId(internal, cursor.getString(bucketIndex));
     90                     AlbumData data = foundAlbums.get(id);
     91                     if (foundAlbums.get(id) == null) {
     92                         data = new AlbumData();
     93                         data.id = id;
     94                         data.account = mLocalSourceName;
     95 
     96                         if (dataIndex >= 0) {
     97                             data.thumbnailUrl = cursor.getString(dataIndex);
     98                         }
     99 
    100                         if (nameIndex >= 0) {
    101                             data.title = cursor.getString(nameIndex);
    102                         } else {
    103                             data.title = mUnknownAlbumName;
    104                         }
    105 
    106                         log(TAG, data.title + " found");
    107                         foundAlbums.put(id, data);
    108                     }
    109                     if (updatedIndex >= 0) {
    110                         long updated = cursor.getLong(updatedIndex);
    111                         data.updated = (data.updated == 0 ?
    112                                         updated :
    113                                         Math.min(data.updated, updated));
    114                     }
    115                 }
    116             }
    117             cursor.close();
    118         }
    119     }
    120 
    121     public static String constructId(boolean internal, String bucketId) {
    122         return TAG + ":" + bucketId + (internal ? ":i" : "");
    123     }
    124 
    125     @Override
    126     protected void openCursor(ImageData data) {
    127         log(TAG, "opening single album");
    128 
    129         String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION,
    130                 MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME};
    131         String selection = MediaStore.Images.Media.BUCKET_ID + " = '" + data.albumId + "'";
    132 
    133         data.cursor = mResolver.query(data.uri, projection, selection, null, null);
    134     }
    135 
    136     @Override
    137     protected void findPosition(ImageData data) {
    138         if (data.position == -1) {
    139             if (data.cursor == null) {
    140                 openCursor(data);
    141             }
    142             if (data.cursor != null) {
    143                 int dataIndex = data.cursor.getColumnIndex(MediaStore.Images.Media.DATA);
    144                 data.cursor.moveToPosition(-1);
    145                 while (data.position == -1 && data.cursor.moveToNext()) {
    146                     String url = data.cursor.getString(dataIndex);
    147                     if (url != null && url.equals(data.url)) {
    148                         data.position = data.cursor.getPosition();
    149                     }
    150                 }
    151                 if (data.position == -1) {
    152                     // oops!  The image isn't in this album. How did we get here?
    153                     data.position = INVALID;
    154                 }
    155             }
    156         }
    157     }
    158 
    159     @Override
    160     protected ImageData unpackImageData(Cursor cursor, ImageData data) {
    161         if (data == null) {
    162             data = new ImageData();
    163         }
    164         int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
    165         int orientationIndex = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION);
    166         int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID);
    167 
    168         data.url = cursor.getString(dataIndex);
    169         data.albumId = cursor.getString(bucketIndex);
    170         data.position = UNINITIALIZED;
    171         data.cursor = null;
    172         data.orientation = cursor.getInt(orientationIndex);
    173 
    174         return data;
    175     }
    176 
    177     @Override
    178     protected Collection<ImageData> findImages(int howMany) {
    179         log(TAG, "finding images");
    180         LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
    181         boolean internalFirst = mRNG.nextInt(2) == 0;  // filp a coin to be fair
    182         findImages(internalFirst, howMany, foundImages);
    183         findImages(!internalFirst, howMany - foundImages.size(), foundImages);
    184         log(TAG, "found " + foundImages.size() + " items.");
    185         return foundImages;
    186     }
    187 
    188     protected void findImages(boolean internal, int howMany, LinkedList<ImageData> foundImages ) {
    189         Uri uri = internal ? MediaStore.Images.Media.INTERNAL_CONTENT_URI
    190             : MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    191         String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION,
    192                 MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME};
    193         String selection = "";
    194         for (String id : getFoundAlbums()) {
    195             if (isInternalId(id) == internal && mSettings.isAlbumEnabled(id)) {
    196                 String[] parts = id.split(":");
    197                 if (parts.length > 1) {
    198                     if (selection.length() > 0) {
    199                         selection += " OR ";
    200                     }
    201                     selection += MediaStore.Images.Media.BUCKET_ID + " = '" + parts[1] + "'";
    202                 }
    203             }
    204         }
    205         if (selection.isEmpty()) {
    206             return;
    207         }
    208         Cursor cursor = mResolver.query(uri, projection, selection, null, null);
    209         if (cursor != null) {
    210             int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
    211 
    212             if (cursor.getCount() > howMany && mLastPosition == INVALID) {
    213                 mLastPosition = pickRandomStart(cursor.getCount(), howMany);
    214             }
    215             cursor.moveToPosition(mLastPosition);
    216 
    217             if (dataIndex < 0) {
    218                 log(TAG, "can't find the DATA column!");
    219             } else {
    220                 while (foundImages.size() < howMany && cursor.moveToNext()) {
    221                     ImageData data = unpackImageData(cursor, null);
    222                     data.uri = uri;
    223                     foundImages.offer(data);
    224                     mLastPosition = cursor.getPosition();
    225                 }
    226                 if (cursor.isAfterLast()) {
    227                     mLastPosition = -1;
    228                 }
    229                 if (cursor.isBeforeFirst()) {
    230                     mLastPosition = INVALID;
    231                 }
    232             }
    233 
    234             cursor.close();
    235         }
    236     }
    237 
    238     private boolean isInternalId(String id) {
    239         return id.endsWith("i");
    240     }
    241 
    242     @Override
    243     protected InputStream getStream(ImageData data, int longSide) {
    244         FileInputStream fis = null;
    245         try {
    246             log(TAG, "opening:" + data.url);
    247             fis = new FileInputStream(data.url);
    248         } catch (Exception ex) {
    249             log(TAG, ex.toString());
    250             fis = null;
    251         }
    252 
    253         return (InputStream) fis;
    254     }
    255 }
    256 
    257