/*******************************************************************************
* Copyright 2012-present Pixate, Inc.
*
* 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.pixate.freestyle;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.Spinner;
import com.pixate.freestyle.util.ViewUtil;
/**
* An adapter {@link InvocationHandler} that calls
* {@link java.lang.reflect.Proxy#newProxyInstance(ClassLoader, Class[], java.lang.reflect.InvocationHandler)}
* in order to allow us to intercept the getView calls for adapters. The
* {@link Adapter#getView(int, View, ViewGroup)} is called when a {@link View}
* is being recycled, so we re-apply the styling at that point.
*
* @author Shalom Gibly
*/
public class PXAdapterInvocationHandler implements java.lang.reflect.InvocationHandler {
// The methods we would like to intercept.
private static Set<String> interceptedMethods = new HashSet<String>(Arrays.asList("getView",
"getDropDownView"));
private Adapter adapter;
private WeakReference<AdapterView<? extends Adapter>> viewRef;
/**
* Creates a new proxied instance of the given adapter.
*
* @param obj An {@link AdapterView} instance. The proxy will be made for
* its {@link Adapter}.
* @param adapterInterfaces The interfaces that will be implemented on the
* fly by the proxy
* @return A new proxy for the {@link Adapter}
*/
public static Object newInstance(AdapterView<Adapter> adapterView, Class<?>[] adapterInterfaces) {
Adapter adapter = adapterView.getAdapter();
return java.lang.reflect.Proxy.newProxyInstance(adapter.getClass().getClassLoader(),
adapterInterfaces, new PXAdapterInvocationHandler(adapterView));
}
private PXAdapterInvocationHandler(AdapterView<Adapter> adapterView) {
this.adapter = adapterView.getAdapter();
this.viewRef = new WeakReference<AdapterView<? extends Adapter>>(adapterView);
}
/**
* Returns the original {@link Adapter} that is being proxied.
*
* @return The original {@link Adapter} instance.
*/
public Adapter getOriginal() {
return adapter;
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
Object result;
try {
result = m.invoke(adapter, args);
if (interceptedMethods.contains(m.getName()) && result instanceof View) {
// Unfortunately, at this point this view is not yet
// connected to its parent, so we can't style it yet.
// We tag the view with its location in the adapter and the
// current child count. We'll try use those values in cases
// where we can't locate a parent.
View view = (View) result;
view.setTag(ViewUtil.TAG_ELEMENT_INDEX, args[0]);
view.setTag(ViewUtil.TAG_ELEMENTS_COUNT, adapter.getCount());
if (viewRef.get() != args[2]) {
// change the view-reference to what we get here (this will
// happen when dealing with Spinners)
@SuppressWarnings("unchecked")
AdapterView<? extends Adapter> newAdapterView = (AdapterView<? extends Adapter>) args[2];
if (viewRef.get() instanceof Spinner) {
// Set the current spinner reference as a 'future
// parent'. We may need that later for size
// computations.
newAdapterView.setTag(ViewUtil.TAG_ELEMENT_FUTURE_PARENT, viewRef);
// Make a call to style the 'new' adapter view. This is
// the ListView that appears when the Spinner is
// clicked. We avoid styling the children of that
// adapter here. Those will be taken care later.
PixateFreestyle.style(newAdapterView, false);
}
viewRef = new WeakReference<AdapterView<? extends Adapter>>(newAdapterView);
}
view.setTag(ViewUtil.TAG_ELEMENT_FUTURE_PARENT, viewRef);
// Call to style
PixateFreestyle.style(view);
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: " + e.getMessage());
}
return result;
}
}