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.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