/* * Zirco Browser for Android * * Copyright (C) 2010 - 2011 J. Devauchelle and contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 3 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ package org.shadowsocks.zirco.model.adapters; import org.shadowsocks.R; import org.shadowsocks.zirco.model.items.HistoryItem; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.provider.BaseColumns; import android.provider.Browser; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.webkit.DateSorter; import android.widget.AbsListView; import android.widget.BaseExpandableListAdapter; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; /** * Custom adapter for displaying history, splitted in bins. Adapted from: * https:/ * /github.com/CyanogenMod/android_packages_apps_Browser/blob/gingerbread/ * src/com/android/browser/BrowserHistoryPage.java * http://grepcode.com/file/repository * .grepcode.com/java/ext/com.google.android/android * -apps/2.2_r1.1/com/android/browser * /DateSortedExpandableListAdapter.java/?v=source */ public class HistoryExpandableListAdapter extends BaseExpandableListAdapter { private LayoutInflater mInflater = null; private int[] mItemMap; private int mNumberOfBins; private int mIdIndex; private DateSorter mDateSorter; private Context mContext; private Cursor mCursor; private int mDateIndex; private int mFaviconSize; /** * Constructor. * * @param context * The current context. * @param cursor * The data cursor. * @param dateIndex * The date index ? */ public HistoryExpandableListAdapter(Context context, Cursor cursor, int dateIndex, int faviconSize) { mContext = context; mCursor = cursor; mDateIndex = dateIndex; mFaviconSize = faviconSize; mDateSorter = new DateSorter(mContext); mIdIndex = cursor.getColumnIndexOrThrow(BaseColumns._ID); mInflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); buildMap(); } /** * Split the data in the cursor into several "bins": today, yesterday, last * 7 days, last month, older. */ private void buildMap() { 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; } @Override public Object getChild(int groupPosition, int childPosition) { moveCursorToChildPosition(groupPosition, childPosition); String title = mCursor .getString(Browser.HISTORY_PROJECTION_TITLE_INDEX); if (title.length() > 11) title = title.substring(0, 10) + "..."; return new HistoryItem( mCursor.getLong(Browser.HISTORY_PROJECTION_ID_INDEX), title, mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX), mCursor.getBlob(Browser.HISTORY_PROJECTION_FAVICON_INDEX)); } @Override public long getChildId(int groupPosition, int childPosition) { if (moveCursorToChildPosition(groupPosition, childPosition)) { return getLong(mIdIndex); } return 0; } @Override public int getChildrenCount(int groupPosition) { return mItemMap[groupPositionToBin(groupPosition)]; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { View view = getCustomChildView(); TextView titleView = (TextView) view .findViewById(R.id.HistoryRow_Title); HistoryItem item = (HistoryItem) getChild(groupPosition, childPosition); titleView.setText(item.getTitle()); TextView urlView = (TextView) view.findViewById(R.id.HistoryRow_Url); urlView.setText(item.getUrl()); ImageView faviconView = (ImageView) view .findViewById(R.id.HistoryRow_Thumbnail); Bitmap favicon = item.getFavicon(); if (favicon != null) { BitmapDrawable icon = new BitmapDrawable(favicon); Bitmap bm = Bitmap.createBitmap(mFaviconSize, mFaviconSize, Bitmap.Config.ARGB_4444); Canvas canvas = new Canvas(bm); icon.setBounds(0, 0, mFaviconSize, mFaviconSize); icon.draw(canvas); faviconView.setImageBitmap(bm); // faviconView.setImageBitmap(item.getFavicon()); } else { faviconView.setImageResource(R.drawable.fav_icn_unknown); } return view; } /** * Create a new child view. * * @return The created view. */ private View getCustomChildView() { LinearLayout view = (LinearLayout) mInflater.inflate( R.layout.history_row, null, false); return view; } /** * Create a new view. * * @return The created view. */ private TextView getGenericView() { // Layout parameters for the ExpandableListView AbsListView.LayoutParams lp = new AbsListView.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, (int) (45 * mContext .getResources().getDisplayMetrics().density)); TextView textView = new TextView(mContext); textView.setLayoutParams(lp); // Center the text vertically textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); // Set the text starting position textView.setPadding((int) (35 * mContext.getResources() .getDisplayMetrics().density), 0, 0, 0); return textView; } @Override public Object getGroup(int groupPosition) { int binIndex = groupPositionToBin(groupPosition); switch (binIndex) { case 0: return mContext.getResources().getString( R.string.HistoryListActivity_Today); case 1: return mContext.getResources().getString( R.string.HistoryListActivity_Yesterday); case 2: return mContext.getResources().getString( R.string.HistoryListActivity_LastSevenDays); case 3: return mContext.getResources().getString( R.string.HistoryListActivity_LastMonth); default: return mContext.getResources().getString( R.string.HistoryListActivity_Older); } } @Override public int getGroupCount() { return mNumberOfBins; } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { TextView textView = getGenericView(); textView.setText(getGroup(groupPosition).toString()); return textView; } /** * Get a long-typed data from mCursor. * * @param cursorIndex * The column index. * @return The long data. */ private long getLong(int cursorIndex) { return mCursor.getLong(cursorIndex); } /** * 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; } @Override public boolean hasStableIds() { return true; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } /** * Move the cursor to the record corresponding to the given group position * and child position. * * @param groupPosition * The group position. * @param childPosition * The child position. * @return True if the move has succeeded. */ private 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); } /** * Determine which group an item belongs to. * * @param childId * ID of the child view in question. * @return int Group position of the containing group. */ /* * private 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; } * * private 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); } */ }