Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2007 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.cooliris.media;
     18 
     19 /*
     20  *  Added changes to support numeric comparisons, and also expose the current
     21  *  cursor being used
     22  */
     23 
     24 import android.database.AbstractCursor;
     25 import android.database.Cursor;
     26 import android.database.DataSetObserver;
     27 import android.util.Log;
     28 
     29 /**
     30  * A variant of MergeCursor that sorts the cursors being merged. If decent
     31  * performance is ever obtained, it can be put back under android.database.
     32  */
     33 public class SortCursor extends AbstractCursor {
     34     private static final String TAG = "SortCursor";
     35     private Cursor mCursor; // updated in onMove
     36     private Cursor[] mCursors;
     37     private int[] mSortColumns;
     38     private final int ROWCACHESIZE = 64;
     39     private int mRowNumCache[] = new int[ROWCACHESIZE];
     40     private int mCursorCache[] = new int[ROWCACHESIZE];
     41     private int mCurRowNumCache[][];
     42     private int mLastCacheHit = -1;
     43     private int mType;
     44     private boolean mAscending;
     45     public static final int TYPE_STRING = 0;
     46     public static final int TYPE_NUMERIC = 1;
     47 
     48     private DataSetObserver mObserver = new DataSetObserver() {
     49         @Override
     50         public void onChanged() {
     51             // Reset our position so the optimizations in move-related code
     52             // don't screw us over
     53             mPos = -1;
     54         }
     55 
     56         @Override
     57         public void onInvalidated() {
     58             mPos = -1;
     59         }
     60     };
     61     private int mCursorIndex;
     62 
     63     public SortCursor(Cursor[] cursors, String sortcolumn, int type, boolean ascending) {
     64         mAscending = ascending;
     65         mCursors = cursors;
     66         mType = type;
     67         int length = mCursors.length;
     68         mSortColumns = new int[length];
     69         for (int i = 0; i < length; i++) {
     70             if (mCursors[i] == null) {
     71                 continue;
     72             }
     73             // Register ourself as a data set observer
     74             mCursors[i].registerDataSetObserver(mObserver);
     75             mCursors[i].moveToFirst();
     76             // We don't catch the exception.
     77             mSortColumns[i] = mCursors[i].getColumnIndexOrThrow(sortcolumn);
     78         }
     79         mCursor = null;
     80         if (type == TYPE_STRING) {
     81             String smallest = "";
     82             for (int j = 0; j < length; j++) {
     83                 if (mCursors[j] == null || mCursors[j].isAfterLast())
     84                     continue;
     85                 String current = mCursors[j].getString(mSortColumns[j]);
     86                 if (mCursor == null || current == null || current.compareToIgnoreCase(smallest) < 0) {
     87                     smallest = current;
     88                     mCursor = mCursors[j];
     89                     mCursorIndex = j;
     90                 }
     91             }
     92         } else {
     93             long smallest = (ascending) ? Long.MAX_VALUE : Long.MIN_VALUE;
     94             for (int j = 0; j < length; j++) {
     95                 if (mCursors[j] == null || mCursors[j].isAfterLast()) {
     96                     continue;
     97                 }
     98                 long current = mCursors[j].getLong(mSortColumns[j]);
     99                 boolean comparison = (ascending) ? (current < smallest) : (current > smallest);
    100                 if (mCursor == null || comparison) {
    101                     smallest = current;
    102                     mCursor = mCursors[j];
    103                     mCursorIndex = j;
    104                 }
    105             }
    106         }
    107 
    108         for (int i = mRowNumCache.length - 1; i >= 0; i--) {
    109             mRowNumCache[i] = -2;
    110         }
    111         mCurRowNumCache = new int[ROWCACHESIZE][length];
    112     }
    113 
    114     @Override
    115     public int getCount() {
    116         int count = 0;
    117         int length = mCursors.length;
    118         for (int i = 0; i < length; i++) {
    119             if (mCursors[i] != null) {
    120                 count += mCursors[i].getCount();
    121             }
    122         }
    123         return count;
    124     }
    125 
    126     @Override
    127     public boolean onMove(int oldPosition, int newPosition) {
    128         if (oldPosition == newPosition)
    129             return true;
    130 
    131         /*
    132          * Find the right cursor Because the client of this cursor (the
    133          * listadapter/view) tends to jump around in the cursor somewhat, a
    134          * simple cache strategy is used to avoid having to search all cursors
    135          * from the start. TODO: investigate strategies for optimizing random
    136          * access and reverse-order access.
    137          */
    138 
    139         int cache_entry = newPosition % ROWCACHESIZE;
    140 
    141         if (mRowNumCache[cache_entry] == newPosition) {
    142             int which = mCursorCache[cache_entry];
    143             mCursor = mCursors[which];
    144             mCursorIndex = which;
    145             if (mCursor == null) {
    146                 Log.w(TAG, "onMove: cache results in a null cursor.");
    147                 return false;
    148             }
    149             mCursor.moveToPosition(mCurRowNumCache[cache_entry][which]);
    150             mLastCacheHit = cache_entry;
    151             return true;
    152         }
    153 
    154         mCursor = null;
    155         int length = mCursors.length;
    156 
    157         if (mLastCacheHit >= 0) {
    158             for (int i = 0; i < length; i++) {
    159                 if (mCursors[i] == null)
    160                     continue;
    161                 mCursors[i].moveToPosition(mCurRowNumCache[mLastCacheHit][i]);
    162             }
    163         }
    164 
    165         if (newPosition < oldPosition || oldPosition == -1) {
    166             for (int i = 0; i < length; i++) {
    167                 if (mCursors[i] == null)
    168                     continue;
    169                 mCursors[i].moveToFirst();
    170             }
    171             oldPosition = 0;
    172         }
    173         if (oldPosition < 0) {
    174             oldPosition = 0;
    175         }
    176 
    177         // search forward to the new position
    178         int smallestIdx = -1;
    179         if (mType == TYPE_STRING) {
    180             for (int i = oldPosition; i <= newPosition; i++) {
    181                 String smallest = "";
    182                 smallestIdx = -1;
    183                 for (int j = 0; j < length; j++) {
    184                     if (mCursors[j] == null || mCursors[j].isAfterLast()) {
    185                         continue;
    186                     }
    187                     String current = mCursors[j].getString(mSortColumns[j]);
    188                     if (smallestIdx < 0 || current == null || current.compareToIgnoreCase(smallest) < 0) {
    189                         smallest = current;
    190                         smallestIdx = j;
    191                     }
    192                 }
    193                 if (i == newPosition) {
    194                     break;
    195                 }
    196                 if (mCursors[smallestIdx] != null) {
    197                     mCursors[smallestIdx].moveToNext();
    198                 }
    199             }
    200         } else {
    201             for (int i = oldPosition; i <= newPosition; i++) {
    202                 long smallest = (mAscending) ? Long.MAX_VALUE : Long.MIN_VALUE;
    203                 smallestIdx = -1;
    204                 for (int j = 0; j < length; j++) {
    205                     if (mCursors[j] == null || mCursors[j].isAfterLast()) {
    206                         continue;
    207                     }
    208                     long current = mCursors[j].getLong(mSortColumns[j]);
    209                     boolean comparison = (mAscending) ? current < smallest : current > smallest;
    210                     if (smallestIdx < 0 || comparison) {
    211                         smallest = current;
    212                         smallestIdx = j;
    213                     }
    214                 }
    215                 if (i == newPosition) {
    216                     break;
    217                 }
    218                 if (mCursors[smallestIdx] != null) {
    219                     mCursors[smallestIdx].moveToNext();
    220                 }
    221             }
    222         }
    223         mCursor = mCursors[smallestIdx];
    224         mCursorIndex = smallestIdx;
    225         mRowNumCache[cache_entry] = newPosition;
    226         mCursorCache[cache_entry] = smallestIdx;
    227         for (int i = 0; i < length; i++) {
    228             if (mCursors[i] != null) {
    229                 mCurRowNumCache[cache_entry][i] = mCursors[i].getPosition();
    230             }
    231         }
    232         mLastCacheHit = -1;
    233         return true;
    234     }
    235 
    236     @Override
    237     public String getString(int column) {
    238         return mCursor.getString(column);
    239     }
    240 
    241     @Override
    242     public short getShort(int column) {
    243         return mCursor.getShort(column);
    244     }
    245 
    246     @Override
    247     public int getInt(int column) {
    248         return mCursor.getInt(column);
    249     }
    250 
    251     @Override
    252     public long getLong(int column) {
    253         return mCursor.getLong(column);
    254     }
    255 
    256     @Override
    257     public float getFloat(int column) {
    258         return mCursor.getFloat(column);
    259     }
    260 
    261     @Override
    262     public double getDouble(int column) {
    263         return mCursor.getDouble(column);
    264     }
    265 
    266     @Override
    267     public boolean isNull(int column) {
    268         return mCursor.isNull(column);
    269     }
    270 
    271     @Override
    272     public byte[] getBlob(int column) {
    273         return mCursor.getBlob(column);
    274     }
    275 
    276     @Override
    277     public String[] getColumnNames() {
    278         if (mCursor != null) {
    279             return mCursor.getColumnNames();
    280         } else {
    281             // All of the cursors may be empty, but they can still return
    282             // this information.
    283             int length = mCursors.length;
    284             for (int i = 0; i < length; i++) {
    285                 if (mCursors[i] != null) {
    286                     return mCursors[i].getColumnNames();
    287                 }
    288             }
    289             throw new IllegalStateException("No cursor that can return names");
    290         }
    291     }
    292 
    293     @Override
    294     public void deactivate() {
    295         int length = mCursors.length;
    296         for (int i = 0; i < length; i++) {
    297             if (mCursors[i] == null)
    298                 continue;
    299             mCursors[i].deactivate();
    300         }
    301     }
    302 
    303     @Override
    304     public void close() {
    305         int length = mCursors.length;
    306         for (int i = 0; i < length; i++) {
    307             if (mCursors[i] == null)
    308                 continue;
    309             mCursors[i].close();
    310         }
    311     }
    312 
    313     @Override
    314     public void registerDataSetObserver(DataSetObserver observer) {
    315         int length = mCursors.length;
    316         for (int i = 0; i < length; i++) {
    317             if (mCursors[i] != null) {
    318                 mCursors[i].registerDataSetObserver(observer);
    319             }
    320         }
    321     }
    322 
    323     @Override
    324     public void unregisterDataSetObserver(DataSetObserver observer) {
    325         int length = mCursors.length;
    326         for (int i = 0; i < length; i++) {
    327             if (mCursors[i] != null) {
    328                 mCursors[i].unregisterDataSetObserver(observer);
    329             }
    330         }
    331     }
    332 
    333     @Override
    334     public boolean requery() {
    335         int length = mCursors.length;
    336         for (int i = 0; i < length; i++) {
    337             if (mCursors[i] == null)
    338                 continue;
    339 
    340             if (mCursors[i].requery() == false) {
    341                 return false;
    342             }
    343         }
    344 
    345         return true;
    346     }
    347 
    348     public int getCurrentCursorIndex() {
    349         return mCursorIndex;
    350     }
    351 }
    352