Home | History | Annotate | Download | only in browser
      1 /*
      2  * Copyright (C) 2010 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.browser;
     18 
     19 import android.content.Context;
     20 import android.database.Cursor;
     21 import android.database.DataSetObserver;
     22 import android.view.LayoutInflater;
     23 import android.view.View;
     24 import android.view.ViewGroup;
     25 import android.webkit.DateSorter;
     26 import android.widget.BaseExpandableListAdapter;
     27 import android.widget.ExpandableListView;
     28 import android.widget.TextView;
     29 
     30 /**
     31  * ExpandableListAdapter which separates data into categories based on date.
     32  * Used for History and Downloads.
     33  */
     34 public class DateSortedExpandableListAdapter extends BaseExpandableListAdapter {
     35     // Array for each of our bins.  Each entry represents how many items are
     36     // in that bin.
     37     private int mItemMap[];
     38     // This is our GroupCount.  We will have at most DateSorter.DAY_COUNT
     39     // bins, less if the user has no items in one or more bins.
     40     private int mNumberOfBins;
     41     private Cursor mCursor;
     42     private DateSorter mDateSorter;
     43     private int mDateIndex;
     44     private int mIdIndex;
     45     private Context mContext;
     46 
     47     boolean mDataValid;
     48 
     49     DataSetObserver mDataSetObserver = new DataSetObserver() {
     50         @Override
     51         public void onChanged() {
     52             mDataValid = true;
     53             notifyDataSetChanged();
     54         }
     55 
     56         @Override
     57         public void onInvalidated() {
     58             mDataValid = false;
     59             notifyDataSetInvalidated();
     60         }
     61     };
     62 
     63     public DateSortedExpandableListAdapter(Context context, int dateIndex) {
     64         mContext = context;
     65         mDateSorter = new DateSorter(context);
     66         mDateIndex = dateIndex;
     67         mDataValid = false;
     68         mIdIndex = -1;
     69     }
     70 
     71     /**
     72      * Set up the bins for determining which items belong to which groups.
     73      */
     74     private void buildMap() {
     75         // The cursor is sorted by date
     76         // The ItemMap will store the number of items in each bin.
     77         int array[] = new int[DateSorter.DAY_COUNT];
     78         // Zero out the array.
     79         for (int j = 0; j < DateSorter.DAY_COUNT; j++) {
     80             array[j] = 0;
     81         }
     82         mNumberOfBins = 0;
     83         int dateIndex = -1;
     84         if (mCursor.moveToFirst() && mCursor.getCount() > 0) {
     85             while (!mCursor.isAfterLast()) {
     86                 long date = getLong(mDateIndex);
     87                 int index = mDateSorter.getIndex(date);
     88                 if (index > dateIndex) {
     89                     mNumberOfBins++;
     90                     if (index == DateSorter.DAY_COUNT - 1) {
     91                         // We are already in the last bin, so it will
     92                         // include all the remaining items
     93                         array[index] = mCursor.getCount()
     94                                 - mCursor.getPosition();
     95                         break;
     96                     }
     97                     dateIndex = index;
     98                 }
     99                 array[dateIndex]++;
    100                 mCursor.moveToNext();
    101             }
    102         }
    103         mItemMap = array;
    104     }
    105 
    106     /**
    107      * Get the byte array at cursorIndex from the Cursor.  Assumes the Cursor
    108      * has already been moved to the correct position.  Along with
    109      * {@link #getInt} and {@link #getString}, these are provided so the client
    110      * does not need to access the Cursor directly
    111      * @param cursorIndex Index to query the Cursor.
    112      * @return corresponding byte array from the Cursor.
    113      */
    114     /* package */ byte[] getBlob(int cursorIndex) {
    115         if (!mDataValid) return null;
    116         return mCursor.getBlob(cursorIndex);
    117     }
    118 
    119     /* package */ Context getContext() {
    120         return mContext;
    121     }
    122 
    123     /**
    124      * Get the integer at cursorIndex from the Cursor.  Assumes the Cursor has
    125      * already been moved to the correct position.  Along with
    126      * {@link #getBlob} and {@link #getString}, these are provided so the client
    127      * does not need to access the Cursor directly
    128      * @param cursorIndex Index to query the Cursor.
    129      * @return corresponding integer from the Cursor.
    130      */
    131     /* package */ int getInt(int cursorIndex) {
    132         if (!mDataValid) return 0;
    133         return mCursor.getInt(cursorIndex);
    134     }
    135 
    136     /**
    137      * Get the long at cursorIndex from the Cursor.  Assumes the Cursor has
    138      * already been moved to the correct position.
    139      */
    140     /* package */ long getLong(int cursorIndex) {
    141         if (!mDataValid) return 0;
    142         return mCursor.getLong(cursorIndex);
    143     }
    144 
    145     /**
    146      * Get the String at cursorIndex from the Cursor.  Assumes the Cursor has
    147      * already been moved to the correct position.  Along with
    148      * {@link #getInt} and {@link #getInt}, these are provided so the client
    149      * does not need to access the Cursor directly
    150      * @param cursorIndex Index to query the Cursor.
    151      * @return corresponding String from the Cursor.
    152      */
    153     /* package */ String getString(int cursorIndex) {
    154         if (!mDataValid) return null;
    155         return mCursor.getString(cursorIndex);
    156     }
    157 
    158     /**
    159      * Determine which group an item belongs to.
    160      * @param childId ID of the child view in question.
    161      * @return int Group position of the containing group.
    162     /* package */ int groupFromChildId(long childId) {
    163         if (!mDataValid) return -1;
    164         int group = -1;
    165         for (mCursor.moveToFirst(); !mCursor.isAfterLast();
    166                 mCursor.moveToNext()) {
    167             if (getLong(mIdIndex) == childId) {
    168                 int bin = mDateSorter.getIndex(getLong(mDateIndex));
    169                 // bin is the same as the group if the number of bins is the
    170                 // same as DateSorter
    171                 if (DateSorter.DAY_COUNT == mNumberOfBins) {
    172                     return bin;
    173                 }
    174                 // There are some empty bins.  Find the corresponding group.
    175                 group = 0;
    176                 for (int i = 0; i < bin; i++) {
    177                     if (mItemMap[i] != 0) {
    178                         group++;
    179                     }
    180                 }
    181                 break;
    182             }
    183         }
    184         return group;
    185     }
    186 
    187     /**
    188      * Translates from a group position in the ExpandableList to a bin.  This is
    189      * necessary because some groups have no history items, so we do not include
    190      * those in the ExpandableList.
    191      * @param groupPosition Position in the ExpandableList's set of groups
    192      * @return The corresponding bin that holds that group.
    193      */
    194     private int groupPositionToBin(int groupPosition) {
    195         if (!mDataValid) return -1;
    196         if (groupPosition < 0 || groupPosition >= DateSorter.DAY_COUNT) {
    197             throw new AssertionError("group position out of range");
    198         }
    199         if (DateSorter.DAY_COUNT == mNumberOfBins || 0 == mNumberOfBins) {
    200             // In the first case, we have exactly the same number of bins
    201             // as our maximum possible, so there is no need to do a
    202             // conversion
    203             // The second statement is in case this method gets called when
    204             // the array is empty, in which case the provided groupPosition
    205             // will do fine.
    206             return groupPosition;
    207         }
    208         int arrayPosition = -1;
    209         while (groupPosition > -1) {
    210             arrayPosition++;
    211             if (mItemMap[arrayPosition] != 0) {
    212                 groupPosition--;
    213             }
    214         }
    215         return arrayPosition;
    216     }
    217 
    218     /**
    219      * Move the cursor to the position indicated.
    220      * @param packedPosition Position in packed position representation.
    221      * @return True on success, false otherwise.
    222      */
    223     boolean moveCursorToPackedChildPosition(long packedPosition) {
    224         if (ExpandableListView.getPackedPositionType(packedPosition) !=
    225                 ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
    226             return false;
    227         }
    228         int groupPosition = ExpandableListView.getPackedPositionGroup(
    229                 packedPosition);
    230         int childPosition = ExpandableListView.getPackedPositionChild(
    231                 packedPosition);
    232         return moveCursorToChildPosition(groupPosition, childPosition);
    233     }
    234 
    235     /**
    236      * Move the cursor the the position indicated.
    237      * @param groupPosition Index of the group containing the desired item.
    238      * @param childPosition Index of the item within the specified group.
    239      * @return boolean False if the cursor is closed, so the Cursor was not
    240      *      moved.  True on success.
    241      */
    242     /* package */ boolean moveCursorToChildPosition(int groupPosition,
    243             int childPosition) {
    244         if (!mDataValid || mCursor.isClosed()) {
    245             return false;
    246         }
    247         groupPosition = groupPositionToBin(groupPosition);
    248         int index = childPosition;
    249         for (int i = 0; i < groupPosition; i++) {
    250             index += mItemMap[i];
    251         }
    252         return mCursor.moveToPosition(index);
    253     }
    254 
    255     public void changeCursor(Cursor cursor) {
    256         if (cursor == mCursor) {
    257             return;
    258         }
    259         if (mCursor != null) {
    260             mCursor.unregisterDataSetObserver(mDataSetObserver);
    261             mCursor.close();
    262         }
    263         mCursor = cursor;
    264         if (cursor != null) {
    265             cursor.registerDataSetObserver(mDataSetObserver);
    266             mIdIndex = cursor.getColumnIndexOrThrow("_id");
    267             mDataValid = true;
    268             buildMap();
    269             // notify the observers about the new cursor
    270             notifyDataSetChanged();
    271         } else {
    272             mIdIndex = -1;
    273             mDataValid = false;
    274             // notify the observers about the lack of a data set
    275             notifyDataSetInvalidated();
    276         }
    277     }
    278 
    279     @Override
    280     public View getGroupView(int groupPosition, boolean isExpanded,
    281             View convertView, ViewGroup parent) {
    282         if (!mDataValid) throw new IllegalStateException("Data is not valid");
    283         TextView item;
    284         if (null == convertView || !(convertView instanceof TextView)) {
    285             LayoutInflater factory = LayoutInflater.from(mContext);
    286             item = (TextView) factory.inflate(R.layout.history_header, null);
    287         } else {
    288             item = (TextView) convertView;
    289         }
    290         String label = mDateSorter.getLabel(groupPositionToBin(groupPosition));
    291         item.setText(label);
    292         return item;
    293     }
    294 
    295     @Override
    296     public View getChildView(int groupPosition, int childPosition,
    297             boolean isLastChild, View convertView, ViewGroup parent) {
    298         if (!mDataValid) throw new IllegalStateException("Data is not valid");
    299         return null;
    300     }
    301 
    302     @Override
    303     public boolean areAllItemsEnabled() {
    304         return true;
    305     }
    306 
    307     @Override
    308     public boolean isChildSelectable(int groupPosition, int childPosition) {
    309         return true;
    310     }
    311 
    312     @Override
    313     public int getGroupCount() {
    314         if (!mDataValid) return 0;
    315         return mNumberOfBins;
    316     }
    317 
    318     @Override
    319     public int getChildrenCount(int groupPosition) {
    320         if (!mDataValid) return 0;
    321         return mItemMap[groupPositionToBin(groupPosition)];
    322     }
    323 
    324     @Override
    325     public Object getGroup(int groupPosition) {
    326         return null;
    327     }
    328 
    329     @Override
    330     public Object getChild(int groupPosition, int childPosition) {
    331         return null;
    332     }
    333 
    334     @Override
    335     public long getGroupId(int groupPosition) {
    336         if (!mDataValid) return 0;
    337         return groupPosition;
    338     }
    339 
    340     @Override
    341     public long getChildId(int groupPosition, int childPosition) {
    342         if (!mDataValid) return 0;
    343         if (moveCursorToChildPosition(groupPosition, childPosition)) {
    344             return getLong(mIdIndex);
    345         }
    346         return 0;
    347     }
    348 
    349     @Override
    350     public boolean hasStableIds() {
    351         return true;
    352     }
    353 
    354     @Override
    355     public void onGroupExpanded(int groupPosition) {
    356     }
    357 
    358     @Override
    359     public void onGroupCollapsed(int groupPosition) {
    360     }
    361 
    362     @Override
    363     public long getCombinedChildId(long groupId, long childId) {
    364         if (!mDataValid) return 0;
    365         return childId;
    366     }
    367 
    368     @Override
    369     public long getCombinedGroupId(long groupId) {
    370         if (!mDataValid) return 0;
    371         return groupId;
    372     }
    373 
    374     @Override
    375     public boolean isEmpty() {
    376         return !mDataValid || mCursor == null || mCursor.isClosed() || mCursor.getCount() == 0;
    377     }
    378 }
    379