From 54d88f7678387a6eb871ec2dccd36af4ff35b1fe Mon Sep 17 00:00:00 2001 From: Deepanshu Gupta <deepanshu@google.com> Date: Tue, 10 Sep 2013 15:39:41 -0700 Subject: Fix ClassCastException when rendering ListView Bug: b.android.com/59300 Change-Id: Ia350adb3308daa8170bff755302760790163c811 --- .../bridge/impl/binding/AdapterHelper.java | 148 ++++++++++++ .../layoutlib/bridge/impl/binding/AdapterItem.java | 74 ++++++ .../layoutlib/bridge/impl/binding/BaseAdapter.java | 247 --------------------- .../layoutlib/bridge/impl/binding/FakeAdapter.java | 24 +- .../bridge/impl/binding/FakeExpandableAdapter.java | 50 ++++- 5 files changed, 285 insertions(+), 258 deletions(-) create mode 100644 tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java create mode 100644 tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java delete mode 100644 tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java (limited to 'tools') diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java new file mode 100644 index 0000000..6c998af --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2013 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.layoutlib.bridge.impl.binding; + +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.RenderAction; +import com.android.util.Pair; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.Checkable; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * A Helper class to do fake data binding in {@link AdapterView} objects. + */ +@SuppressWarnings("deprecation") +public class AdapterHelper { + + static Pair<View, Boolean> getView(AdapterItem item, AdapterItem parentItem, ViewGroup parent, + IProjectCallback callback, ResourceReference adapterRef, boolean skipCallbackParser) { + // we don't care about recycling here because we never scroll. + DataBindingItem dataBindingItem = item.getDataBindingItem(); + + BridgeContext context = RenderAction.getCurrentContext(); + + Pair<View, Boolean> pair = context.inflateView(dataBindingItem.getViewReference(), + parent, false /*attachToRoot*/, skipCallbackParser); + + View view = pair.getFirst(); + skipCallbackParser |= pair.getSecond(); + + if (view != null) { + fillView(context, view, item, parentItem, callback, adapterRef); + } else { + // create a text view to display an error. + TextView tv = new TextView(context); + tv.setText("Unable to find layout: " + dataBindingItem.getViewReference().getName()); + view = tv; + } + + return Pair.of(view, skipCallbackParser); + } + + private static void fillView(BridgeContext context, View view, AdapterItem item, + AdapterItem parentItem, IProjectCallback callback, ResourceReference adapterRef) { + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + final int count = group.getChildCount(); + for (int i = 0 ; i < count ; i++) { + fillView(context, group.getChildAt(i), item, parentItem, callback, adapterRef); + } + } else { + int id = view.getId(); + if (id != 0) { + ResourceReference resolvedRef = context.resolveId(id); + if (resolvedRef != null) { + int fullPosition = item.getFullPosition(); + int positionPerType = item.getPositionPerType(); + int fullParentPosition = parentItem != null ? parentItem.getFullPosition() : 0; + int parentPositionPerType = parentItem != null ? + parentItem.getPositionPerType() : 0; + + if (view instanceof TextView) { + TextView tv = (TextView) view; + Object value = callback.getAdapterItemValue( + adapterRef, context.getViewKey(view), + item.getDataBindingItem().getViewReference(), + fullPosition, positionPerType, + fullParentPosition, parentPositionPerType, + resolvedRef, ViewAttribute.TEXT, tv.getText().toString()); + if (value != null) { + if (value.getClass() != ViewAttribute.TEXT.getAttributeClass()) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( + "Wrong Adapter Item value class for TEXT. Expected String, got %s", + value.getClass().getName()), null); + } else { + tv.setText((String) value); + } + } + } + + if (view instanceof Checkable) { + Checkable cb = (Checkable) view; + + Object value = callback.getAdapterItemValue( + adapterRef, context.getViewKey(view), + item.getDataBindingItem().getViewReference(), + fullPosition, positionPerType, + fullParentPosition, parentPositionPerType, + resolvedRef, ViewAttribute.IS_CHECKED, cb.isChecked()); + if (value != null) { + if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( + "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", + value.getClass().getName()), null); + } else { + cb.setChecked((Boolean) value); + } + } + } + + if (view instanceof ImageView) { + ImageView iv = (ImageView) view; + + Object value = callback.getAdapterItemValue( + adapterRef, context.getViewKey(view), + item.getDataBindingItem().getViewReference(), + fullPosition, positionPerType, + fullParentPosition, parentPositionPerType, + resolvedRef, ViewAttribute.SRC, iv.getDrawable()); + if (value != null) { + if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( + "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", + value.getClass().getName()), null); + } else { + // FIXME + } + } + } + } + } + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java new file mode 100644 index 0000000..8e28dba --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterItem.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 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.layoutlib.bridge.impl.binding; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.android.ide.common.rendering.api.DataBindingItem; + +/** + * This is the items provided by the adapter. They are dynamically generated. + */ +final class AdapterItem { + private final DataBindingItem mItem; + private final int mType; + private final int mFullPosition; + private final int mPositionPerType; + private List<AdapterItem> mChildren; + + protected AdapterItem(DataBindingItem item, int type, int fullPosition, + int positionPerType) { + mItem = item; + mType = type; + mFullPosition = fullPosition; + mPositionPerType = positionPerType; + } + + void addChild(AdapterItem child) { + if (mChildren == null) { + mChildren = new ArrayList<AdapterItem>(); + } + + mChildren.add(child); + } + + List<AdapterItem> getChildren() { + if (mChildren != null) { + return mChildren; + } + + return Collections.emptyList(); + } + + int getType() { + return mType; + } + + int getFullPosition() { + return mFullPosition; + } + + int getPositionPerType() { + return mPositionPerType; + } + + DataBindingItem getDataBindingItem() { + return mItem; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java deleted file mode 100644 index e0414fe..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2011 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.layoutlib.bridge.impl.binding; - -import com.android.ide.common.rendering.api.AdapterBinding; -import com.android.ide.common.rendering.api.DataBindingItem; -import com.android.ide.common.rendering.api.IProjectCallback; -import com.android.ide.common.rendering.api.LayoutLog; -import com.android.ide.common.rendering.api.ResourceReference; -import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute; -import com.android.layoutlib.bridge.Bridge; -import com.android.layoutlib.bridge.android.BridgeContext; -import com.android.layoutlib.bridge.impl.RenderAction; -import com.android.util.Pair; - -import android.database.DataSetObserver; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.Checkable; -import android.widget.ImageView; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Base adapter to do fake data binding in {@link AdapterView} objects. - */ -public class BaseAdapter { - - /** - * This is the items provided by the adapter. They are dynamically generated. - */ - protected final static class AdapterItem { - private final DataBindingItem mItem; - private final int mType; - private final int mFullPosition; - private final int mPositionPerType; - private List<AdapterItem> mChildren; - - protected AdapterItem(DataBindingItem item, int type, int fullPosition, - int positionPerType) { - mItem = item; - mType = type; - mFullPosition = fullPosition; - mPositionPerType = positionPerType; - } - - void addChild(AdapterItem child) { - if (mChildren == null) { - mChildren = new ArrayList<AdapterItem>(); - } - - mChildren.add(child); - } - - List<AdapterItem> getChildren() { - if (mChildren != null) { - return mChildren; - } - - return Collections.emptyList(); - } - - int getType() { - return mType; - } - - int getFullPosition() { - return mFullPosition; - } - - int getPositionPerType() { - return mPositionPerType; - } - - DataBindingItem getDataBindingItem() { - return mItem; - } - } - - private final AdapterBinding mBinding; - private final IProjectCallback mCallback; - private final ResourceReference mAdapterRef; - private boolean mSkipCallbackParser = false; - - protected final List<AdapterItem> mItems = new ArrayList<AdapterItem>(); - - protected BaseAdapter(ResourceReference adapterRef, AdapterBinding binding, - IProjectCallback callback) { - mAdapterRef = adapterRef; - mBinding = binding; - mCallback = callback; - } - - // ------- Some Adapter method used by all children classes. - - public boolean areAllItemsEnabled() { - return true; - } - - public boolean hasStableIds() { - return true; - } - - public boolean isEmpty() { - return mItems.size() == 0; - } - - public void registerDataSetObserver(DataSetObserver observer) { - // pass - } - - public void unregisterDataSetObserver(DataSetObserver observer) { - // pass - } - - // ------- - - - protected AdapterBinding getBinding() { - return mBinding; - } - - protected View getView(AdapterItem item, AdapterItem parentItem, View convertView, - ViewGroup parent) { - // we don't care about recycling here because we never scroll. - DataBindingItem dataBindingItem = item.getDataBindingItem(); - - BridgeContext context = RenderAction.getCurrentContext(); - - Pair<View, Boolean> pair = context.inflateView(dataBindingItem.getViewReference(), - parent, false /*attachToRoot*/, mSkipCallbackParser); - - View view = pair.getFirst(); - mSkipCallbackParser |= pair.getSecond(); - - if (view != null) { - fillView(context, view, item, parentItem); - } else { - // create a text view to display an error. - TextView tv = new TextView(context); - tv.setText("Unable to find layout: " + dataBindingItem.getViewReference().getName()); - view = tv; - } - - return view; - } - - private void fillView(BridgeContext context, View view, AdapterItem item, - AdapterItem parentItem) { - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - final int count = group.getChildCount(); - for (int i = 0 ; i < count ; i++) { - fillView(context, group.getChildAt(i), item, parentItem); - } - } else { - int id = view.getId(); - if (id != 0) { - ResourceReference resolvedRef = context.resolveId(id); - if (resolvedRef != null) { - int fullPosition = item.getFullPosition(); - int positionPerType = item.getPositionPerType(); - int fullParentPosition = parentItem != null ? parentItem.getFullPosition() : 0; - int parentPositionPerType = parentItem != null ? - parentItem.getPositionPerType() : 0; - - if (view instanceof TextView) { - TextView tv = (TextView) view; - Object value = mCallback.getAdapterItemValue( - mAdapterRef, context.getViewKey(view), - item.getDataBindingItem().getViewReference(), - fullPosition, positionPerType, - fullParentPosition, parentPositionPerType, - resolvedRef, ViewAttribute.TEXT, tv.getText().toString()); - if (value != null) { - if (value.getClass() != ViewAttribute.TEXT.getAttributeClass()) { - Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( - "Wrong Adapter Item value class for TEXT. Expected String, got %s", - value.getClass().getName()), null); - } else { - tv.setText((String) value); - } - } - } - - if (view instanceof Checkable) { - Checkable cb = (Checkable) view; - - Object value = mCallback.getAdapterItemValue( - mAdapterRef, context.getViewKey(view), - item.getDataBindingItem().getViewReference(), - fullPosition, positionPerType, - fullParentPosition, parentPositionPerType, - resolvedRef, ViewAttribute.IS_CHECKED, cb.isChecked()); - if (value != null) { - if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) { - Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( - "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", - value.getClass().getName()), null); - } else { - cb.setChecked((Boolean) value); - } - } - } - - if (view instanceof ImageView) { - ImageView iv = (ImageView) view; - - Object value = mCallback.getAdapterItemValue( - mAdapterRef, context.getViewKey(view), - item.getDataBindingItem().getViewReference(), - fullPosition, positionPerType, - fullParentPosition, parentPositionPerType, - resolvedRef, ViewAttribute.SRC, iv.getDrawable()); - if (value != null) { - if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) { - Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( - "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", - value.getClass().getName()), null); - } else { - // FIXME - } - } - } - } - } - } - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java index 22570b9..9a13f5a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java @@ -20,10 +20,12 @@ import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.DataBindingItem; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.ResourceReference; +import com.android.util.Pair; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; +import android.widget.BaseAdapter; import android.widget.ListAdapter; import android.widget.SpinnerAdapter; @@ -35,17 +37,23 @@ import java.util.List; * and {@link SpinnerAdapter}. * */ -public class FakeAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter { +@SuppressWarnings("deprecation") +public class FakeAdapter extends BaseAdapter { // don't use a set because the order is important. private final List<ResourceReference> mTypes = new ArrayList<ResourceReference>(); + private final IProjectCallback mCallback; + private final ResourceReference mAdapterRef; + private final List<AdapterItem> mItems = new ArrayList<AdapterItem>(); + private boolean mSkipCallbackParser = false; public FakeAdapter(ResourceReference adapterRef, AdapterBinding binding, IProjectCallback callback) { - super(adapterRef, binding, callback); + mAdapterRef = adapterRef; + mCallback = callback; - final int repeatCount = getBinding().getRepeatCount(); - final int itemCount = getBinding().getItemCount(); + final int repeatCount = binding.getRepeatCount(); + final int itemCount = binding.getItemCount(); // Need an array to count for each type. // This is likely too big, but is the max it can be. @@ -54,7 +62,7 @@ public class FakeAdapter extends BaseAdapter implements ListAdapter, SpinnerAdap // We put several repeating sets. for (int r = 0 ; r < repeatCount ; r++) { // loop on the type of list items, and add however many for each type. - for (DataBindingItem dataBindingItem : getBinding()) { + for (DataBindingItem dataBindingItem : binding) { ResourceReference viewRef = dataBindingItem.getViewReference(); int typeIndex = mTypes.indexOf(viewRef); if (typeIndex == -1) { @@ -103,7 +111,11 @@ public class FakeAdapter extends BaseAdapter implements ListAdapter, SpinnerAdap public View getView(int position, View convertView, ViewGroup parent) { // we don't care about recycling here because we never scroll. AdapterItem item = mItems.get(position); - return getView(item, null /*parentGroup*/, convertView, parent); + Pair<View, Boolean> pair = AdapterHelper.getView(item, null /*parentGroup*/, parent, + mCallback, mAdapterRef, mSkipCallbackParser); + mSkipCallbackParser = pair.getSecond(); + return pair.getFirst(); + } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java index 199e040..e539579 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java @@ -20,7 +20,9 @@ import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.DataBindingItem; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.ResourceReference; +import com.android.util.Pair; +import android.database.DataSetObserver; import android.view.View; import android.view.ViewGroup; import android.widget.ExpandableListAdapter; @@ -29,8 +31,14 @@ import android.widget.HeterogeneousExpandableList; import java.util.ArrayList; import java.util.List; -public class FakeExpandableAdapter extends BaseAdapter implements ExpandableListAdapter, - HeterogeneousExpandableList { +@SuppressWarnings("deprecation") +public class FakeExpandableAdapter implements ExpandableListAdapter, HeterogeneousExpandableList { + + private final IProjectCallback mCallback; + private final ResourceReference mAdapterRef; + private boolean mSkipCallbackParser = false; + + protected final List<AdapterItem> mItems = new ArrayList<AdapterItem>(); // don't use a set because the order is important. private final List<ResourceReference> mGroupTypes = new ArrayList<ResourceReference>(); @@ -38,7 +46,8 @@ public class FakeExpandableAdapter extends BaseAdapter implements ExpandableList public FakeExpandableAdapter(ResourceReference adapterRef, AdapterBinding binding, IProjectCallback callback) { - super(adapterRef, binding, callback); + mAdapterRef = adapterRef; + mCallback = callback; createItems(binding, binding.getItemCount(), binding.getRepeatCount(), mGroupTypes, 1); } @@ -125,7 +134,10 @@ public class FakeExpandableAdapter extends BaseAdapter implements ExpandableList ViewGroup parent) { // we don't care about recycling here because we never scroll. AdapterItem item = mItems.get(groupPosition); - return getView(item, null /*parentItem*/, convertView, parent); + Pair<View, Boolean> pair = AdapterHelper.getView(item, null /*parentItem*/, parent, + mCallback, mAdapterRef, mSkipCallbackParser); + mSkipCallbackParser = pair.getSecond(); + return pair.getFirst(); } @Override @@ -134,7 +146,10 @@ public class FakeExpandableAdapter extends BaseAdapter implements ExpandableList // we don't care about recycling here because we never scroll. AdapterItem parentItem = mItems.get(groupPosition); AdapterItem item = getChildItem(groupPosition, childPosition); - return getView(item, parentItem, convertView, parent); + Pair<View, Boolean> pair = AdapterHelper.getView(item, parentItem, parent, mCallback, + mAdapterRef, mSkipCallbackParser); + mSkipCallbackParser = pair.getSecond(); + return pair.getFirst(); } @Override @@ -172,6 +187,31 @@ public class FakeExpandableAdapter extends BaseAdapter implements ExpandableList // pass } + @Override + public void registerDataSetObserver(DataSetObserver observer) { + // pass + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + // pass + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEmpty() { + return mItems.isEmpty(); + } + // ---- HeterogeneousExpandableList @Override -- cgit v1.1