Home | History | Annotate | Download | only in data
      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