1 /* 2 * Copyright (C) 2013 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.camera.data; 18 19 import android.app.Activity; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.graphics.drawable.Drawable; 24 import android.net.Uri; 25 import android.os.AsyncTask; 26 import android.provider.MediaStore; 27 import android.util.Log; 28 import android.view.View; 29 30 import com.android.camera.Storage; 31 import com.android.camera.app.PlaceholderManager; 32 import com.android.camera.ui.FilmStripView.ImageData; 33 34 import java.util.ArrayList; 35 import java.util.Comparator; 36 37 /** 38 * A {@link LocalDataAdapter} that provides data in the camera folder. 39 */ 40 public class CameraDataAdapter implements LocalDataAdapter { 41 private static final String TAG = "CAM_CameraDataAdapter"; 42 43 private static final int DEFAULT_DECODE_SIZE = 1600; 44 private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" }; 45 46 private LocalDataList mImages; 47 48 private Listener mListener; 49 private Drawable mPlaceHolder; 50 51 private int mSuggestedWidth = DEFAULT_DECODE_SIZE; 52 private int mSuggestedHeight = DEFAULT_DECODE_SIZE; 53 54 private LocalData mLocalDataToDelete; 55 56 public CameraDataAdapter(Drawable placeHolder) { 57 mImages = new LocalDataList(); 58 mPlaceHolder = placeHolder; 59 } 60 61 @Override 62 public void requestLoad(ContentResolver resolver) { 63 QueryTask qtask = new QueryTask(); 64 qtask.execute(resolver); 65 } 66 67 @Override 68 public LocalData getLocalData(int dataID) { 69 if (dataID < 0 || dataID >= mImages.size()) { 70 return null; 71 } 72 73 return mImages.get(dataID); 74 } 75 76 @Override 77 public int getTotalNumber() { 78 return mImages.size(); 79 } 80 81 @Override 82 public ImageData getImageData(int id) { 83 return getLocalData(id); 84 } 85 86 @Override 87 public void suggestViewSizeBound(int w, int h) { 88 if (w <= 0 || h <= 0) { 89 mSuggestedWidth = mSuggestedHeight = DEFAULT_DECODE_SIZE; 90 } else { 91 mSuggestedWidth = (w < DEFAULT_DECODE_SIZE ? w : DEFAULT_DECODE_SIZE); 92 mSuggestedHeight = (h < DEFAULT_DECODE_SIZE ? h : DEFAULT_DECODE_SIZE); 93 } 94 } 95 96 @Override 97 public View getView(Activity activity, int dataID) { 98 if (dataID >= mImages.size() || dataID < 0) { 99 return null; 100 } 101 102 return mImages.get(dataID).getView( 103 activity, mSuggestedWidth, mSuggestedHeight, 104 mPlaceHolder.getConstantState().newDrawable(), this); 105 } 106 107 @Override 108 public void setListener(Listener listener) { 109 mListener = listener; 110 if (mImages != null) { 111 mListener.onDataLoaded(); 112 } 113 } 114 115 @Override 116 public boolean canSwipeInFullScreen(int dataID) { 117 if (dataID < mImages.size() && dataID > 0) { 118 return mImages.get(dataID).canSwipeInFullScreen(); 119 } 120 return true; 121 } 122 123 @Override 124 public void removeData(Context c, int dataID) { 125 if (dataID >= mImages.size()) return; 126 LocalData d = mImages.remove(dataID); 127 // Delete previously removed data first. 128 executeDeletion(c); 129 mLocalDataToDelete = d; 130 mListener.onDataRemoved(dataID, d); 131 } 132 133 // TODO: put the database query on background thread 134 @Override 135 public void addNewVideo(ContentResolver cr, Uri uri) { 136 Cursor c = cr.query(uri, 137 LocalMediaData.VideoData.QUERY_PROJECTION, 138 MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, 139 LocalMediaData.VideoData.QUERY_ORDER); 140 if (c == null || !c.moveToFirst()) { 141 return; 142 } 143 int pos = findDataByContentUri(uri); 144 LocalMediaData.VideoData newData = LocalMediaData.VideoData.buildFromCursor(c); 145 if (pos != -1) { 146 // A duplicate one, just do a substitute. 147 updateData(pos, newData); 148 } else { 149 // A new data. 150 insertData(newData); 151 } 152 } 153 154 // TODO: put the database query on background thread 155 @Override 156 public void addNewPhoto(ContentResolver cr, Uri uri) { 157 Cursor c = cr.query(uri, 158 LocalMediaData.PhotoData.QUERY_PROJECTION, 159 MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, 160 LocalMediaData.PhotoData.QUERY_ORDER); 161 if (c == null || !c.moveToFirst()) { 162 return; 163 } 164 int pos = findDataByContentUri(uri); 165 LocalMediaData.PhotoData newData = LocalMediaData.PhotoData.buildFromCursor(c); 166 if (pos != -1) { 167 // a duplicate one, just do a substitute. 168 Log.v(TAG, "found duplicate photo"); 169 updateData(pos, newData); 170 } else { 171 // a new data. 172 insertData(newData); 173 } 174 } 175 176 @Override 177 public int findDataByContentUri(Uri uri) { 178 // LocalDataList will return in O(1) if the uri is not contained. 179 // Otherwise the performance is O(n), but this is acceptable as we will 180 // most often call this to find an element at the beginning of the list. 181 return mImages.indexOf(uri); 182 } 183 184 @Override 185 public boolean undoDataRemoval() { 186 if (mLocalDataToDelete == null) return false; 187 LocalData d = mLocalDataToDelete; 188 mLocalDataToDelete = null; 189 insertData(d); 190 return true; 191 } 192 193 @Override 194 public boolean executeDeletion(Context c) { 195 if (mLocalDataToDelete == null) return false; 196 197 DeletionTask task = new DeletionTask(c); 198 task.execute(mLocalDataToDelete); 199 mLocalDataToDelete = null; 200 return true; 201 } 202 203 @Override 204 public void flush() { 205 replaceData(new LocalDataList()); 206 } 207 208 @Override 209 public void refresh(ContentResolver resolver, Uri contentUri) { 210 int pos = findDataByContentUri(contentUri); 211 if (pos == -1) { 212 return; 213 } 214 215 LocalData data = mImages.get(pos); 216 LocalData refreshedData = data.refresh(resolver); 217 if (refreshedData != null) { 218 updateData(pos, refreshedData); 219 } 220 } 221 222 @Override 223 public void updateData(final int pos, LocalData data) { 224 mImages.set(pos, data); 225 if (mListener != null) { 226 mListener.onDataUpdated(new UpdateReporter() { 227 @Override 228 public boolean isDataRemoved(int dataID) { 229 return false; 230 } 231 232 @Override 233 public boolean isDataUpdated(int dataID) { 234 return (dataID == pos); 235 } 236 }); 237 } 238 } 239 240 @Override 241 public void insertData(LocalData data) { 242 // Since this function is mostly for adding the newest data, 243 // a simple linear search should yield the best performance over a 244 // binary search. 245 int pos = 0; 246 Comparator<LocalData> comp = new LocalData.NewestFirstComparator(); 247 for (; pos < mImages.size() 248 && comp.compare(data, mImages.get(pos)) > 0; pos++); 249 mImages.add(pos, data); 250 if (mListener != null) { 251 mListener.onDataInserted(pos, data); 252 } 253 } 254 255 /** Update all the data */ 256 private void replaceData(LocalDataList list) { 257 if (list.size() == 0 && mImages.size() == 0) { 258 return; 259 } 260 mImages = list; 261 if (mListener != null) { 262 mListener.onDataLoaded(); 263 } 264 } 265 266 private class QueryTask extends AsyncTask<ContentResolver, Void, LocalDataList> { 267 268 /** 269 * Loads all the photo and video data in the camera folder in background 270 * and combine them into one single list. 271 * 272 * @param resolver {@link ContentResolver} to load all the data. 273 * @return An {@link ArrayList} of all loaded data. 274 */ 275 @Override 276 protected LocalDataList doInBackground(ContentResolver... resolver) { 277 LocalDataList l = new LocalDataList(); 278 // Photos 279 Cursor c = resolver[0].query( 280 LocalMediaData.PhotoData.CONTENT_URI, 281 LocalMediaData.PhotoData.QUERY_PROJECTION, 282 MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, 283 LocalMediaData.PhotoData.QUERY_ORDER); 284 if (c != null && c.moveToFirst()) { 285 // build up the list. 286 while (true) { 287 LocalData data = LocalMediaData.PhotoData.buildFromCursor(c); 288 if (data != null) { 289 if (data.getMimeType().equals(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) { 290 l.add(new InProgressDataWrapper(data, true)); 291 } else { 292 l.add(data); 293 } 294 } else { 295 Log.e(TAG, "Error loading data:" 296 + c.getString(LocalMediaData.PhotoData.COL_DATA)); 297 } 298 if (c.isLast()) { 299 break; 300 } 301 c.moveToNext(); 302 } 303 } 304 if (c != null) { 305 c.close(); 306 } 307 308 c = resolver[0].query( 309 LocalMediaData.VideoData.CONTENT_URI, 310 LocalMediaData.VideoData.QUERY_PROJECTION, 311 MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH, 312 LocalMediaData.VideoData.QUERY_ORDER); 313 if (c != null && c.moveToFirst()) { 314 // build up the list. 315 c.moveToFirst(); 316 while (true) { 317 LocalData data = LocalMediaData.VideoData.buildFromCursor(c); 318 if (data != null) { 319 l.add(data); 320 } else { 321 Log.e(TAG, "Error loading data:" 322 + c.getString(LocalMediaData.VideoData.COL_DATA)); 323 } 324 if (!c.isLast()) { 325 c.moveToNext(); 326 } else { 327 break; 328 } 329 } 330 } 331 if (c != null) { 332 c.close(); 333 } 334 335 if (l.size() != 0) { 336 l.sort(new LocalData.NewestFirstComparator()); 337 } 338 339 return l; 340 } 341 342 @Override 343 protected void onPostExecute(LocalDataList l) { 344 replaceData(l); 345 } 346 } 347 348 private class DeletionTask extends AsyncTask<LocalData, Void, Void> { 349 Context mContext; 350 351 DeletionTask(Context context) { 352 mContext = context; 353 } 354 355 @Override 356 protected Void doInBackground(LocalData... data) { 357 for (int i = 0; i < data.length; i++) { 358 if (!data[i].isDataActionSupported(LocalData.ACTION_DELETE)) { 359 Log.v(TAG, "Deletion is not supported:" + data[i]); 360 continue; 361 } 362 data[i].delete(mContext); 363 } 364 return null; 365 } 366 } 367 } 368