Loading core/java/android/widget/RemoteViews.java +90 −97 Original line number Original line Diff line number Diff line Loading @@ -73,6 +73,9 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.Target; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; Loading Loading @@ -188,17 +191,12 @@ public class RemoteViews implements Parcelable, Filter { private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); private static final Object[] sMethodsLock = new Object[0]; private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>(); private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods = new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>(); private static final ArrayMap<Method, Method> sAsyncMethods = new ArrayMap<>(); private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() { /** @Override * This key is used to perform lookups in sMethods without causing allocations. protected Object[] initialValue() { */ return new Object[1]; private static final MethodKey sLookupKey = new MethodKey(); } }; /** /** * @hide * @hide Loading Loading @@ -255,37 +253,47 @@ public class RemoteViews implements Parcelable, Filter { } } /** /** * Handle with care! * Stores information related to reflection method lookup. */ */ static class MutablePair<F, S> { static class MethodKey { F first; public Class targetClass; S second; public Class paramClass; public String methodName; MutablePair(F first, S second) { this.first = first; this.second = second; } @Override @Override public boolean equals(Object o) { public boolean equals(Object o) { if (!(o instanceof MutablePair)) { if (!(o instanceof MethodKey)) { return false; return false; } } MutablePair<?, ?> p = (MutablePair<?, ?>) o; MethodKey p = (MethodKey) o; return Objects.equal(p.first, first) && Objects.equal(p.second, second); return Objects.equal(p.targetClass, targetClass) && Objects.equal(p.paramClass, paramClass) && Objects.equal(p.methodName, methodName); } } @Override @Override public int hashCode() { public int hashCode() { return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass) ^ Objects.hashCode(methodName); } public void set(Class targetClass, Class paramClass, String methodName) { this.targetClass = targetClass; this.paramClass = paramClass; this.methodName = methodName; } } } } /** /** * This pair is used to perform lookups in sMethods without causing allocations. * Stores information related to reflection method lookup result. */ */ private final MutablePair<String, Class<?>> mPair = static class MethodArgs { new MutablePair<String, Class<?>>(null, null); public MethodHandle syncMethod; public MethodHandle asyncMethod; public String asyncMethodName; } /** /** * This annotation indicates that a subclass of View is allowed to be used * This annotation indicates that a subclass of View is allowed to be used Loading @@ -307,6 +315,12 @@ public class RemoteViews implements Parcelable, Filter { public ActionException(String message) { public ActionException(String message) { super(message); super(message); } } /** * @hide */ public ActionException(Throwable t) { super(t); } } } /** @hide */ /** @hide */ Loading Loading @@ -943,73 +957,66 @@ public class RemoteViews implements Parcelable, Filter { return rect; return rect; } } private Method getMethod(View view, String methodName, Class<?> paramType) { private MethodHandle getMethod(View view, String methodName, Class<?> paramType, Method method; boolean async) { MethodArgs result; Class<? extends View> klass = view.getClass(); Class<? extends View> klass = view.getClass(); synchronized (sMethodsLock) { synchronized (sMethods) { ArrayMap<MutablePair<String, Class<?>>, Method> methods = sMethods.get(klass); // The key is defined by the view class, param class and method name. if (methods == null) { sLookupKey.set(klass, paramType, methodName); methods = new ArrayMap<MutablePair<String, Class<?>>, Method>(); result = sMethods.get(sLookupKey); sMethods.put(klass, methods); } mPair.first = methodName; if (result == null) { mPair.second = paramType; Method method; method = methods.get(mPair); if (method == null) { try { try { if (paramType == null) { if (paramType == null) { method = klass.getMethod(methodName); method = klass.getMethod(methodName); } else { } else { method = klass.getMethod(methodName, paramType); method = klass.getMethod(methodName, paramType); } } } catch (NoSuchMethodException ex) { throw new ActionException("view: " + klass.getName() + " doesn't have method: " + methodName + getParameters(paramType)); } if (!method.isAnnotationPresent(RemotableViewMethod.class)) { if (!method.isAnnotationPresent(RemotableViewMethod.class)) { throw new ActionException("view: " + klass.getName() throw new ActionException("view: " + klass.getName() + " can't use method with RemoteViews: " + " can't use method with RemoteViews: " + methodName + getParameters(paramType)); + methodName + getParameters(paramType)); } } methods.put(new MutablePair<String, Class<?>>(methodName, paramType), method); result = new MethodArgs(); } result.syncMethod = MethodHandles.publicLookup().unreflect(method); result.asyncMethodName = method.getAnnotation(RemotableViewMethod.class).asyncImpl(); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new ActionException("view: " + klass.getName() + " doesn't have method: " + methodName + getParameters(paramType)); } } return method; MethodKey key = new MethodKey(); key.set(klass, paramType, methodName); sMethods.put(key, result); } } /** if (!async) { * @return the async implementation of the provided method. return result.syncMethod; */ private Method getAsyncMethod(Method method) { synchronized (sAsyncMethods) { int valueIndex = sAsyncMethods.indexOfKey(method); if (valueIndex >= 0) { return sAsyncMethods.valueAt(valueIndex); } } // Check this so see if async method is implemented or not. RemotableViewMethod annotation = method.getAnnotation(RemotableViewMethod.class); if (result.asyncMethodName.isEmpty()) { Method asyncMethod = null; return null; if (!annotation.asyncImpl().isEmpty()) { try { asyncMethod = method.getDeclaringClass() .getMethod(annotation.asyncImpl(), method.getParameterTypes()); if (!asyncMethod.getReturnType().equals(Runnable.class)) { throw new ActionException("Async implementation for " + method.getName() + " does not return a Runnable"); } } } catch (NoSuchMethodException ex) { // Async method is lazily loaded. If it is not yet loaded, load now. throw new ActionException("Async implementation declared but not defined for " + if (result.asyncMethod == null) { method.getName()); MethodType asyncType = result.syncMethod.type() .dropParameterTypes(0, 1).changeReturnType(Runnable.class); try { result.asyncMethod = MethodHandles.publicLookup().findVirtual( klass, result.asyncMethodName, asyncType); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new ActionException("Async implementation declared as " + result.asyncMethodName + " but not defined for " + methodName + ": public Runnable " + result.asyncMethodName + " (" + TextUtils.join(",", asyncType.parameterArray()) + ")"); } } } } sAsyncMethods.put(method, asyncMethod); return result.asyncMethod; return asyncMethod; } } } } Loading @@ -1018,12 +1025,6 @@ public class RemoteViews implements Parcelable, Filter { return "(" + paramType + ")"; return "(" + paramType + ")"; } } private static Object[] wrapArg(Object value) { Object[] args = sInvokeArgsTls.get(); args[0] = value; return args; } /** /** * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, Loading Loading @@ -1140,10 +1141,8 @@ public class RemoteViews implements Parcelable, Filter { if (view == null) return; if (view == null) return; try { try { getMethod(view, this.methodName, null).invoke(view); getMethod(view, this.methodName, null, false /* async */).invoke(view); } catch (ActionException e) { } catch (Throwable ex) { throw e; } catch (Exception ex) { throw new ActionException(ex); throw new ActionException(ex); } } } } Loading Loading @@ -1516,12 +1515,9 @@ public class RemoteViews implements Parcelable, Filter { if (param == null) { if (param == null) { throw new ActionException("bad type: " + this.type); throw new ActionException("bad type: " + this.type); } } try { try { getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value)); getMethod(view, this.methodName, param, false /* async */).invoke(view, this.value); } catch (ActionException e) { } catch (Throwable ex) { throw e; } catch (Exception ex) { throw new ActionException(ex); throw new ActionException(ex); } } } } Loading @@ -1537,11 +1533,10 @@ public class RemoteViews implements Parcelable, Filter { } } try { try { Method method = getMethod(view, this.methodName, param); MethodHandle method = getMethod(view, this.methodName, param, true /* async */); Method asyncMethod = getAsyncMethod(method); if (asyncMethod != null) { if (method != null) { Runnable endAction = (Runnable) asyncMethod.invoke(view, wrapArg(this.value)); Runnable endAction = (Runnable) method.invoke(view, this.value); if (endAction == null) { if (endAction == null) { return ACTION_NOOP; return ACTION_NOOP; } else { } else { Loading @@ -1555,9 +1550,7 @@ public class RemoteViews implements Parcelable, Filter { return new RunnableAction(endAction); return new RunnableAction(endAction); } } } } } catch (ActionException e) { } catch (Throwable ex) { throw e; } catch (Exception ex) { throw new ActionException(ex); throw new ActionException(ex); } } Loading Loading @@ -2672,7 +2665,7 @@ public class RemoteViews implements Parcelable, Filter { * given {@link RemoteViews}. * given {@link RemoteViews}. * * * @param viewId The id of the parent {@link ViewGroup} to add the child into. * @param viewId The id of the parent {@link ViewGroup} to add the child into. * @param nestedView {@link RemoveViews} of the child to add. * @param nestedView {@link RemoteViews} of the child to add. * @param index The position at which to add the child. * @param index The position at which to add the child. * * * @hide * @hide Loading Loading
core/java/android/widget/RemoteViews.java +90 −97 Original line number Original line Diff line number Diff line Loading @@ -73,6 +73,9 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.Target; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.ArrayList; import java.util.HashMap; import java.util.HashMap; Loading Loading @@ -188,17 +191,12 @@ public class RemoteViews implements Parcelable, Filter { private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); private static final Object[] sMethodsLock = new Object[0]; private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>(); private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods = new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>(); private static final ArrayMap<Method, Method> sAsyncMethods = new ArrayMap<>(); private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() { /** @Override * This key is used to perform lookups in sMethods without causing allocations. protected Object[] initialValue() { */ return new Object[1]; private static final MethodKey sLookupKey = new MethodKey(); } }; /** /** * @hide * @hide Loading Loading @@ -255,37 +253,47 @@ public class RemoteViews implements Parcelable, Filter { } } /** /** * Handle with care! * Stores information related to reflection method lookup. */ */ static class MutablePair<F, S> { static class MethodKey { F first; public Class targetClass; S second; public Class paramClass; public String methodName; MutablePair(F first, S second) { this.first = first; this.second = second; } @Override @Override public boolean equals(Object o) { public boolean equals(Object o) { if (!(o instanceof MutablePair)) { if (!(o instanceof MethodKey)) { return false; return false; } } MutablePair<?, ?> p = (MutablePair<?, ?>) o; MethodKey p = (MethodKey) o; return Objects.equal(p.first, first) && Objects.equal(p.second, second); return Objects.equal(p.targetClass, targetClass) && Objects.equal(p.paramClass, paramClass) && Objects.equal(p.methodName, methodName); } } @Override @Override public int hashCode() { public int hashCode() { return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass) ^ Objects.hashCode(methodName); } public void set(Class targetClass, Class paramClass, String methodName) { this.targetClass = targetClass; this.paramClass = paramClass; this.methodName = methodName; } } } } /** /** * This pair is used to perform lookups in sMethods without causing allocations. * Stores information related to reflection method lookup result. */ */ private final MutablePair<String, Class<?>> mPair = static class MethodArgs { new MutablePair<String, Class<?>>(null, null); public MethodHandle syncMethod; public MethodHandle asyncMethod; public String asyncMethodName; } /** /** * This annotation indicates that a subclass of View is allowed to be used * This annotation indicates that a subclass of View is allowed to be used Loading @@ -307,6 +315,12 @@ public class RemoteViews implements Parcelable, Filter { public ActionException(String message) { public ActionException(String message) { super(message); super(message); } } /** * @hide */ public ActionException(Throwable t) { super(t); } } } /** @hide */ /** @hide */ Loading Loading @@ -943,73 +957,66 @@ public class RemoteViews implements Parcelable, Filter { return rect; return rect; } } private Method getMethod(View view, String methodName, Class<?> paramType) { private MethodHandle getMethod(View view, String methodName, Class<?> paramType, Method method; boolean async) { MethodArgs result; Class<? extends View> klass = view.getClass(); Class<? extends View> klass = view.getClass(); synchronized (sMethodsLock) { synchronized (sMethods) { ArrayMap<MutablePair<String, Class<?>>, Method> methods = sMethods.get(klass); // The key is defined by the view class, param class and method name. if (methods == null) { sLookupKey.set(klass, paramType, methodName); methods = new ArrayMap<MutablePair<String, Class<?>>, Method>(); result = sMethods.get(sLookupKey); sMethods.put(klass, methods); } mPair.first = methodName; if (result == null) { mPair.second = paramType; Method method; method = methods.get(mPair); if (method == null) { try { try { if (paramType == null) { if (paramType == null) { method = klass.getMethod(methodName); method = klass.getMethod(methodName); } else { } else { method = klass.getMethod(methodName, paramType); method = klass.getMethod(methodName, paramType); } } } catch (NoSuchMethodException ex) { throw new ActionException("view: " + klass.getName() + " doesn't have method: " + methodName + getParameters(paramType)); } if (!method.isAnnotationPresent(RemotableViewMethod.class)) { if (!method.isAnnotationPresent(RemotableViewMethod.class)) { throw new ActionException("view: " + klass.getName() throw new ActionException("view: " + klass.getName() + " can't use method with RemoteViews: " + " can't use method with RemoteViews: " + methodName + getParameters(paramType)); + methodName + getParameters(paramType)); } } methods.put(new MutablePair<String, Class<?>>(methodName, paramType), method); result = new MethodArgs(); } result.syncMethod = MethodHandles.publicLookup().unreflect(method); result.asyncMethodName = method.getAnnotation(RemotableViewMethod.class).asyncImpl(); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new ActionException("view: " + klass.getName() + " doesn't have method: " + methodName + getParameters(paramType)); } } return method; MethodKey key = new MethodKey(); key.set(klass, paramType, methodName); sMethods.put(key, result); } } /** if (!async) { * @return the async implementation of the provided method. return result.syncMethod; */ private Method getAsyncMethod(Method method) { synchronized (sAsyncMethods) { int valueIndex = sAsyncMethods.indexOfKey(method); if (valueIndex >= 0) { return sAsyncMethods.valueAt(valueIndex); } } // Check this so see if async method is implemented or not. RemotableViewMethod annotation = method.getAnnotation(RemotableViewMethod.class); if (result.asyncMethodName.isEmpty()) { Method asyncMethod = null; return null; if (!annotation.asyncImpl().isEmpty()) { try { asyncMethod = method.getDeclaringClass() .getMethod(annotation.asyncImpl(), method.getParameterTypes()); if (!asyncMethod.getReturnType().equals(Runnable.class)) { throw new ActionException("Async implementation for " + method.getName() + " does not return a Runnable"); } } } catch (NoSuchMethodException ex) { // Async method is lazily loaded. If it is not yet loaded, load now. throw new ActionException("Async implementation declared but not defined for " + if (result.asyncMethod == null) { method.getName()); MethodType asyncType = result.syncMethod.type() .dropParameterTypes(0, 1).changeReturnType(Runnable.class); try { result.asyncMethod = MethodHandles.publicLookup().findVirtual( klass, result.asyncMethodName, asyncType); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new ActionException("Async implementation declared as " + result.asyncMethodName + " but not defined for " + methodName + ": public Runnable " + result.asyncMethodName + " (" + TextUtils.join(",", asyncType.parameterArray()) + ")"); } } } } sAsyncMethods.put(method, asyncMethod); return result.asyncMethod; return asyncMethod; } } } } Loading @@ -1018,12 +1025,6 @@ public class RemoteViews implements Parcelable, Filter { return "(" + paramType + ")"; return "(" + paramType + ")"; } } private static Object[] wrapArg(Object value) { Object[] args = sInvokeArgsTls.get(); args[0] = value; return args; } /** /** * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, Loading Loading @@ -1140,10 +1141,8 @@ public class RemoteViews implements Parcelable, Filter { if (view == null) return; if (view == null) return; try { try { getMethod(view, this.methodName, null).invoke(view); getMethod(view, this.methodName, null, false /* async */).invoke(view); } catch (ActionException e) { } catch (Throwable ex) { throw e; } catch (Exception ex) { throw new ActionException(ex); throw new ActionException(ex); } } } } Loading Loading @@ -1516,12 +1515,9 @@ public class RemoteViews implements Parcelable, Filter { if (param == null) { if (param == null) { throw new ActionException("bad type: " + this.type); throw new ActionException("bad type: " + this.type); } } try { try { getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value)); getMethod(view, this.methodName, param, false /* async */).invoke(view, this.value); } catch (ActionException e) { } catch (Throwable ex) { throw e; } catch (Exception ex) { throw new ActionException(ex); throw new ActionException(ex); } } } } Loading @@ -1537,11 +1533,10 @@ public class RemoteViews implements Parcelable, Filter { } } try { try { Method method = getMethod(view, this.methodName, param); MethodHandle method = getMethod(view, this.methodName, param, true /* async */); Method asyncMethod = getAsyncMethod(method); if (asyncMethod != null) { if (method != null) { Runnable endAction = (Runnable) asyncMethod.invoke(view, wrapArg(this.value)); Runnable endAction = (Runnable) method.invoke(view, this.value); if (endAction == null) { if (endAction == null) { return ACTION_NOOP; return ACTION_NOOP; } else { } else { Loading @@ -1555,9 +1550,7 @@ public class RemoteViews implements Parcelable, Filter { return new RunnableAction(endAction); return new RunnableAction(endAction); } } } } } catch (ActionException e) { } catch (Throwable ex) { throw e; } catch (Exception ex) { throw new ActionException(ex); throw new ActionException(ex); } } Loading Loading @@ -2672,7 +2665,7 @@ public class RemoteViews implements Parcelable, Filter { * given {@link RemoteViews}. * given {@link RemoteViews}. * * * @param viewId The id of the parent {@link ViewGroup} to add the child into. * @param viewId The id of the parent {@link ViewGroup} to add the child into. * @param nestedView {@link RemoveViews} of the child to add. * @param nestedView {@link RemoteViews} of the child to add. * @param index The position at which to add the child. * @param index The position at which to add the child. * * * @hide * @hide Loading