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