package mobi.urika.android.widget;
import java.util.ArrayList;
import mobi.urika.android.content.LauncherIntent;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.text.Html;
import android.text.Spanned;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.TextView;
/**
*
* @author Francois DESLANDES
*
*/
public class WidgetListAdapter extends ScrollableBaseAdapter {
static final String LOG_TAG = "LauncherPP_WLA";
static final int IMPOSSIBLE_INDEX = -100;
private static final boolean LOGD = true;
private static final int NB_MAX_VIEWS_TYPES = 1;
final LayoutInflater mInflater;
final int mItemLayoutId;
final int mAppWidgetId;
final int mListViewId;
ItemMapping[] mItemMappings;
boolean mAllowRequery = true;
private final ContentResolver mContentResolver;
private final Intent mIntent;
private MyQueryHandler mAsyncQuery;
static ListViewImageManager mImageManager = ListViewImageManager.getInstance();
class RowElement {
// item data
public Object data;
public String tag;
}
class RowElementsList {
RowElement[] singleRowElementsList;
public RowElementsList(int size) {
singleRowElementsList = new RowElement[size];
}
}
public ArrayList<RowElementsList> rowsElementsList = new ArrayList<RowElementsList>();
class ItemMapping {
int type;
int layoutId;
int defaultResource;
int index;
boolean clickable;
/**
*
* @param t
* view type
* @param l
* layout id
* @param i
* index
* @param r
* default resource
* @param u
* clickable
*/
ItemMapping(int t, int l, int i, int r, boolean u) {
type = t;
layoutId = l;
defaultResource = r;
index = i;
clickable = u;
}
ItemMapping(int t, int l, int i) {
type = t;
layoutId = l;
index = i;
defaultResource = -1;
clickable = false;
}
}
public final boolean mItemChildrenClickable;
final int mItemActionUriIndex;
ComponentName mAppWidgetProvider;
// Need handler for callbacks to the UI thread
final Handler mHandler = new Handler();
// Create runnable for posting
final Runnable mGenerateDataCacheRunnable = new Runnable() {
public void run() {
if (LOGD)
Log.d(LOG_TAG, "mGenerateDataCacheRunnable start");
generateDataCache();
if (LOGD)
Log.d(LOG_TAG, "mGenerateDataCacheRunnable end");
}
};
public void clearDataCache() {
rowsElementsList.clear();
if (LOGD)
Log.d(LOG_TAG, "clearDataCache");
}
/**
*
* @param context
* remote context
* @param c
* cursor for reading data
* @param intent
* broadcast intent initiated the replacement, don't save it
* @param appWidgetId
* @param listViewId
*/
public WidgetListAdapter(Context context, Intent intent, ComponentName provider,
int appWidgetId, int listViewId) throws IllegalArgumentException {
super();
mAppWidgetId = appWidgetId;
mListViewId = listViewId;
mContentResolver = context.getContentResolver();
mIntent = intent;
mAppWidgetProvider = provider;
mInflater = LayoutInflater.from(context);
// verify is contentProvider requery is allowed
mAllowRequery = intent.getBooleanExtra(
LauncherIntent.Extra.Scroll.EXTRA_DATA_PROVIDER_ALLOW_REQUERY, false);
// Get the layout if for items
mItemLayoutId = intent.getIntExtra(LauncherIntent.Extra.Scroll.EXTRA_ITEM_LAYOUT_ID, -1);
if (mItemLayoutId <= 0)
throw (new IllegalArgumentException("The passed layout id is illegal"));
mItemChildrenClickable = intent.getBooleanExtra(
LauncherIntent.Extra.Scroll.EXTRA_ITEM_CHILDREN_CLICKABLE, false);
mItemActionUriIndex = intent.getIntExtra(
LauncherIntent.Extra.Scroll.EXTRA_ITEM_ACTION_VIEW_URI_INDEX, -1);
// Generate item mapping
generateItemMapping(intent);
mAsyncQuery=new MyQueryHandler(mContentResolver);
// Generate data cache from content provider
mHandler.post(mGenerateDataCacheRunnable);
}
/**
* Collect arrays and put them together
*
* @param t
* @param ids
* @param c
* @param u
* uri indices; could be zero, IMPOSSIBLE_INDEX will be used
*/
private void generateItemMapping(Intent intent) {
// Read the mapping data
int[] viewTypes = intent
.getIntArrayExtra(LauncherIntent.Extra.Scroll.Mapping.EXTRA_VIEW_TYPES);
int[] viewIds = intent.getIntArrayExtra(LauncherIntent.Extra.Scroll.Mapping.EXTRA_VIEW_IDS);
int[] cursorIndices = intent
.getIntArrayExtra(LauncherIntent.Extra.Scroll.Mapping.EXTRA_CURSOR_INDICES);
int[] defaultResources = intent
.getIntArrayExtra(LauncherIntent.Extra.Scroll.Mapping.EXTRA_DEFAULT_RESOURCES);
boolean[] viewClickable = intent
.getBooleanArrayExtra(LauncherIntent.Extra.Scroll.Mapping.EXTRA_VIEW_CLICKABLE);
// Check
if (viewTypes == null || viewIds == null || cursorIndices == null)
throw (new IllegalArgumentException("A mapping component is missing"));
if (viewTypes.length == viewIds.length && viewTypes.length == cursorIndices.length) {
} else
throw (new IllegalArgumentException("Mapping inconsistent"));
// Init mapping array
final int size = viewTypes.length;
mItemMappings = new ItemMapping[size];
for (int i = size - 1; i >= 0; i--)
mItemMappings[i] = new ItemMapping(viewTypes[i], viewIds[i], cursorIndices[i]);
// Put extra data in if they are available
if (viewClickable != null && viewClickable.length == size)
for (int i = size - 1; i >= 0; i--)
mItemMappings[i].clickable = viewClickable[i];
if (defaultResources != null && defaultResources.length == size)
for (int i = size - 1; i >= 0; i--)
mItemMappings[i].defaultResource = defaultResources[i];
}
private void generateDataCache() {
if (mItemMappings == null)
return;
android.util.Log.d("LAUNCHER","API v1 START QUERY");
mAsyncQuery.startQuery(1, "cookie",
Uri.parse(mIntent.getStringExtra(LauncherIntent.Extra.Scroll.EXTRA_DATA_URI)) ,
mIntent.getStringArrayExtra(LauncherIntent.Extra.Scroll.EXTRA_PROJECTION),
mIntent.getStringExtra(LauncherIntent.Extra.Scroll.EXTRA_SELECTION),
mIntent.getStringArrayExtra(LauncherIntent.Extra.Scroll.EXTRA_SELECTION_ARGUMENTS),
mIntent.getStringExtra(LauncherIntent.Extra.Scroll.EXTRA_SORT_ORDER));
}
public void bindView(ViewHolder holder, View view, Context context, int itemPosition) {
if (mItemMappings == null)
return;
final int size = mItemMappings.length;
ItemMapping itemMapping;
View child;
ImageView iv;
RowElement rowElement;
try {
// bind children views
for (int i = size - 1; i >= 0; i--) {
itemMapping = mItemMappings[i];
if ((holder.views[i] != null)) {
child = holder.views[i];
} else {
child = view.findViewById(itemMapping.layoutId);
holder.views[i] = child;
}
rowElement = rowsElementsList.get(itemPosition).singleRowElementsList[i];
switch (itemMapping.type) {
case LauncherIntent.Extra.Scroll.Types.TEXTVIEW:
if (!(child instanceof TextView))
break;
if (rowElement.data != null)
((TextView) child).setText((String) rowElement.data);
else
((TextView) child).setText(itemMapping.defaultResource);
break;
case LauncherIntent.Extra.Scroll.Types.TEXTVIEWHTML:
if (!(child instanceof TextView))
break;
if (rowElement.data != null)
((TextView) child).setText((Spanned) rowElement.data);
else
((TextView) child).setText(itemMapping.defaultResource);
break;
case LauncherIntent.Extra.Scroll.Types.IMAGEBLOB:
if (!(child instanceof ImageView))
break;
iv = (ImageView) child;
if (rowElement.data != null) {
byte[] blob = (byte[]) rowElement.data;
iv.setImageBitmap(BitmapFactory.decodeByteArray(blob, 0, blob.length));
} else if (itemMapping.defaultResource > 0)
iv.setImageResource(itemMapping.defaultResource);
else
iv.setImageDrawable(null);
break;
case LauncherIntent.Extra.Scroll.Types.IMAGEURI:
if (!(child instanceof ImageView))
break;
iv = (ImageView) child;
if ((rowElement.data != null) && (!rowElement.data.equals(""))) {
Drawable d = mImageManager.getImageFromUri(context, mAppWidgetId,
(String) rowElement.data);
iv.setImageDrawable(d);
} else
iv.setImageDrawable(null);
break;
case LauncherIntent.Extra.Scroll.Types.IMAGERESOURCE:
if (!(child instanceof ImageView))
break;
iv = (ImageView) child;
if ((Integer) rowElement.data > 0) {
// assign new bitmap
Drawable drawable = mImageManager.getImageFromId(context, mAppWidgetId,
(Integer) rowElement.data);
// iv.setImageResource(rowElement.imageResId);
iv.setImageDrawable(drawable);
} else if (itemMapping.defaultResource > 0) {
Drawable drawable = mImageManager.getImageFromId(context, mAppWidgetId,
itemMapping.defaultResource);
iv.setImageDrawable(drawable);
} else
iv.setImageDrawable(null);
break;
}
// Prepare tag
holder.lvClickItemTag = null;
if (mItemChildrenClickable && itemMapping.clickable) {
child.setTag(rowElement.tag);
child.setOnClickListener(new ItemViewClickListener());
} else {
if (mItemActionUriIndex >= 0) {
holder.lvClickItemTag = rowElement.tag;
}
}
}
} catch (OutOfMemoryError e) {
Log.d(LOG_TAG, "****** freeMemory = " + Runtime.getRuntime().freeMemory() / 1000
+ " Kb");
System.gc();
e.printStackTrace();
} catch (Exception e) {
Log.d(LOG_TAG, "****** freeMemory = " + Runtime.getRuntime().freeMemory() / 1000
+ " Kb");
e.printStackTrace();
}
// if (LOGD)
// Log.d(LOG_TAG, "freeMemory = " + Runtime.getRuntime().freeMemory() /
// 1000 + " Kb");
if (Runtime.getRuntime().freeMemory() < 500000) {
if (LOGD)
Log.d(LOG_TAG, "force gargabe collecting below 500kb");
System.gc();
}
}
class ItemViewClickListener implements OnClickListener {
public void onClick(View v) {
try {
String pos = (String) v.getTag();
Intent intent = new Intent(LauncherIntent.Action.ACTION_VIEW_CLICK);
intent.setComponent(mAppWidgetProvider);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId).putExtra(
LauncherIntent.Extra.EXTRA_APPWIDGET_ID, mAppWidgetId);
intent.putExtra(LauncherIntent.Extra.EXTRA_VIEW_ID, v.getId());
intent.putExtra(LauncherIntent.Extra.Scroll.EXTRA_LISTVIEW_ID, mListViewId);
intent.putExtra(LauncherIntent.Extra.Scroll.EXTRA_ITEM_POS, pos);
Rect srcRect = new Rect();
final int[] location = new int[2];
v.getLocationOnScreen(location);
srcRect.left = location[0];
srcRect.top = location[1];
srcRect.right = srcRect.left + v.getWidth();
srcRect.bottom = srcRect.top + v.getHeight();
intent.putExtra(LauncherIntent.Extra.Scroll.EXTRA_SOURCE_BOUNDS, srcRect);
v.getContext().sendBroadcast(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public int getViewTypeCount() {
return NB_MAX_VIEWS_TYPES;
}
@Override
public int getItemViewType(int position) {
return 0;
}
@Override
public int getCount() {
return rowsElementsList.size();
}
@Override
public Object getItem(int position) {
return rowsElementsList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(mItemLayoutId, null);
holder = new ViewHolder(mItemMappings.length);
convertView.setTag(holder);
// if (LOGD)
// Log.d(LOG_TAG, "*** new view");
} else {
holder = (ViewHolder) convertView.getTag();
// if (LOGD)
// Log.d(LOG_TAG, "*** recycled view");
}
if (position < getCount())
bindView(holder, convertView, convertView.getContext(), position);
return convertView;
}
public static class ViewHolder {
public View views[];
public Object lvClickItemTag = null;
public ViewHolder(int size) {
views = new View[size];
}
}
@Override
public void notifyToRegenerate() {
if (LOGD)
Log.d(LOG_TAG, "notifyToRegenerate widgetId = " + mAppWidgetId);
mHandler.post(mGenerateDataCacheRunnable);
}
@Override
public void dropCache(Context context) {
mImageManager.clearCacheForWidget(context, mAppWidgetId);
rowsElementsList.clear();
}
/**
* AsyncQueryHandler helper class to do async queries
* instead of blocking the UI thread
* (yeah, don't know why but the runnable was not avoiding
* the UI lock
* @author adw
*
*/
private class MyQueryHandler extends AsyncQueryHandler {
public MyQueryHandler(ContentResolver cr) {
super(cr);
}
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
super.onQueryComplete(token, cookie, cursor);
android.util.Log.d("LAUNCHER", "API v1 QUERY COMPLETE");
rowsElementsList.clear();
final int size = mItemMappings.length;
while ((cursor != null) && (cursor.moveToNext())) {
RowElementsList singleRowElem = new RowElementsList(size);
ItemMapping itemMapping;
try {
// bind children views
for (int i = size - 1; i >= 0; i--) {
RowElement re = new RowElement();
itemMapping = mItemMappings[i];
switch (itemMapping.type) {
case LauncherIntent.Extra.Scroll.Types.TEXTVIEW:
re.data = cursor.getString(itemMapping.index);
break;
case LauncherIntent.Extra.Scroll.Types.TEXTVIEWHTML:
re.data = Html.fromHtml(cursor
.getString(itemMapping.index));
break;
case LauncherIntent.Extra.Scroll.Types.IMAGEBLOB:
byte[] localData = cursor
.getBlob(itemMapping.index);
re.data = localData;
break;
case LauncherIntent.Extra.Scroll.Types.IMAGEURI:
re.data = cursor.getString(itemMapping.index);
break;
case LauncherIntent.Extra.Scroll.Types.IMAGERESOURCE:
re.data = cursor.getInt(itemMapping.index);
break;
}
// Prepare tag
if (mItemChildrenClickable && itemMapping.clickable) {
if (mItemActionUriIndex >= 0)
re.tag = cursor.getString(mItemActionUriIndex);
else
re.tag = Integer.toString(cursor.getPosition());
} else {
if (mItemActionUriIndex >= 0) {
re.tag = cursor.getString(mItemActionUriIndex);
}
}
singleRowElem.singleRowElementsList[i] = re;
}
rowsElementsList.add(singleRowElem);
} catch (Exception e) {
e.printStackTrace();
}
}
if (cursor != null)
cursor.close();
System.gc();
notifyDataSetInvalidated();
}
}
}
// if (RECYCLE) {
// // recycle old bitmap
// BitmapDrawable lastDrawableImageRes = (BitmapDrawable)
// iv.getDrawable();
// if ((lastDrawableImageRes != null) &&
// (!lastDrawableImageRes.getBitmap().isRecycled()))
// lastDrawableImageRes.getBitmap().recycle();
// }