Home | History | Annotate | Download | only in gadget
      1 /*
      2  * Copyright (C) 2011 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.gadget;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.Context;
     21 import android.database.ContentObserver;
     22 import android.database.Cursor;
     23 import android.graphics.Bitmap;
     24 import android.net.Uri;
     25 import android.os.Environment;
     26 import android.os.Handler;
     27 import android.provider.MediaStore.Images.Media;
     28 
     29 import com.android.gallery3d.app.GalleryApp;
     30 import com.android.gallery3d.common.Utils;
     31 import com.android.gallery3d.data.ContentListener;
     32 import com.android.gallery3d.data.DataManager;
     33 import com.android.gallery3d.data.MediaItem;
     34 import com.android.gallery3d.data.Path;
     35 import com.android.gallery3d.util.GalleryUtils;
     36 
     37 import java.util.ArrayList;
     38 import java.util.Arrays;
     39 import java.util.HashSet;
     40 import java.util.Random;
     41 
     42 public class LocalPhotoSource implements WidgetSource {
     43 
     44     @SuppressWarnings("unused")
     45     private static final String TAG = "LocalPhotoSource";
     46 
     47     private static final int MAX_PHOTO_COUNT = 128;
     48 
     49     /* Static fields used to query for the correct set of images */
     50     private static final Uri CONTENT_URI = Media.EXTERNAL_CONTENT_URI;
     51     private static final String DATE_TAKEN = Media.DATE_TAKEN;
     52     private static final String[] PROJECTION = {Media._ID};
     53     private static final String[] COUNT_PROJECTION = {"count(*)"};
     54     /* We don't want to include the download directory */
     55     private static final String SELECTION =
     56             String.format("%s != %s", Media.BUCKET_ID, getDownloadBucketId());
     57     private static final String ORDER = String.format("%s DESC", DATE_TAKEN);
     58 
     59     private Context mContext;
     60     private ArrayList<Long> mPhotos = new ArrayList<Long>();
     61     private ContentListener mContentListener;
     62     private ContentObserver mContentObserver;
     63     private boolean mContentDirty = true;
     64     private DataManager mDataManager;
     65     private static final Path LOCAL_IMAGE_ROOT = Path.fromString("/local/image/item");
     66 
     67     public LocalPhotoSource(Context context) {
     68         mContext = context;
     69         mDataManager = ((GalleryApp) context.getApplicationContext()).getDataManager();
     70         mContentObserver = new ContentObserver(new Handler()) {
     71             @Override
     72             public void onChange(boolean selfChange) {
     73                 mContentDirty = true;
     74                 if (mContentListener != null) mContentListener.onContentDirty();
     75             }
     76         };
     77         mContext.getContentResolver()
     78                 .registerContentObserver(CONTENT_URI, true, mContentObserver);
     79     }
     80 
     81     @Override
     82     public void close() {
     83         mContext.getContentResolver().unregisterContentObserver(mContentObserver);
     84     }
     85 
     86     @Override
     87     public Uri getContentUri(int index) {
     88         if (index < mPhotos.size()) {
     89             return CONTENT_URI.buildUpon()
     90                     .appendPath(String.valueOf(mPhotos.get(index)))
     91                     .build();
     92         }
     93         return null;
     94     }
     95 
     96     @Override
     97     public Bitmap getImage(int index) {
     98         if (index >= mPhotos.size()) return null;
     99         long id = mPhotos.get(index);
    100         MediaItem image = (MediaItem)
    101                 mDataManager.getMediaObject(LOCAL_IMAGE_ROOT.getChild(id));
    102         if (image == null) return null;
    103 
    104         return WidgetUtils.createWidgetBitmap(image);
    105     }
    106 
    107     private int[] getExponentialIndice(int total, int count) {
    108         Random random = new Random();
    109         if (count > total) count = total;
    110         HashSet<Integer> selected = new HashSet<Integer>(count);
    111         while (selected.size() < count) {
    112             int row = (int)(-Math.log(random.nextDouble()) * total / 2);
    113             if (row < total) selected.add(row);
    114         }
    115         int values[] = new int[count];
    116         int index = 0;
    117         for (int value : selected) {
    118             values[index++] = value;
    119         }
    120         return values;
    121     }
    122 
    123     private int getPhotoCount(ContentResolver resolver) {
    124         Cursor cursor = resolver.query(
    125                 CONTENT_URI, COUNT_PROJECTION, SELECTION, null, null);
    126         if (cursor == null) return 0;
    127         try {
    128             Utils.assertTrue(cursor.moveToNext());
    129             return cursor.getInt(0);
    130         } finally {
    131             cursor.close();
    132         }
    133     }
    134 
    135     private boolean isContentSound(int totalCount) {
    136         if (mPhotos.size() < Math.min(totalCount, MAX_PHOTO_COUNT)) return false;
    137         if (mPhotos.size() == 0) return true; // totalCount is also 0
    138 
    139         StringBuilder builder = new StringBuilder();
    140         for (Long imageId : mPhotos) {
    141             if (builder.length() > 0) builder.append(",");
    142             builder.append(imageId);
    143         }
    144         Cursor cursor = mContext.getContentResolver().query(
    145                 CONTENT_URI, COUNT_PROJECTION,
    146                 String.format("%s in (%s)", Media._ID, builder.toString()),
    147                 null, null);
    148         if (cursor == null) return false;
    149         try {
    150             Utils.assertTrue(cursor.moveToNext());
    151             return cursor.getInt(0) == mPhotos.size();
    152         } finally {
    153             cursor.close();
    154         }
    155     }
    156 
    157     @Override
    158     public void reload() {
    159         if (!mContentDirty) return;
    160         mContentDirty = false;
    161 
    162         ContentResolver resolver = mContext.getContentResolver();
    163         int photoCount = getPhotoCount(resolver);
    164         if (isContentSound(photoCount)) return;
    165 
    166         int choosedIds[] = getExponentialIndice(photoCount, MAX_PHOTO_COUNT);
    167         Arrays.sort(choosedIds);
    168 
    169         mPhotos.clear();
    170         Cursor cursor = mContext.getContentResolver().query(
    171                 CONTENT_URI, PROJECTION, SELECTION, null, ORDER);
    172         if (cursor == null) return;
    173         try {
    174             for (int index : choosedIds) {
    175                 if (cursor.moveToPosition(index)) {
    176                     mPhotos.add(cursor.getLong(0));
    177                 }
    178             }
    179         } finally {
    180             cursor.close();
    181         }
    182     }
    183 
    184     @Override
    185     public int size() {
    186         reload();
    187         return mPhotos.size();
    188     }
    189 
    190     /**
    191      * Builds the bucket ID for the public external storage Downloads directory
    192      * @return the bucket ID
    193      */
    194     private static int getDownloadBucketId() {
    195         String downloadsPath = Environment
    196                 .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    197                 .getAbsolutePath();
    198         return GalleryUtils.getBucketId(downloadsPath);
    199     }
    200 
    201     @Override
    202     public void setContentListener(ContentListener listener) {
    203         mContentListener = listener;
    204     }
    205 }
    206