/* * Copyright (C) 2011 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.gadget; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Environment; import android.os.Handler; import android.provider.MediaStore.Images.Media; import com.android.gallery3d.app.GalleryApp; 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.Path; import com.android.gallery3d.util.GalleryUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Random; public class LocalPhotoSource implements WidgetSource { @SuppressWarnings("unused") private static final String TAG = "LocalPhotoSource"; private static final int MAX_PHOTO_COUNT = 128; /* Static fields used to query for the correct set of images */ private static final Uri CONTENT_URI = Media.EXTERNAL_CONTENT_URI; private static final String DATE_TAKEN = Media.DATE_TAKEN; private static final String[] PROJECTION = {Media._ID}; private static final String[] COUNT_PROJECTION = {"count(*)"}; /* We don't want to include the download directory */ private static final String SELECTION = String.format("%s != %s", Media.BUCKET_ID, getDownloadBucketId()); private static final String ORDER = String.format("%s DESC", DATE_TAKEN); private Context mContext; private ArrayList mPhotos = new ArrayList(); private ContentListener mContentListener; private ContentObserver mContentObserver; private boolean mContentDirty = true; private DataManager mDataManager; private static final Path LOCAL_IMAGE_ROOT = Path.fromString("/local/image/item"); public LocalPhotoSource(Context context) { mContext = context; mDataManager = ((GalleryApp) context.getApplicationContext()).getDataManager(); mContentObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { mContentDirty = true; if (mContentListener != null) mContentListener.onContentDirty(); } }; mContext.getContentResolver() .registerContentObserver(CONTENT_URI, true, mContentObserver); } @Override public void close() { mContext.getContentResolver().unregisterContentObserver(mContentObserver); } @Override public Uri getContentUri(int index) { if (index < mPhotos.size()) { return CONTENT_URI.buildUpon() .appendPath(String.valueOf(mPhotos.get(index))) .build(); } return null; } @Override public Bitmap getImage(int index) { if (index >= mPhotos.size()) return null; long id = mPhotos.get(index); MediaItem image = (MediaItem) mDataManager.getMediaObject(LOCAL_IMAGE_ROOT.getChild(id)); if (image == null) return null; return WidgetUtils.createWidgetBitmap(image); } private int[] getExponentialIndice(int total, int count) { Random random = new Random(); if (count > total) count = total; HashSet selected = new HashSet(count); while (selected.size() < count) { int row = (int)(-Math.log(random.nextDouble()) * total / 2); if (row < total) selected.add(row); } int values[] = new int[count]; int index = 0; for (int value : selected) { values[index++] = value; } return values; } private int getPhotoCount(ContentResolver resolver) { Cursor cursor = resolver.query( CONTENT_URI, COUNT_PROJECTION, SELECTION, null, null); if (cursor == null) return 0; try { Utils.assertTrue(cursor.moveToNext()); return cursor.getInt(0); } finally { cursor.close(); } } private boolean isContentSound(int totalCount) { if (mPhotos.size() < Math.min(totalCount, MAX_PHOTO_COUNT)) return false; if (mPhotos.size() == 0) return true; // totalCount is also 0 StringBuilder builder = new StringBuilder(); for (Long imageId : mPhotos) { if (builder.length() > 0) builder.append(","); builder.append(imageId); } Cursor cursor = mContext.getContentResolver().query( CONTENT_URI, COUNT_PROJECTION, String.format("%s in (%s)", Media._ID, builder.toString()), null, null); if (cursor == null) return false; try { Utils.assertTrue(cursor.moveToNext()); return cursor.getInt(0) == mPhotos.size(); } finally { cursor.close(); } } @Override public void reload() { if (!mContentDirty) return; mContentDirty = false; ContentResolver resolver = mContext.getContentResolver(); int photoCount = getPhotoCount(resolver); if (isContentSound(photoCount)) return; int choosedIds[] = getExponentialIndice(photoCount, MAX_PHOTO_COUNT); Arrays.sort(choosedIds); mPhotos.clear(); Cursor cursor = mContext.getContentResolver().query( CONTENT_URI, PROJECTION, SELECTION, null, ORDER); if (cursor == null) return; try { for (int index : choosedIds) { if (cursor.moveToPosition(index)) { mPhotos.add(cursor.getLong(0)); } } } finally { cursor.close(); } } @Override public int size() { reload(); return mPhotos.size(); } /** * Builds the bucket ID for the public external storage Downloads directory * @return the bucket ID */ private static int getDownloadBucketId() { String downloadsPath = Environment .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) .getAbsolutePath(); return GalleryUtils.getBucketId(downloadsPath); } @Override public void setContentListener(ContentListener listener) { mContentListener = listener; } }