blob: 38b91f55390370e83f9c26a0b176972a1e73aa2e [file] [log] [blame]
package com.xtremelabs.robolectric.shadows;
import android.database.DataSetObserver;
import android.os.Handler;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;
import com.xtremelabs.robolectric.internal.Implementation;
import com.xtremelabs.robolectric.internal.Implements;
import com.xtremelabs.robolectric.internal.RealObject;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings({"UnusedDeclaration"})
@Implements(AdapterView.class)
public class ShadowAdapterView extends ShadowViewGroup {
@RealObject private AdapterView realAdapterView;
private Adapter adapter;
private View mEmptyView;
private AdapterView.OnItemSelectedListener onItemSelectedListener;
private AdapterView.OnItemClickListener onItemClickListener;
private boolean valid = false;
private int selectedPosition;
private int itemCount = 0;
private List<Object> previousItems = new ArrayList<Object>();
@Implementation
public void setAdapter(Adapter adapter) {
this.adapter = adapter;
if (null != adapter) {
adapter.registerDataSetObserver(new AdapterViewDataSetObserver());
}
invalidateAndScheduleUpdate();
setSelection(0);
}
@Implementation
public void setEmptyView(View emptyView) {
this.mEmptyView = emptyView;
updateEmptyStatus(adapter == null || adapter.isEmpty());
}
private void invalidateAndScheduleUpdate() {
valid = false;
itemCount = adapter == null ? 0 : adapter.getCount();
updateEmptyStatus(itemCount == 0);
new Handler().post(new Runnable() {
@Override
public void run() {
if (!valid) {
update();
valid = true;
}
}
});
}
private void updateEmptyStatus(boolean empty) {
// code taken from the real AdapterView and commented out where not (yet?) applicable
// we don't deal with filterMode yet...
// if (isInFilterMode()) {
// empty = false;
// }
if (empty) {
if (mEmptyView != null) {
mEmptyView.setVisibility(View.VISIBLE);
setVisibility(View.GONE);
} else {
// If the caller just removed our empty view, make sure the list view is visible
setVisibility(View.VISIBLE);
}
// leave layout for the moment...
// // We are now GONE, so pending layouts will not be dispatched.
// // Force one here to make sure that the state of the list matches
// // the state of the adapter.
// if (mDataChanged) {
// this.onLayout(false, mLeft, mTop, mRight, mBottom);
// }
} else {
if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
setVisibility(View.VISIBLE);
}
}
/**
* Check if our adapter's items have changed without {@code onChanged()} or {@code onInvalidated()} having been called.
*
* @return true if the object is valid, false if not
* @throws RuntimeException if the items have been changed without notification
*/
public boolean checkValidity() {
update();
return valid;
}
/**
* Non-Android accessor.
*
* @return the index of the selected item
*/
@Implementation
public int getSelectedItemPosition() {
return selectedPosition;
}
@Implementation
public Object getSelectedItem() {
int pos = getSelectedItemPosition();
return getItemAtPosition(pos);
}
@Implementation
public Adapter getAdapter() {
return adapter;
}
@Implementation
public int getCount() {
return itemCount;
}
@Implementation
public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {
this.onItemSelectedListener = listener;
}
@Implementation
public final AdapterView.OnItemSelectedListener getOnItemSelectedListener() {
return onItemSelectedListener;
}
@Implementation
public void setOnItemClickListener(AdapterView.OnItemClickListener listener) {
this.onItemClickListener = listener;
}
@Implementation
public final AdapterView.OnItemClickListener getOnItemClickListener() {
return onItemClickListener;
}
@Implementation
public Object getItemAtPosition(int position) {
Adapter adapter = getAdapter();
return (adapter == null || position < 0) ? null : adapter.getItem(position);
}
@Implementation
public long getItemIdAtPosition(int position) {
Adapter adapter = getAdapter();
return (adapter == null || position < 0) ? AdapterView.INVALID_ROW_ID : adapter.getItemId(position);
}
@Implementation
public void setSelection(final int position) {
selectedPosition = position;
new Handler().post(new Runnable() {
@Override
public void run() {
if (onItemSelectedListener != null) {
onItemSelectedListener.onItemSelected(realAdapterView, getChildAt(position), position, getAdapter().getItemId(position));
}
}
});
}
@Implementation
public boolean performItemClick(View view, int position, long id) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(realAdapterView, view, position, id);
return true;
}
return false;
}
@Implementation
public View getEmptyView() {
return mEmptyView;
}
private void update() {
removeAllViews();
Adapter adapter = getAdapter();
if (adapter != null) {
if (valid && previousItems.size() != adapter.getCount()) {
throw new ArrayIndexOutOfBoundsException("view is valid but adapter.getCount() has changed from " + previousItems.size() + " to " + adapter.getCount());
}
List<Object> newItems = new ArrayList<Object>();
for (int i = 0; i < adapter.getCount(); i++) {
newItems.add(adapter.getItem(i));
View view = adapter.getView(i, null, realAdapterView);
// don't add null views
if( view != null ) {
addView(view);
}
}
if (valid && !newItems.equals(previousItems)) {
throw new RuntimeException("view is valid but current items <" + newItems + "> don't match previous items <" + previousItems + ">");
}
previousItems = newItems;
}
}
/**
* Simple default implementation of {@code android.database.DataSetObserver}
*/
protected class AdapterViewDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
invalidateAndScheduleUpdate();
}
@Override
public void onInvalidated() {
invalidateAndScheduleUpdate();
}
}
}