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.ContentObserver;
     21 import android.database.Cursor;
     22 import android.database.DataSetObserver;
     23 import android.os.Handler;
     24 import android.provider.BaseColumns;
     25 import android.view.LayoutInflater;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     28 import android.webkit.DateSorter;
     29 import android.widget.ExpandableListAdapter;
     30 import android.widget.ExpandableListView;
     31 import android.widget.TextView;
     32 
     33 import java.util.Vector;
     34 
     35 /**
     36  * ExpandableListAdapter which separates data into categories based on date.
     37  * Used for History and Downloads.
     38  */
     39 public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
     40     // Array for each of our bins.  Each entry represents how many items are
     41     // in that bin.
     42     private int mItemMap[];
     43     // This is our GroupCount.  We will have at most DateSorter.DAY_COUNT
     44     // bins, less if the user has no items in one or more bins.
     45     private int mNumberOfBins;
     46     private Vector<DataSetObserver> mObservers;
     47     private Cursor mCursor;
     48     private DateSorter mDateSorter;
     49     private int mDateIndex;
     50     private int mIdIndex;
     51     private Context mContext;
     52 
     53     private class ChangeObserver extends ContentObserver {
     54         public ChangeObserver() {
     55             super(new Handler());
     56         }
     57 
     58         @Override
     59         public boolean deliverSelfNotifications() {
     60             return true;
     61         }
     62 
     63         @Override
     64         public void onChange(boolean selfChange) {
     65             refreshData();
     66         }
     67     }
     68 
     69     public DateSortedExpandableListAdapter(Context context, Cursor cursor,
     70             int dateIndex) {
     71         mContext = context;
     72         mDateSorter = new DateSorter(context);
     73         mObservers = new Vector<DataSetObserver>();
     74         mCursor = cursor;
     75         mIdIndex = cursor.getColumnIndexOrThrow(BaseColumns._ID);
     76         cursor.registerContentObserver(new ChangeObserver());
     77         mDateIndex = dateIndex;
     78         buildMap();
     79     }
     80 
     81     /**
     82      * Set up the bins for determining which items belong to which groups.
     83      */
     84     private void buildMap() {
     85         // The cursor is sorted by date
     86         // The ItemMap will store the number of items in each bin.
     87         int array[] = new int[DateSorter.DAY_COUNT];
     88         // Zero out the array.
     89         for (int j = 0; j < DateSorter.DAY_COUNT; j++) {
     90             array[j] = 0;
     91         }
     92         mNumberOfBins = 0;
     93         int dateIndex = -1;
     94         if (mCursor.moveToFirst() && mCursor.getCount() > 0) {
     95             while (!mCursor.isAfterLast()) {
     96                 long date = getLong(mDateIndex);
     97                 int index = mDateSorter.getIndex(date);
     98                 if (index > dateIndex) {
     99                     mNumberOfBins++;
    100                     if (index == DateSorter.DAY_COUNT - 1) {
    101                         // We are already in the last bin, so it will
    102                         // include all the remaining items
    103                         array[index] = mCursor.getCount()
    104                                 - mCursor.getPosition();
    105                         break;
    106                     }
    107                     dateIndex = index;
    108                 }
    109                 array[dateIndex]++;
    110                 mCursor.moveToNext();
    111             }
    112         }
    113         mItemMap = array;
    114     }
    115 
    116     /**
    117      * Get the byte array at cursorIndex from the Cursor.  Assumes the Cursor
    118      * has already been moved to the correct position.  Along with
    119      * {@link #getInt} and {@link #getString}, these are provided so the client
    120      * does not need to access the Cursor directly
    121      * @param cursorIndex Index to query the Cursor.
    122      * @return corresponding byte array from the Cursor.
    123      */
    124     /* package */ byte[] getBlob(int cursorIndex) {
    125         return mCursor.getBlob(cursorIndex);
    126     }
    127 
    128     /* package */ Context getContext() {
    129         return mContext;
    130     }
    131 
    132     /**
    133      * Get the integer at cursorIndex from the Cursor.  Assumes the Cursor has
    134      * already been moved to the correct position.  Along with
    135      * {@link #getBlob} and {@link #getString}, these are provided so the client
    136      * does not need to access the Cursor directly
    137      * @param cursorIndex Index to query the Cursor.
    138      * @return corresponding integer from the Cursor.
    139      */
    140     /* package */ int getInt(int cursorIndex) {
    141         return mCursor.getInt(cursorIndex);
    142     }
    143 
    144     /**
    145      * Get the long at cursorIndex from the Cursor.  Assumes the Cursor has
    146      * already been moved to the correct position.
    147      */
    148     /* package */ long getLong(int cursorIndex) {
    149         return mCursor.getLong(cursorIndex);
    150     }
    151 
    152     /**
    153      * Get the String at cursorIndex from the Cursor.  Assumes the Cursor has
    154      * already been moved to the correct position.  Along with
    155      * {@link #getInt} and {@link #getInt}, these are provided so the client
    156      * does not need to access the Cursor directly
    157      * @param cursorIndex Index to query the Cursor.
    158      * @return corresponding String from the Cursor.
    159      */
    160     /* package */ String getString(int cursorIndex) {
    161         return mCursor.getString(cursorIndex);
    162     }
    163 
    164     /**
    165      * Determine which group an item belongs to.
    166      * @param childId ID of the child view in question.
    167      * @return int Group position of the containing group.
    168     /* package */ int groupFromChildId(long childId) {
    169         int group = -1;
    170         for (mCursor.moveToFirst(); !mCursor.isAfterLast();
    171                 mCursor.moveToNext()) {
    172             if (getLong(mIdIndex) == childId) {
    173                 int bin = mDateSorter.getIndex(getLong(mDateIndex));
    174                 // bin is the same as the group if the number of bins is the
    175                 // same as DateSorter
    176                 if (mDateSorter.DAY_COUNT == mNumberOfBins) return bin;
    177                 // There are some empty bins.  Find the corresponding group.
    178                 group = 0;
    179                 for (int i = 0; i < bin; i++) {
    180                     if (mItemMap[i] != 0) group++;
    181                 }
    182                 break;
    183             }
    184         }
    185         return group;
    186     }
    187 
    188     /**
    189      * Translates from a group position in the ExpandableList to a bin.  This is
    190      * necessary because some groups have no history items, so we do not include
    191      * those in the ExpandableList.
    192      * @param groupPosition Position in the ExpandableList's set of groups
    193      * @return The corresponding bin that holds that group.
    194      */
    195     private int groupPositionToBin(int groupPosition) {
    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 (mCursor.isClosed()) return false;
    245         groupPosition = groupPositionToBin(groupPosition);
    246         int index = childPosition;
    247         for (int i = 0; i < groupPosition; i++) {
    248             index += mItemMap[i];
    249         }
    250         return mCursor.moveToPosition(index);
    251     }
    252 
    253     /* package */ void refreshData() {
    254         if (mCursor.isClosed()) {
    255             return;
    256         }
    257         mCursor.requery();
    258         buildMap();
    259         for (DataSetObserver o : mObservers) {
    260             o.onChanged();
    261         }
    262     }
    263 
    264     public View getGroupView(int groupPosition, boolean isExpanded,
    265             View convertView, ViewGroup parent) {
    266         TextView item;
    267         if (null == convertView || !(convertView instanceof TextView)) {
    268             LayoutInflater factory = LayoutInflater.from(mContext);
    269             item = (TextView) factory.inflate(R.layout.history_header, null);
    270         } else {
    271             item = (TextView) convertView;
    272         }
    273         String label = mDateSorter.getLabel(groupPositionToBin(groupPosition));
    274         item.setText(label);
    275         return item;
    276     }
    277 
    278     public View getChildView(int groupPosition, int childPosition,
    279             boolean isLastChild, View convertView, ViewGroup parent) {
    280         return null;
    281     }
    282 
    283     public boolean areAllItemsEnabled() {
    284         return true;
    285     }
    286 
    287     public boolean isChildSelectable(int groupPosition, int childPosition) {
    288         return true;
    289     }
    290 
    291     public int getGroupCount() {
    292         return mNumberOfBins;
    293     }
    294 
    295     public int getChildrenCount(int groupPosition) {
    296         return mItemMap[groupPositionToBin(groupPosition)];
    297     }
    298 
    299     public Object getGroup(int groupPosition) {
    300         return null;
    301     }
    302 
    303     public Object getChild(int groupPosition, int childPosition) {
    304         return null;
    305     }
    306 
    307     public long getGroupId(int groupPosition) {
    308         return groupPosition;
    309     }
    310 
    311     public long getChildId(int groupPosition, int childPosition) {
    312         if (moveCursorToChildPosition(groupPosition, childPosition)) {
    313             return getLong(mIdIndex);
    314         }
    315         return 0;
    316     }
    317 
    318     public boolean hasStableIds() {
    319         return true;
    320     }
    321 
    322     public void registerDataSetObserver(DataSetObserver observer) {
    323         mObservers.add(observer);
    324     }
    325 
    326     public void unregisterDataSetObserver(DataSetObserver observer) {
    327         mObservers.remove(observer);
    328     }
    329 
    330     public void onGroupExpanded(int groupPosition) {
    331     }
    332 
    333     public void onGroupCollapsed(int groupPosition) {
    334     }
    335 
    336     public long getCombinedChildId(long groupId, long childId) {
    337         return childId;
    338     }
    339 
    340     public long getCombinedGroupId(long groupId) {
    341         return groupId;
    342     }
    343 
    344     public boolean isEmpty() {
    345         return mCursor.isClosed() || mCursor.getCount() == 0;
    346     }
    347 }
    348