CameraDataAdapter.java revision a16e7b50f3148f581439509279f242092e254309
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 17package com.android.camera.data; 18 19import android.content.ContentResolver; 20import android.content.Context; 21import android.database.Cursor; 22import android.graphics.drawable.Drawable; 23import android.net.Uri; 24import android.os.AsyncTask; 25import android.provider.MediaStore; 26import android.util.Log; 27import android.view.View; 28 29import com.android.camera.Storage; 30import com.android.camera.ui.FilmStripView.ImageData; 31 32import java.util.ArrayList; 33import java.util.Collections; 34import java.util.Comparator; 35import java.util.List; 36 37/** 38 * A {@link LocalDataAdapter} that provides data in the camera folder. 39 */ 40public 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 List<LocalData> 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 ArrayList<LocalData>(); 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(Context c, int dataID) { 98 if (dataID >= mImages.size() || dataID < 0) { 99 return null; 100 } 101 102 return mImages.get(dataID).getView( 103 c, mSuggestedWidth, mSuggestedHeight, 104 mPlaceHolder.getConstantState().newDrawable()); 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 for (int i = 0; i < mImages.size(); i++) { 179 Uri u = mImages.get(i).getContentUri(); 180 if (u == null) { 181 continue; 182 } 183 if (u.equals(uri)) { 184 return i; 185 } 186 } 187 return -1; 188 } 189 190 @Override 191 public boolean undoDataRemoval() { 192 if (mLocalDataToDelete == null) return false; 193 LocalData d = mLocalDataToDelete; 194 mLocalDataToDelete = null; 195 insertData(d); 196 return true; 197 } 198 199 @Override 200 public boolean executeDeletion(Context c) { 201 if (mLocalDataToDelete == null) return false; 202 203 DeletionTask task = new DeletionTask(c); 204 task.execute(mLocalDataToDelete); 205 mLocalDataToDelete = null; 206 return true; 207 } 208 209 @Override 210 public void flush() { 211 replaceData(new ArrayList<LocalData>()); 212 } 213 214 @Override 215 public void refresh(ContentResolver resolver, Uri contentUri) { 216 int pos = findDataByContentUri(contentUri); 217 if (pos == -1) { 218 return; 219 } 220 221 LocalData data = mImages.get(pos); 222 LocalData refreshedData = data.refresh(resolver); 223 if (refreshedData != null) { 224 updateData(pos, refreshedData); 225 } 226 } 227 228 @Override 229 public void updateData(final int pos, LocalData data) { 230 mImages.set(pos, data); 231 if (mListener != null) { 232 mListener.onDataUpdated(new UpdateReporter() { 233 @Override 234 public boolean isDataRemoved(int dataID) { 235 return false; 236 } 237 238 @Override 239 public boolean isDataUpdated(int dataID) { 240 return (dataID == pos); 241 } 242 }); 243 } 244 } 245 246 @Override 247 public void insertData(LocalData data) { 248 // Since this function is mostly for adding the newest data, 249 // a simple linear search should yield the best performance over a 250 // binary search. 251 int pos = 0; 252 Comparator<LocalData> comp = new LocalData.NewestFirstComparator(); 253 for (; pos < mImages.size() 254 && comp.compare(data, mImages.get(pos)) > 0; pos++); 255 mImages.add(pos, data); 256 if (mListener != null) { 257 mListener.onDataInserted(pos, data); 258 } 259 } 260 261 /** Update all the data */ 262 private void replaceData(List<LocalData> list) { 263 if (list.size() == 0 && mImages.size() == 0) { 264 return; 265 } 266 mImages = list; 267 if (mListener != null) { 268 mListener.onDataLoaded(); 269 } 270 } 271 272 private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalData>> { 273 274 /** 275 * Loads all the photo and video data in the camera folder in background 276 * and combine them into one single list. 277 * 278 * @param resolver {@link ContentResolver} to load all the data. 279 * @return An {@link ArrayList} of all loaded data. 280 */ 281 @Override 282 protected List<LocalData> doInBackground(ContentResolver... resolver) { 283 List<LocalData> l = new ArrayList<LocalData>(); 284 // Photos 285 Cursor c = resolver[0].query( 286 LocalMediaData.PhotoData.CONTENT_URI, 287 LocalMediaData.PhotoData.QUERY_PROJECTION, 288 MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH, 289 LocalMediaData.PhotoData.QUERY_ORDER); 290 if (c != null && c.moveToFirst()) { 291 // build up the list. 292 while (true) { 293 LocalData data = LocalMediaData.PhotoData.buildFromCursor(c); 294 if (data != null) { 295 l.add(data); 296 } else { 297 Log.e(TAG, "Error loading data:" 298 + c.getString(LocalMediaData.PhotoData.COL_DATA)); 299 } 300 if (c.isLast()) { 301 break; 302 } 303 c.moveToNext(); 304 } 305 } 306 if (c != null) { 307 c.close(); 308 } 309 310 c = resolver[0].query( 311 LocalMediaData.VideoData.CONTENT_URI, 312 LocalMediaData.VideoData.QUERY_PROJECTION, 313 MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH, 314 LocalMediaData.VideoData.QUERY_ORDER); 315 if (c != null && c.moveToFirst()) { 316 // build up the list. 317 c.moveToFirst(); 318 while (true) { 319 LocalData data = LocalMediaData.VideoData.buildFromCursor(c); 320 if (data != null) { 321 l.add(data); 322 } else { 323 Log.e(TAG, "Error loading data:" 324 + c.getString(LocalMediaData.VideoData.COL_DATA)); 325 } 326 if (!c.isLast()) { 327 c.moveToNext(); 328 } else { 329 break; 330 } 331 } 332 } 333 if (c != null) { 334 c.close(); 335 } 336 337 if (l.size() != 0) { 338 Collections.sort(l, new LocalData.NewestFirstComparator()); 339 } 340 341 return l; 342 } 343 344 @Override 345 protected void onPostExecute(List<LocalData> l) { 346 replaceData(l); 347 } 348 } 349 350 private class DeletionTask extends AsyncTask<LocalData, Void, Void> { 351 Context mContext; 352 353 DeletionTask(Context context) { 354 mContext = context; 355 } 356 357 @Override 358 protected Void doInBackground(LocalData... data) { 359 for (int i = 0; i < data.length; i++) { 360 if (!data[i].isDataActionSupported(LocalData.ACTION_DELETE)) { 361 Log.v(TAG, "Deletion is not supported:" + data[i]); 362 continue; 363 } 364 data[i].delete(mContext); 365 } 366 return null; 367 } 368 } 369} 370