blob: f5d70770fb1a23a776474935ed9aed87313fef85 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.providers.downloads.ui;
import android.app.DownloadManager;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.DateSorter;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;
import java.util.Vector;
/**
* ExpandableListAdapter which separates data into categories based on date. Copied from
* packages/apps/Browser.
*/
public class DateSortedExpandableListAdapter implements ExpandableListAdapter {
// Array for each of our bins. Each entry represents how many items are
// in that bin.
private int mItemMap[];
// This is our GroupCount. We will have at most DateSorter.DAY_COUNT
// bins, less if the user has no items in one or more bins.
private int mNumberOfBins;
private Vector<DataSetObserver> mObservers;
private Cursor mCursor;
private DateSorter mDateSorter;
private int mDateIndex;
private int mIdIndex;
private Context mContext;
private class ChangeObserver extends ContentObserver {
public ChangeObserver() {
super(new Handler());
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public void onChange(boolean selfChange) {
refreshData();
}
}
private class MyDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
buildMap();
for (DataSetObserver o : mObservers) {
o.onChanged();
}
}
}
public DateSortedExpandableListAdapter(Context context, Cursor cursor,
int dateIndex) {
mContext = context;
mDateSorter = new DateSorter(context);
mObservers = new Vector<DataSetObserver>();
mCursor = cursor;
mIdIndex = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID);
cursor.registerContentObserver(new ChangeObserver());
cursor.registerDataSetObserver(new MyDataSetObserver());
mDateIndex = dateIndex;
buildMap();
}
/**
* Set up the bins for determining which items belong to which groups.
*/
private void buildMap() {
// The cursor is sorted by date
// The ItemMap will store the number of items in each bin.
int array[] = new int[DateSorter.DAY_COUNT];
// Zero out the array.
for (int j = 0; j < DateSorter.DAY_COUNT; j++) {
array[j] = 0;
}
mNumberOfBins = 0;
int dateIndex = -1;
if (mCursor.moveToFirst() && mCursor.getCount() > 0) {
while (!mCursor.isAfterLast()) {
long date = getLong(mDateIndex);
int index = mDateSorter.getIndex(date);
if (index > dateIndex) {
mNumberOfBins++;
if (index == DateSorter.DAY_COUNT - 1) {
// We are already in the last bin, so it will
// include all the remaining items
array[index] = mCursor.getCount()
- mCursor.getPosition();
break;
}
dateIndex = index;
}
array[dateIndex]++;
mCursor.moveToNext();
}
}
mItemMap = array;
}
/**
* Get the byte array at cursorIndex from the Cursor. Assumes the Cursor
* has already been moved to the correct position. Along with
* {@link #getInt} and {@link #getString}, these are provided so the client
* does not need to access the Cursor directly
* @param cursorIndex Index to query the Cursor.
* @return corresponding byte array from the Cursor.
*/
/* package */ byte[] getBlob(int cursorIndex) {
return mCursor.getBlob(cursorIndex);
}
/* package */ Context getContext() {
return mContext;
}
/**
* Get the integer at cursorIndex from the Cursor. Assumes the Cursor has
* already been moved to the correct position. Along with
* {@link #getBlob} and {@link #getString}, these are provided so the client
* does not need to access the Cursor directly
* @param cursorIndex Index to query the Cursor.
* @return corresponding integer from the Cursor.
*/
/* package */ int getInt(int cursorIndex) {
return mCursor.getInt(cursorIndex);
}
/**
* Get the long at cursorIndex from the Cursor. Assumes the Cursor has
* already been moved to the correct position.
*/
/* package */ long getLong(int cursorIndex) {
return mCursor.getLong(cursorIndex);
}
/**
* Get the String at cursorIndex from the Cursor. Assumes the Cursor has
* already been moved to the correct position. Along with
* {@link #getInt} and {@link #getInt}, these are provided so the client
* does not need to access the Cursor directly
* @param cursorIndex Index to query the Cursor.
* @return corresponding String from the Cursor.
*/
/* package */ String getString(int cursorIndex) {
return mCursor.getString(cursorIndex);
}
/**
* Determine which group an item belongs to.
* @param childId ID of the child view in question.
* @return int Group position of the containing group.
/* package */ int groupFromChildId(long childId) {
int group = -1;
for (mCursor.moveToFirst(); !mCursor.isAfterLast();
mCursor.moveToNext()) {
if (getLong(mIdIndex) == childId) {
int bin = mDateSorter.getIndex(getLong(mDateIndex));
// bin is the same as the group if the number of bins is the
// same as DateSorter
if (DateSorter.DAY_COUNT == mNumberOfBins) return bin;
// There are some empty bins. Find the corresponding group.
group = 0;
for (int i = 0; i < bin; i++) {
if (mItemMap[i] != 0) group++;
}
break;
}
}
return group;
}
/**
* Translates from a group position in the ExpandableList to a bin. This is
* necessary because some groups have no history items, so we do not include
* those in the ExpandableList.
* @param groupPosition Position in the ExpandableList's set of groups
* @return The corresponding bin that holds that group.
*/
private int groupPositionToBin(int groupPosition) {
if (groupPosition < 0 || groupPosition >= DateSorter.DAY_COUNT) {
throw new AssertionError("group position out of range");
}
if (DateSorter.DAY_COUNT == mNumberOfBins || 0 == mNumberOfBins) {
// In the first case, we have exactly the same number of bins
// as our maximum possible, so there is no need to do a
// conversion
// The second statement is in case this method gets called when
// the array is empty, in which case the provided groupPosition
// will do fine.
return groupPosition;
}
int arrayPosition = -1;
while (groupPosition > -1) {
arrayPosition++;
if (mItemMap[arrayPosition] != 0) {
groupPosition--;
}
}
return arrayPosition;
}
/**
* Move the cursor to the position indicated.
* @param packedPosition Position in packed position representation.
* @return True on success, false otherwise.
*/
boolean moveCursorToPackedChildPosition(long packedPosition) {
if (ExpandableListView.getPackedPositionType(packedPosition) !=
ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
return false;
}
int groupPosition = ExpandableListView.getPackedPositionGroup(
packedPosition);
int childPosition = ExpandableListView.getPackedPositionChild(
packedPosition);
return moveCursorToChildPosition(groupPosition, childPosition);
}
/**
* Move the cursor the the position indicated.
* @param groupPosition Index of the group containing the desired item.
* @param childPosition Index of the item within the specified group.
* @return boolean False if the cursor is closed, so the Cursor was not
* moved. True on success.
*/
/* package */ boolean moveCursorToChildPosition(int groupPosition,
int childPosition) {
if (mCursor.isClosed()) return false;
groupPosition = groupPositionToBin(groupPosition);
int index = childPosition;
for (int i = 0; i < groupPosition; i++) {
index += mItemMap[i];
}
return mCursor.moveToPosition(index);
}
/* package */ void refreshData() {
if (mCursor.isClosed()) {
return;
}
mCursor.requery();
}
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
TextView item;
if (null == convertView || !(convertView instanceof TextView)) {
LayoutInflater factory = LayoutInflater.from(mContext);
item = (TextView) factory.inflate(R.layout.list_group_header, parent, false);
} else {
item = (TextView) convertView;
}
String label = mDateSorter.getLabel(groupPositionToBin(groupPosition));
item.setText(label);
return item;
}
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
return null;
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
public int getGroupCount() {
return mNumberOfBins;
}
public int getChildrenCount(int groupPosition) {
return mItemMap[groupPositionToBin(groupPosition)];
}
public Object getGroup(int groupPosition) {
return null;
}
public Object getChild(int groupPosition, int childPosition) {
return null;
}
public long getGroupId(int groupPosition) {
return groupPosition;
}
public long getChildId(int groupPosition, int childPosition) {
if (moveCursorToChildPosition(groupPosition, childPosition)) {
return getLong(mIdIndex);
}
return 0;
}
public boolean hasStableIds() {
return true;
}
public void registerDataSetObserver(DataSetObserver observer) {
mObservers.add(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mObservers.remove(observer);
}
public void onGroupExpanded(int groupPosition) {
}
public void onGroupCollapsed(int groupPosition) {
}
public long getCombinedChildId(long groupId, long childId) {
return childId;
}
public long getCombinedGroupId(long groupId) {
return groupId;
}
public boolean isEmpty() {
return mCursor.isClosed() || mCursor.getCount() == 0;
}
}