/* * Copyright (C) 2012 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.data; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore.MediaColumns; import android.provider.MediaStore.Images; import android.provider.MediaStore.Video; import com.android.gallery3d.app.GalleryApp; import com.android.gallery3d.app.StitchingChangeListener; import com.android.gallery3d.app.StitchingProgressManager; import com.android.gallery3d.util.MediaSetUtils; import java.util.ArrayList; // This class lists all media items added by the client. public class SecureAlbum extends MediaSet implements StitchingChangeListener { @SuppressWarnings("unused") private static final String TAG = "SecureAlbum"; private static final String[] PROJECTION = {MediaColumns._ID}; private int mMinImageId = Integer.MAX_VALUE; // the smallest id of images private int mMaxImageId = Integer.MIN_VALUE; // the biggest id in images private int mMinVideoId = Integer.MAX_VALUE; // the smallest id of videos private int mMaxVideoId = Integer.MIN_VALUE; // the biggest id of videos // All the media items added by the client. private ArrayList mAllItems = new ArrayList(); // The types of items in mAllItems. True is video and false is image. private ArrayList mAllItemTypes = new ArrayList(); private ArrayList mExistingItems = new ArrayList(); private ArrayList mStitchingItems = new ArrayList(); private Context mContext; private DataManager mDataManager; private StitchingProgressManager mStitchingProgressManager; private static final Uri[] mWatchUris = {Images.Media.EXTERNAL_CONTENT_URI, Video.Media.EXTERNAL_CONTENT_URI}; private final ChangeNotifier mNotifier; // A placeholder image in the end of secure album. When it is tapped, it // will take the user to the lock screen. private MediaItem mUnlockItem; private boolean mShowUnlockItem; public SecureAlbum(Path path, GalleryApp application, MediaItem unlock) { super(path, nextVersionNumber()); mContext = application.getAndroidContext(); mDataManager = application.getDataManager(); mNotifier = new ChangeNotifier(this, mWatchUris, application); mUnlockItem = unlock; mShowUnlockItem = (!isCameraBucketEmpty(Images.Media.EXTERNAL_CONTENT_URI) || !isCameraBucketEmpty(Video.Media.EXTERNAL_CONTENT_URI)); mStitchingProgressManager = application.getStitchingProgressManager(); mStitchingProgressManager.addChangeListener(this); } public void addMediaItem(boolean isVideo, int id) { if (isVideo) { mAllItems.add(Path.fromString("/local/video/item/" + id)); mMinVideoId = Math.min(mMinVideoId, id); mMaxVideoId = Math.max(mMaxVideoId, id); } else { mAllItems.add(Path.fromString("/local/image/item/" + id)); mMinImageId = Math.min(mMinImageId, id); mMaxImageId = Math.max(mMaxImageId, id); } mAllItemTypes.add(isVideo); mNotifier.fakeChange(); } // The sequence is stitching items, local media items, and unlock image. @Override public ArrayList getMediaItem(int start, int count) { int stitchingCount = mStitchingItems.size(); int existingCount = mExistingItems.size(); if (start >= stitchingCount + existingCount + 1) { return new ArrayList(); } // Add paths of requested stitching items. int end = Math.min(start + count, stitchingCount + existingCount); ArrayList subset = new ArrayList(); if (start < stitchingCount) { subset.addAll(mStitchingItems.subList( start, Math.min(stitchingCount, end))); } // Add paths of requested local media items. if (end >= stitchingCount) { int existingStart = Math.max(0, start - stitchingCount); int existingEnd = end - stitchingCount; subset.addAll(mExistingItems.subList(existingStart, existingEnd)); } // Convert paths to media items. final MediaItem[] buf = new MediaItem[end - start]; ItemConsumer consumer = new ItemConsumer() { @Override public void consume(int index, MediaItem item) { buf[index] = item; } }; mDataManager.mapMediaItems(subset, consumer, 0); ArrayList result = new ArrayList(end - start); for (int i = 0; i < buf.length; i++) { result.add(buf[i]); } if (mShowUnlockItem) result.add(mUnlockItem); return result; } @Override public int getMediaItemCount() { return (mStitchingItems.size() + mExistingItems.size() + (mShowUnlockItem ? 1 : 0)); } @Override public String getName() { return "secure"; } @Override public long reload() { if (mNotifier.isDirty()) { mDataVersion = nextVersionNumber(); updateExistingItems(); } return mDataVersion; } private ArrayList queryExistingIds(Uri uri, int minId, int maxId) { ArrayList ids = new ArrayList(); if (minId == Integer.MAX_VALUE || maxId == Integer.MIN_VALUE) return ids; String[] selectionArgs = {String.valueOf(minId), String.valueOf(maxId)}; Cursor cursor = mContext.getContentResolver().query(uri, PROJECTION, "_id BETWEEN ? AND ?", selectionArgs, null); if (cursor == null) return ids; try { while (cursor.moveToNext()) { ids.add(cursor.getInt(0)); } } finally { cursor.close(); } return ids; } private boolean isCameraBucketEmpty(Uri baseUri) { Uri uri = baseUri.buildUpon() .appendQueryParameter("limit", "1").build(); String[] selection = {String.valueOf(MediaSetUtils.CAMERA_BUCKET_ID)}; Cursor cursor = mContext.getContentResolver().query(uri, PROJECTION, "bucket_id = ?", selection, null); if (cursor == null) return true; try { return (cursor.getCount() == 0); } finally { cursor.close(); } } private void updateExistingItems() { if (mAllItems.size() == 0) return; // Query existing ids. ArrayList imageIds = queryExistingIds( Images.Media.EXTERNAL_CONTENT_URI, mMinImageId, mMaxImageId); ArrayList videoIds = queryExistingIds( Video.Media.EXTERNAL_CONTENT_URI, mMinVideoId, mMaxVideoId); // Construct the existing items list. mExistingItems.clear(); for (int i = mAllItems.size() - 1; i >= 0; i--) { Path path = mAllItems.get(i); boolean isVideo = mAllItemTypes.get(i); int id = Integer.parseInt(path.getSuffix()); if (isVideo) { if (videoIds.contains(id)) mExistingItems.add(path); } else { if (imageIds.contains(id)) mExistingItems.add(path); } } } @Override public boolean isLeafAlbum() { return true; } @Override public void onStitchingQueued(Path path) { mStitchingItems.add(path); notifyContentChanged(); } @Override public void onStitchingResult(Path path, Uri uri) { if (mStitchingItems.remove(path)) { int id = Integer.parseInt(uri.getLastPathSegment()); addMediaItem(false, id); notifyContentChanged(); } } @Override public void onStitchingProgress(Path path, int progress) { if (mStitchingItems.contains(path)) { notifyContentChanged(); } } }