package mobi.urika.android.widget;
import java.util.ArrayList;
import java.util.HashMap;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
import android.view.View.OnClickListener;
public class BoundRemoteViews extends SimpleRemoteViews {
class CursorCache {
final ArrayList<HashMap<Action, Object>> mCache;
final HashMap<Action, Object> mDefaults;
public Object getValueOrDefault(int index, Action target) {
HashMap<Action, Object> row = mCache.get(index);
Object result = null;
if (row.containsKey(target))
result = row.get(target);
if (result == null)
result = mDefaults.get(target);
return result;
}
public CursorCache(Cursor cursor, Context context) {
final int cacheSize = cursor != null ? cursor.getCount() : 0;
mCache = new ArrayList<HashMap<Action,Object>>(cacheSize);
mDefaults = new HashMap<Action, Object>();
final ArrayList<Action> actions = BoundRemoteViews.this.mActions;
for (int i = 0; i < actions.size(); i++) {
Action act = actions.get(i);
if (act instanceof BindingAction)
mDefaults.put(act, ((BindingAction)act).getDefault(context));
else if (act instanceof SetBoundOnClickIntent)
mDefaults.put(act, null);
}
if (cursor != null) {
cursor.moveToFirst();
while(!cursor.isAfterLast()) {
HashMap<Action, Object> row = new HashMap<Action, Object>();
for (int i = 0; i < actions.size(); i++) {
Action act = actions.get(i);
if (act instanceof BindingAction)
row.put(act, ((BindingAction)act).readValue(cursor, context));
else if (act instanceof SetBoundOnClickIntent)
row.put(act, ((SetBoundOnClickIntent)act).readValue(cursor));
}
mCache.add(row);
cursor.moveToNext();
}
}
}
private void dropCacheRow(HashMap<Action,Object> row) {
for(Action key : row.keySet()) {
Object value = row.get(key);
if (value instanceof Bitmap) {
((Bitmap)value).recycle();
}
}
row.clear();
}
public void clear() {
for(HashMap<Action, Object> row : mCache) {
dropCacheRow(row);
}
mCache.clear();
dropCacheRow(mDefaults);
}
}
protected class BindingAction extends SimpleRemoteViews.ReflectionAction
{
public static final int tag = 99;
private int mCursorIndex;
private int mDefaultResource;
public BindingAction(int viewId, String methodName, int type, int cursorIndex, int defaultResource) {
super(viewId, methodName, type);
mCursorIndex = cursorIndex;
mDefaultResource = defaultResource;
}
public BindingAction(Parcel in) {
super(in);
}
@Override
protected int getTag() {
return tag;
}
@Override
protected void readValue(Parcel in) {
mCursorIndex = in.readInt();
mDefaultResource = in.readInt();
}
@Override
protected void writeValue(Parcel out, int flags) {
out.writeInt(mCursorIndex);
out.writeInt(mDefaultResource);
}
@Override
protected Object getValue(Context context) {
return mCursor.getValueOrDefault(mCursorPos, this);
}
public Object readValue(Cursor cursor, Context context) {
try
{
switch(this.type) {
case STRING:
case CHAR_SEQUENCE:
return cursor.getString(mCursorIndex);
case BYTE:
return (byte)cursor.getInt(mCursorIndex);
case SHORT:
return (short)cursor.getInt(mCursorIndex);
case INT:
return cursor.getInt(mCursorIndex);
case LONG:
return cursor.getLong(mCursorIndex);
case FLOAT:
return cursor.getFloat(mCursorIndex);
case DOUBLE:
return cursor.getDouble(mCursorIndex);
case CHAR:
return cursor.getString(mCursorIndex).charAt(0);
case URI:
return Uri.parse(cursor.getString(mCursorIndex));
case BITMAP:
byte[] blob = cursor.getBlob(mCursorIndex);
return BitmapFactory.decodeByteArray(blob, 0, blob.length);
}
}
catch(Exception e) {
return null;
}
return null;
}
public Object getDefault(Context context) {
try
{
switch(this.type) {
case STRING:
case CHAR_SEQUENCE:
return context.getString(mDefaultResource);
case BITMAP:
return BitmapFactory.decodeResource(context.getResources(), mDefaultResource);
}
}
catch(Exception e) {
return null;
}
return null;
}
@Override
public void apply(View root) {
super.apply(root);
}
}
protected class SetBoundOnClickIntent extends Action {
private static final int TAG = 100;
private final String mExtraName;
private final int mExtraCursorIndex;
private final int mViewId;
private final PendingIntent mIntent;
public SetBoundOnClickIntent(int id, PendingIntent intent,
String extraName, int extraCursorIndex) {
mViewId = id;
mIntent = intent;
mExtraName = extraName;
mExtraCursorIndex = extraCursorIndex;
}
public SetBoundOnClickIntent(Parcel parcel) {
mViewId = parcel.readInt();
mExtraName = parcel.readString();
mExtraCursorIndex = parcel.readInt();
mIntent = PendingIntent.CREATOR.createFromParcel(parcel);
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(TAG);
dest.writeInt(mViewId);
dest.writeString(mExtraName);
dest.writeInt(mExtraCursorIndex);
mIntent.writeToParcel(dest, 0 /* no flags */);
}
@Override
public void apply(View root) {
final View target = root.findViewById(mViewId);
if (target != null && mIntent != null) {
target.setOnClickListener(new BoundOnClickListener(mCursorPos));
}
}
private class BoundOnClickListener implements OnClickListener {
private final int myCursorPos;
public BoundOnClickListener(int cursorPos) {
myCursorPos = cursorPos;
}
public void onClick(View v) {
// Find target view location in screen coordinates and
// fill into PendingIntent before sending.
final int[] location = new int[2];
v.getLocationOnScreen(location);
Rect srcRect = new Rect();
srcRect.left = location[0];
srcRect.top = location[1];
srcRect.right = srcRect.left + v.getWidth();
srcRect.bottom = srcRect.top + v.getHeight();
Intent intent = new Intent();
intent.setSourceBounds(srcRect);
prepareIntent(intent);
try {
mIntent.send(v.getContext(), 0, intent, null, null);
} catch (PendingIntent.CanceledException e) {
android.util.Log.e("SetOnClickPendingIntent", "Cannot send pending intent: ", e);
}
}
protected void prepareIntent(Intent intent) {
String value = (String)mCursor.getValueOrDefault(myCursorPos, SetBoundOnClickIntent.this);
intent.putExtra(mExtraName, value);
}
}
public String readValue(Cursor cursor) {
return cursor.getString(mExtraCursorIndex);
}
}
private CursorCache mCursor;
private int mCursorPos;
public BoundRemoteViews(Parcel parcel) {
super(parcel);
}
public BoundRemoteViews(int layoutId) {
super(layoutId);
}
public void setBindingCursor(Cursor cursor, Context context) {
mCursor = new CursorCache(cursor,context);
}
public void dropCache() {
if (mCursor != null) {
mCursor.clear();
}
mCursor = null;
}
public int getCursorCacheSize() {
if (mCursor != null)
return mCursor.mCache.size();
else
return 0;
}
public void moveCursor(int newPosition) {
mCursorPos = newPosition;
}
@Override
protected Action loadActionFromParcel(int tag, Parcel parcel) {
if (tag == BoundRemoteViews.BindingAction.tag)
return new BindingAction(parcel);
else if (tag == SetBoundOnClickIntent.TAG)
return new SetBoundOnClickIntent(parcel);
else
return super.loadActionFromParcel(tag, parcel);
}
public void reapplyBinding(View v) {
try
{
if (mActions != null) {
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
if (a instanceof BindingAction || a instanceof SetBoundOnClickIntent)
a.apply(v);
}
}
} catch (OutOfMemoryError e) {
System.gc();
}
}
public void setBoundString(int viewId, String methodName, int cursorIndex, int defaultResource) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.STRING,
cursorIndex, defaultResource));
}
public void setBoundCharSequence(int viewId, String methodName, int cursorIndex, int defaultResource) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE,
cursorIndex, defaultResource));
}
public void setBoundByte(int viewId, String methodName, int cursorIndex) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.BYTE,
cursorIndex,0));
}
public void setBoundShort(int viewId, String methodName, int cursorIndex) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.SHORT,
cursorIndex,0));
}
public void setBoundInt(int viewId, String methodName, int cursorIndex) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.INT,
cursorIndex,0));
}
public void setBoundLong(int viewId, String methodName, int cursorIndex) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.LONG,
cursorIndex,0));
}
public void setBoundFloat(int viewId, String methodName, int cursorIndex) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.FLOAT,
cursorIndex,0));
}
public void setBoundDouble(int viewId, String methodName, int cursorIndex) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.DOUBLE,
cursorIndex,0));
}
public void setBoundChar(int viewId, String methodName, int cursorIndex) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.CHAR,
cursorIndex,0));
}
public void setBoundUri(int viewId, String methodName, int cursorIndex) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.URI,
cursorIndex,0));
}
public void setBoundBitmap(int viewId, String methodName, int cursorIndex, int defaultResource) {
addAction(new BindingAction(viewId, methodName, ReflectionAction.BITMAP,
cursorIndex, defaultResource));
}
public void SetBoundOnClickIntent(int viewId, PendingIntent intent,
String extraName, int extraCursorIndex) {
addAction(new SetBoundOnClickIntent(viewId, intent, extraName, extraCursorIndex));
}
/**
* Parcelable.Creator that instantiates RemoteViews objects
*/
public static final Parcelable.Creator<BoundRemoteViews> CREATOR = new Parcelable.Creator<BoundRemoteViews>() {
public BoundRemoteViews createFromParcel(Parcel parcel) {
return new BoundRemoteViews(parcel);
}
public BoundRemoteViews[] newArray(int size) {
return new BoundRemoteViews[size];
}
};
}