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 17 package com.android.gallery3d.data; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.net.Uri; 22 import android.provider.MediaStore.Images; 23 import android.provider.MediaStore.MediaColumns; 24 import android.provider.MediaStore.Video; 25 26 import com.android.gallery3d.app.GalleryApp; 27 import com.android.gallery3d.app.StitchingChangeListener; 28 import com.android.gallery3d.util.MediaSetUtils; 29 30 import java.util.ArrayList; 31 32 // This class lists all media items added by the client. 33 public class SecureAlbum extends MediaSet implements StitchingChangeListener { 34 @SuppressWarnings("unused") 35 private static final String TAG = "SecureAlbum"; 36 private static final String[] PROJECTION = {MediaColumns._ID}; 37 private int mMinImageId = Integer.MAX_VALUE; // the smallest id of images 38 private int mMaxImageId = Integer.MIN_VALUE; // the biggest id in images 39 private int mMinVideoId = Integer.MAX_VALUE; // the smallest id of videos 40 private int mMaxVideoId = Integer.MIN_VALUE; // the biggest id of videos 41 // All the media items added by the client. 42 private ArrayList<Path> mAllItems = new ArrayList<Path>(); 43 // The types of items in mAllItems. True is video and false is image. 44 private ArrayList<Boolean> mAllItemTypes = new ArrayList<Boolean>(); 45 private ArrayList<Path> mExistingItems = new ArrayList<Path>(); 46 private Context mContext; 47 private DataManager mDataManager; 48 private static final Uri[] mWatchUris = 49 {Images.Media.EXTERNAL_CONTENT_URI, Video.Media.EXTERNAL_CONTENT_URI}; 50 private final ChangeNotifier mNotifier; 51 // A placeholder image in the end of secure album. When it is tapped, it 52 // will take the user to the lock screen. 53 private MediaItem mUnlockItem; 54 private boolean mShowUnlockItem; 55 56 public SecureAlbum(Path path, GalleryApp application, MediaItem unlock) { 57 super(path, nextVersionNumber()); 58 mContext = application.getAndroidContext(); 59 mDataManager = application.getDataManager(); 60 mNotifier = new ChangeNotifier(this, mWatchUris, application); 61 mUnlockItem = unlock; 62 mShowUnlockItem = (!isCameraBucketEmpty(Images.Media.EXTERNAL_CONTENT_URI) 63 || !isCameraBucketEmpty(Video.Media.EXTERNAL_CONTENT_URI)); 64 } 65 66 public void addMediaItem(boolean isVideo, int id) { 67 Path pathBase; 68 if (isVideo) { 69 pathBase = LocalVideo.ITEM_PATH; 70 mMinVideoId = Math.min(mMinVideoId, id); 71 mMaxVideoId = Math.max(mMaxVideoId, id); 72 } else { 73 pathBase = LocalImage.ITEM_PATH; 74 mMinImageId = Math.min(mMinImageId, id); 75 mMaxImageId = Math.max(mMaxImageId, id); 76 } 77 Path path = pathBase.getChild(id); 78 if (!mAllItems.contains(path)) { 79 mAllItems.add(path); 80 mAllItemTypes.add(isVideo); 81 mNotifier.fakeChange(); 82 } 83 } 84 85 // The sequence is stitching items, local media items, and unlock image. 86 @Override 87 public ArrayList<MediaItem> getMediaItem(int start, int count) { 88 int existingCount = mExistingItems.size(); 89 if (start >= existingCount + 1) { 90 return new ArrayList<MediaItem>(); 91 } 92 93 // Add paths of requested stitching items. 94 int end = Math.min(start + count, existingCount); 95 ArrayList<Path> subset = new ArrayList<Path>(mExistingItems.subList(start, end)); 96 97 // Convert paths to media items. 98 final MediaItem[] buf = new MediaItem[end - start]; 99 ItemConsumer consumer = new ItemConsumer() { 100 @Override 101 public void consume(int index, MediaItem item) { 102 buf[index] = item; 103 } 104 }; 105 mDataManager.mapMediaItems(subset, consumer, 0); 106 ArrayList<MediaItem> result = new ArrayList<MediaItem>(end - start); 107 for (int i = 0; i < buf.length; i++) { 108 result.add(buf[i]); 109 } 110 if (mShowUnlockItem) result.add(mUnlockItem); 111 return result; 112 } 113 114 @Override 115 public int getMediaItemCount() { 116 return (mExistingItems.size() + (mShowUnlockItem ? 1 : 0)); 117 } 118 119 @Override 120 public String getName() { 121 return "secure"; 122 } 123 124 @Override 125 public long reload() { 126 if (mNotifier.isDirty()) { 127 mDataVersion = nextVersionNumber(); 128 updateExistingItems(); 129 } 130 return mDataVersion; 131 } 132 133 private ArrayList<Integer> queryExistingIds(Uri uri, int minId, int maxId) { 134 ArrayList<Integer> ids = new ArrayList<Integer>(); 135 if (minId == Integer.MAX_VALUE || maxId == Integer.MIN_VALUE) return ids; 136 137 String[] selectionArgs = {String.valueOf(minId), String.valueOf(maxId)}; 138 Cursor cursor = mContext.getContentResolver().query(uri, PROJECTION, 139 "_id BETWEEN ? AND ?", selectionArgs, null); 140 if (cursor == null) return ids; 141 try { 142 while (cursor.moveToNext()) { 143 ids.add(cursor.getInt(0)); 144 } 145 } finally { 146 cursor.close(); 147 } 148 return ids; 149 } 150 151 private boolean isCameraBucketEmpty(Uri baseUri) { 152 Uri uri = baseUri.buildUpon() 153 .appendQueryParameter("limit", "1").build(); 154 String[] selection = {String.valueOf(MediaSetUtils.CAMERA_BUCKET_ID)}; 155 Cursor cursor = mContext.getContentResolver().query(uri, PROJECTION, 156 "bucket_id = ?", selection, null); 157 if (cursor == null) return true; 158 try { 159 return (cursor.getCount() == 0); 160 } finally { 161 cursor.close(); 162 } 163 } 164 165 private void updateExistingItems() { 166 if (mAllItems.size() == 0) return; 167 168 // Query existing ids. 169 ArrayList<Integer> imageIds = queryExistingIds( 170 Images.Media.EXTERNAL_CONTENT_URI, mMinImageId, mMaxImageId); 171 ArrayList<Integer> videoIds = queryExistingIds( 172 Video.Media.EXTERNAL_CONTENT_URI, mMinVideoId, mMaxVideoId); 173 174 // Construct the existing items list. 175 mExistingItems.clear(); 176 for (int i = mAllItems.size() - 1; i >= 0; i--) { 177 Path path = mAllItems.get(i); 178 boolean isVideo = mAllItemTypes.get(i); 179 int id = Integer.parseInt(path.getSuffix()); 180 if (isVideo) { 181 if (videoIds.contains(id)) mExistingItems.add(path); 182 } else { 183 if (imageIds.contains(id)) mExistingItems.add(path); 184 } 185 } 186 } 187 188 @Override 189 public boolean isLeafAlbum() { 190 return true; 191 } 192 193 @Override 194 public void onStitchingQueued(Uri uri) { 195 int id = Integer.parseInt(uri.getLastPathSegment()); 196 addMediaItem(false, id); 197 } 198 199 @Override 200 public void onStitchingResult(Uri uri) { 201 } 202 203 @Override 204 public void onStitchingProgress(Uri uri, final int progress) { 205 } 206 } 207