From 29ed07524ce0fc2e5950f5340d306247145d0efa Mon Sep 17 00:00:00 2001 From: Diego Perez Date: Wed, 14 Oct 2015 14:20:26 +0100 Subject: [PATCH] Add support for Choreographer animations First step to add support for Choreographer based animations. The Choreographer_Delegate avoid using a handler so the animation callbacks can be called on-demand (by using doFrame). This allows things like frame by frame animation or selecting a specific frame, and doesn't need a separate thread to run. The CL also changes the System and SystemClock implementations to allow to set specific times. Because animations heavily rely on the system time, this allows controlling it. It can also be useful to ensure that the rendering produces a deterministic result when using controls like the Calendar widget. Change-Id: Iff221d2698a82075cafbb60f341be01741f7aa13 --- .../src/android/animation/FakeAnimator.java | 52 -------- .../PropertyValuesHolder_Delegate.java | 125 +++++++++++++++--- .../graphics/PathMeasure_Delegate.java | 2 +- .../src/android/os/SystemClock_Delegate.java | 14 +- .../android/view/Choreographer_Delegate.java | 34 +++++ .../com/android/layoutlib/bridge/Bridge.java | 2 +- .../layoutlib/bridge/BridgeRenderSession.java | 16 +++ .../bridge/impl/RenderSessionImpl.java | 24 ++++ .../MyApplication/golden/animated_vector.png | Bin 0 -> 3274 bytes .../golden/animated_vector_1.png | Bin 0 -> 2816 bytes .../res/layout/indeterminate_progressbar.xml | 14 ++ .../layoutlib/bridge/intensive/Main.java | 43 +++++- .../tools/layoutlib/create/CreateInfo.java | 3 + .../create/ReplaceMethodCallsAdapter.java | 30 ++++- .../tools/layoutlib/java/System_Delegate.java | 34 +++++ 15 files changed, 309 insertions(+), 84 deletions(-) delete mode 100644 tools/layoutlib/bridge/src/android/animation/FakeAnimator.java create mode 100644 tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png create mode 100644 tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png create mode 100644 tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml diff --git a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java b/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java deleted file mode 100644 index 78aedc5a31029..0000000000000 --- a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2014 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 android.animation; - -/** - * A fake implementation of Animator which doesn't do anything. - */ -public class FakeAnimator extends Animator { - @Override - public long getStartDelay() { - return 0; - } - - @Override - public void setStartDelay(long startDelay) { - - } - - @Override - public Animator setDuration(long duration) { - return this; - } - - @Override - public long getDuration() { - return 0; - } - - @Override - public void setInterpolator(TimeInterpolator value) { - - } - - @Override - public boolean isRunning() { - return false; - } -} diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java index 4603b6362b0eb..54021c9f988d0 100644 --- a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java +++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java @@ -16,9 +16,16 @@ package android.animation; +import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + /** * Delegate implementing the native methods of android.animation.PropertyValuesHolder * @@ -29,81 +36,161 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; * around to map int to instance of the delegate. * * The main goal of this class' methods are to provide a native way to access setters and getters - * on some object. In this case we want to default to using Java reflection instead so the native - * methods do nothing. + * on some object. We override these methods to use reflection since the original reflection + * implementation of the PropertyValuesHolder won't be able to access protected methods. * */ -/*package*/ class PropertyValuesHolder_Delegate { +/*package*/ +@SuppressWarnings("unused") +class PropertyValuesHolder_Delegate { + // This code is copied from android.animation.PropertyValuesHolder and must be kept in sync + // We try several different types when searching for appropriate setter/getter functions. + // The caller may have supplied values in a type that does not match the setter/getter + // functions (such as the integers 0 and 1 to represent floating point values for alpha). + // Also, the use of generics in constructors means that we end up with the Object versions + // of primitive types (Float vs. float). But most likely, the setter/getter functions + // will take primitive types instead. + // So we supply an ordered array of other types to try before giving up. + private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class, + Double.class, Integer.class}; + private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class, + Float.class, Double.class}; + + private static final Object sMethodIndexLock = new Object(); + private static final Map ID_TO_METHOD = new HashMap(); + private static final Map METHOD_NAME_TO_ID = new HashMap(); + private static long sNextId = 1; + + private static long registerMethod(Class targetClass, String methodName, Class[] types, + int nArgs) { + // Encode the number of arguments in the method name + String methodIndexName = String.format("%1$s#%2$d", methodName, nArgs); + synchronized (sMethodIndexLock) { + Long methodId = METHOD_NAME_TO_ID.get(methodIndexName); + + if (methodId != null) { + // The method was already registered + return methodId; + } + + Class[] args = new Class[nArgs]; + Method method = null; + for (Class typeVariant : types) { + for (int i = 0; i < nArgs; i++) { + args[i] = typeVariant; + } + try { + method = targetClass.getDeclaredMethod(methodName, args); + } catch (NoSuchMethodException ignore) { + } + } + + if (method != null) { + methodId = sNextId++; + ID_TO_METHOD.put(methodId, method); + METHOD_NAME_TO_ID.put(methodIndexName, methodId); + + return methodId; + } + } + + // Method not found + return 0; + } + + private static void callMethod(Object target, long methodID, Object... args) { + Method method = ID_TO_METHOD.get(methodID); + assert method != null; + + try { + method.setAccessible(true); + method.invoke(target, args); + } catch (IllegalAccessException e) { + Bridge.getLog().error(null, "Unable to update property during animation", e, null); + } catch (InvocationTargetException e) { + Bridge.getLog().error(null, "Unable to update property during animation", e, null); + } + } @LayoutlibDelegate /*package*/ static long nGetIntMethod(Class targetClass, String methodName) { - // return 0 to force PropertyValuesHolder to use Java reflection. - return 0; + return nGetMultipleIntMethod(targetClass, methodName, 1); } @LayoutlibDelegate /*package*/ static long nGetFloatMethod(Class targetClass, String methodName) { - // return 0 to force PropertyValuesHolder to use Java reflection. - return 0; + return nGetMultipleFloatMethod(targetClass, methodName, 1); } @LayoutlibDelegate /*package*/ static long nGetMultipleIntMethod(Class targetClass, String methodName, int numParams) { - // TODO: return the right thing. - return 0; + return registerMethod(targetClass, methodName, INTEGER_VARIANTS, numParams); } @LayoutlibDelegate /*package*/ static long nGetMultipleFloatMethod(Class targetClass, String methodName, int numParams) { - // TODO: return the right thing. - return 0; + return registerMethod(targetClass, methodName, FLOAT_VARIANTS, numParams); } @LayoutlibDelegate /*package*/ static void nCallIntMethod(Object target, long methodID, int arg) { - // do nothing + callMethod(target, methodID, arg); } @LayoutlibDelegate /*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) { - // do nothing + callMethod(target, methodID, arg); } @LayoutlibDelegate /*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2) { - // do nothing + callMethod(target, methodID, arg1, arg2); } @LayoutlibDelegate /*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1, int arg2, int arg3, int arg4) { - // do nothing + callMethod(target, methodID, arg1, arg2, arg3, arg4); } @LayoutlibDelegate /*package*/ static void nCallMultipleIntMethod(Object target, long methodID, int[] args) { - // do nothing + assert args != null; + + // Box parameters + Object[] params = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + params[i] = args; + } + callMethod(target, methodID, params); } @LayoutlibDelegate /*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1, float arg2) { - // do nothing + callMethod(target, methodID, arg1, arg2); } @LayoutlibDelegate /*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1, float arg2, float arg3, float arg4) { - // do nothing + callMethod(target, methodID, arg1, arg2, arg3, arg4); } @LayoutlibDelegate /*package*/ static void nCallMultipleFloatMethod(Object target, long methodID, float[] args) { - // do nothing + assert args != null; + + // Box parameters + Object[] params = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + params[i] = args; + } + callMethod(target, methodID, params); } } diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java index dd2978f5c4146..3c71233b1df5c 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java @@ -44,7 +44,7 @@ public final class PathMeasure_Delegate { // ---- delegate data ---- // This governs how accurate the approximation of the Path is. - private static final float PRECISION = 0.002f; + private static final float PRECISION = 0.0002f; /** * Array containing the path points components. There are three components for each point: diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java index 5f0d98b35431b..9677aaf5d07aa 100644 --- a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java +++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java @@ -18,6 +18,7 @@ package android.os; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import com.android.tools.layoutlib.java.System_Delegate; /** * Delegate implementing the native methods of android.os.SystemClock @@ -30,9 +31,6 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; * */ public class SystemClock_Delegate { - private static long sBootTime = System.currentTimeMillis(); - private static long sBootTimeNano = System.nanoTime(); - /** * Returns milliseconds since boot, not counting time spent in deep sleep. * Note: This value may get reset occasionally (before it would @@ -42,7 +40,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long uptimeMillis() { - return System.currentTimeMillis() - sBootTime; + return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis(); } /** @@ -52,7 +50,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long elapsedRealtime() { - return System.currentTimeMillis() - sBootTime; + return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis(); } /** @@ -62,7 +60,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long elapsedRealtimeNanos() { - return System.nanoTime() - sBootTimeNano; + return System_Delegate.nanoTime() - System_Delegate.bootTime(); } /** @@ -72,7 +70,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long currentThreadTimeMillis() { - return System.currentTimeMillis(); + return System_Delegate.currentTimeMillis(); } /** @@ -84,7 +82,7 @@ public class SystemClock_Delegate { */ @LayoutlibDelegate /*package*/ static long currentThreadTimeMicro() { - return System.currentTimeMillis() * 1000; + return System_Delegate.currentTimeMillis() * 1000; } /** diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java index f75ee50306744..01af669e39d3a 100644 --- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java @@ -17,6 +17,8 @@ package android.view; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import java.util.concurrent.atomic.AtomicReference; + /** * Delegate used to provide new implementation of a select few methods of {@link Choreographer} * @@ -25,9 +27,41 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; * */ public class Choreographer_Delegate { + static final AtomicReference mInstance = new AtomicReference(); + + @LayoutlibDelegate + public static Choreographer getInstance() { + if (mInstance.get() == null) { + mInstance.compareAndSet(null, Choreographer.getInstance_Original()); + } + + return mInstance.get(); + } @LayoutlibDelegate public static float getRefreshRate() { return 60.f; } + + @LayoutlibDelegate + static void scheduleVsyncLocked(Choreographer thisChoreographer) { + // do nothing + } + + public static void doFrame(long frameTimeNanos) { + Choreographer thisChoreographer = Choreographer.getInstance(); + + thisChoreographer.mLastFrameTimeNanos = frameTimeNanos; + + thisChoreographer.mFrameInfo.markInputHandlingStart(); + thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); + + thisChoreographer.mFrameInfo.markAnimationsStart(); + thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); + + thisChoreographer.mFrameInfo.markPerformTraversalsStart(); + thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); + + thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index 48ca7d8d5fb6b..683c4aabf6d95 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -183,7 +183,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { */ private static LayoutLog sCurrentLog = sDefaultLog; - private static final int LAST_SUPPORTED_FEATURE = Features.RECYCLER_VIEW_ADAPTER; + private static final int LAST_SUPPORTED_FEATURE = Features.CHOREOGRAPHER; @Override public int getApiLevel() { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index feb25905390cf..2ac212c312c09 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -23,6 +23,7 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.ViewInfo; import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.tools.layoutlib.java.System_Delegate; import android.view.View; import android.view.ViewGroup; @@ -190,6 +191,21 @@ public class BridgeRenderSession extends RenderSession { return mLastResult; } + @Override + public void setSystemTimeNanos(long nanos) { + System_Delegate.setNanosTime(nanos); + } + + @Override + public void setSystemBootTimeNanos(long nanos) { + System_Delegate.setBootTimeNanos(nanos); + } + + @Override + public void setElapsedFrameTimeNanos(long nanos) { + mSession.setElapsedFrameTimeNanos(nanos); + } + @Override public void dispose() { } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 0ffa357331800..ec50cfe556516 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -46,6 +46,7 @@ import com.android.layoutlib.bridge.android.support.DesignLibUtil; import com.android.layoutlib.bridge.impl.binding.FakeAdapter; import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; import com.android.resources.ResourceType; +import com.android.tools.layoutlib.java.System_Delegate; import com.android.util.Pair; import android.animation.AnimationThread; @@ -62,6 +63,7 @@ import android.graphics.Canvas; import android.preference.Preference_Delegate; import android.view.AttachInfo_Accessor; import android.view.BridgeInflater; +import android.view.Choreographer_Delegate; import android.view.IWindowManager; import android.view.IWindowManagerImpl; import android.view.Surface; @@ -120,6 +122,10 @@ public class RenderSessionImpl extends RenderAction { private int mMeasuredScreenWidth = -1; private int mMeasuredScreenHeight = -1; private boolean mIsAlphaChannelImage; + /** If >= 0, a frame will be executed */ + private long mElapsedFrameTimeNanos = -1; + /** True if one frame has been already executed to start the animations */ + private boolean mFirstFrameExecuted = false; // information being returned through the API private BufferedImage mImage; @@ -251,6 +257,14 @@ public class RenderSessionImpl extends RenderAction { } } + /** + * Sets the time for which the next frame will be selected. The time is the elapsed time from + * the current system nanos time. You + */ + public void setElapsedFrameTimeNanos(long nanos) { + mElapsedFrameTimeNanos = nanos; + } + /** * Renders the scene. *

@@ -428,6 +442,16 @@ public class RenderSessionImpl extends RenderAction { gc.dispose(); } + if (mElapsedFrameTimeNanos >= 0) { + long initialTime = System_Delegate.nanoTime(); + if (!mFirstFrameExecuted) { + // The first frame will initialize the animations + Choreographer_Delegate.doFrame(initialTime); + mFirstFrameExecuted = true; + } + // Second frame will move the animations + Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos); + } mViewRoot.draw(mCanvas); } diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png new file mode 100644 index 0000000000000000000000000000000000000000..9f266278c352d80ae0ad1d1d35c0d3893cad80c2 GIT binary patch literal 3274 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 literal 0 HcmV?d00001 diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png new file mode 100644 index 0000000000000000000000000000000000000000..89009be843e72d54edea36e37245c6177cd4d1fa GIT binary patch literal 2816 zcma);dpOf?AICT6l7t_YV+S4Nuwhhks4UDJqloa8L)jb~N{;<1g)bG$l*&(Iin5Rv z!bpZWo17_&j8HUVGd+H%=WspGAJ2VV_x--!_w{~%uKS<+x?ahS_Eu7xl{P~l5Gk}Z z>g0xk8=EH~wsGRCDH9OLW;z;m%;oaX+~}Z#{24V`I2ENHtB0M)ftzKo>|%&skEy@m z=`Z7SRN=(7ZAqR{C9M}yk6E3lkE#zTh#3igFUl>LV?SOwD42SD5z*Rpzo`0XpXs=L zEjy$~mD2A}WdF!G65BCUfxFQhgkO=BK#0JEwm_307JqBvPTIEL{qG7$AW(m)2yE9M z{?FLIYKc4ltp5?a^*?GtTVyuI{5@SHo1GF-*_e4(cNSl!QS%&CHoJ`R!mwUV8ZpsH z3w49pM6Jg^kjaaJy2MSabEY9J1I~L31Xg}O`xrgE=@OMSR*edeYgLDf4)Z92=Mta1 ztI4RSYfTh^(Jj6=m+Zco&!zT5KOsDZ%Wt04i?A%i7HQ~e${^COW*jPaBs2JYk7uf? zcXdw5%c?3%!j34YD)L2J>8|Bs{#8|DW<=R>wqqI*;!E1KYxg8=#ni*+zWZE-40`mG z1gx6i=@E=WWcH1#++#iwqP6iRFS-z3nZ|vTKo&jFQuMR1Hv$4U`<@ z;aL?@yMTV%=2&Q<9&mbTZ_AOzG$U7nviWfL;9h@Wdj8_tnLwp#pSIHLYbV#*4O+C} z!fl|M{|v4!H<=@qwB(-S=N$hSWoacI(dtYUV!8#6-@y0qE@=CO9k?kTZ1`RA15m5t zucqY4R4?Ii9mkR!xdf)5;95eg#DNC_bMLZtsm_;mtJC%vNlgOVo$6B)t|U37ajv%} zr?&TV6zzIsZLw;F#*_7?3t=DER*z-84iU2*pwxU%BkD&HZARhdq9A#xPtI3W8bI!E zxYRgUMrnAnefxks6OSIr*aY)TNl(oj-NzHo`5bd_=k^#ID9IT+SZM6Nl1gU1e-jG6 zl0q2Ev!XC`M)*%+irHJau7|RKO4YaG>6iVXNNl(QfV3S=`L;g$$om2_Q!23=QIR;J zj;P5UyS2|i7-4L`EU!Al2{pR>)v`HsC!~oyNu{ zUH^Isw-qYBT> zR$meOHYHEHj>Z-v%YhzD>c;&M<915Z@ZjiH*qnH6OsnlR&GkHKgzd-sxil?vyey)s zFlrttNuKG6rXg6$K0G}Fxw;=}fwO}TyHuM%lE8f;5DPM{HbEKTSNnnPo4B(H@ryo! z0=1Y*L0cnCrJWC65k(Aj{+P9bhiOX2h%_ZtbG`d zrLA?i+>61vcuuP+=r#`9dHHHa+87xevNo&v7MTX^fI$y|fdf4?!?p2<$K%S?^nO zA4|0e;^yHuwd8133okXajHUR-?1B;I_5SJAPSsE(W$Q z`31NQDa7knbmO0z$pJy`Sf{IOfmiJdUQd{{xgrqc=H8;qHe3Y+)X3T^2TE2h)23aX zEFk?N+w{-Olr-E)Fry6wwq@L%?+Pzf(48I#u6F0A@5P2XaJxvtU zom9QB%tz+y=EwdB$Y|uE5cb zyh@E}GCu+Y@SaYDW9%SS_t4rN#aiblmxKezAv<_t1xKoez&`#}z{&SfJM`;<5KLUf zY0+It2X41g%*;$LU*$t>Zwr3YumY-LMp|tTBgBaIWt7Rll;)eYY_LhJW#b3kDIO>mhvu_U@;L*L?+AjyKex z7YHa#!55}!X;sG;CYGXuJJpgR3RHjw5%5873odx`pwd~I+l9F0)5FJ|T!XM&#?t|q zRa~Kul%;92|H1t=wG5tToL6%AzC(*g9?5mc1^np^#SR~&uIV+aEgW&9q zyVl!*@zp(1!`Oeew*RFlDF6_7sw-P#Plk1}p)A1(Cp|qMZcUP)Cja_pzwvIeeQW1K z#Mnx=VEsf_*i+@yc5Vn7{ZpnT_^I5bEA9j12EFrBqz8+xa=wApDH?R{&F4|)53J^O z4j^C7G<3Z>_0V+B``Vv->qw_AD$l0T4^L!dsNglX{KXxKpD;WANf!Ts3V#Rw9Z3E~ z82=BD{1?YGmjDWIFKtvn4vUYurQJw8`WN0frzR)gEp*7W2@HHTR|i(&zu5TrzCX2HF68Cg1+%ga%RBWDP6=2TwD?97aYnb~^v{79v50wPsGCnG6IY`eNTv9S@w zI^WXL@>;wc+joG~tk3zP2=^iw;yw(hfxPs>^${6heym1ISXj6WsQ;B>ju*;);X$>r zvuppk_O`6-ka^8};y5N~4*dELjT}aU literal 0 HcmV?d00001 diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml new file mode 100644 index 0000000000000..70d739692e29b --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java index 9ebeebd49c829..2dca07cc4fafb 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java @@ -48,6 +48,8 @@ import java.io.IOException; import java.net.URL; import java.util.Arrays; import java.util.Comparator; +import java.util.Map; +import java.util.concurrent.TimeUnit; import static org.junit.Assert.fail; @@ -348,16 +350,46 @@ public class Main { renderAndVerify(params, "expand_horz_layout.png"); } + /** Test expand_layout.xml */ + @Test + public void testVectorAnimation() throws ClassNotFoundException { + // Create the layout pull parser. + LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + + "indeterminate_progressbar.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); + layoutLibCallback.initResources(); + + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + RenderingMode.V_SCROLL, 22); + + renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2)); + + parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + + "indeterminate_progressbar.xml"); + params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + RenderingMode.V_SCROLL, 22); + renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3)); + } + /** * Create a new rendering session and test that rendering given layout on nexus 5 * doesn't throw any exceptions and matches the provided image. + *

If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time + * indicates how far in the future is. */ - private void renderAndVerify(SessionParams params, String goldenFileName) + private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos) throws ClassNotFoundException { // TODO: Set up action bar handler properly to test menu rendering. // Create session params. RenderSession session = sBridge.createSession(params); + if (frameTimeNanos != -1) { + session.setElapsedFrameTimeNanos(frameTimeNanos); + } + if (!session.getResult().isSuccess()) { getLogger().error(session.getResult().getException(), session.getResult().getErrorMessage()); @@ -376,6 +408,15 @@ public class Main { } } + /** + * Create a new rendering session and test that rendering given layout on nexus 5 + * doesn't throw any exceptions and matches the provided image. + */ + private void renderAndVerify(SessionParams params, String goldenFileName) + throws ClassNotFoundException { + renderAndVerify(params, goldenFileName, -1); + } + /** * Create a new rendering session and test that rendering given layout on nexus 5 * doesn't throw any exceptions and matches the provided image. diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index c9bc62ec3bcb1..b03cf686e7f1b 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -174,7 +174,9 @@ public final class CreateInfo implements ICreateInfo { "android.text.format.DateFormat#is24HourFormat", "android.text.Hyphenator#getSystemHyphenatorLocation", "android.util.Xml#newPullParser", + "android.view.Choreographer#getInstance", "android.view.Choreographer#getRefreshRate", + "android.view.Choreographer#scheduleVsyncLocked", "android.view.Display#updateDisplayInfoLocked", "android.view.Display#getWindowManager", "android.view.LayoutInflater#rInflate", @@ -298,6 +300,7 @@ public final class CreateInfo implements ICreateInfo { }; private final static String[] PROMOTED_FIELDS = new String[] { + "android.view.Choreographer#mLastFrameTimeNanos", "android.widget.SimpleMonthView#mTitle", "android.widget.SimpleMonthView#mCalendar", "android.widget.SimpleMonthView#mDayOfWeekLabelCalendar" diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java index 0b85c48b4e681..5e47261ea89c2 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java @@ -134,7 +134,33 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { } }); - // Case 5: java.util.LinkedHashMap.eldest() + // Case 5: java.lang.System time calls + METHOD_REPLACERS.add(new MethodReplacer() { + @Override + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { + return JAVA_LANG_SYSTEM.equals(owner) && name.equals("nanoTime"); + } + + @Override + public void replace(MethodInformation mi) { + mi.name = "nanoTime"; + mi.owner = Type.getInternalName(System_Delegate.class); + } + }); + METHOD_REPLACERS.add(new MethodReplacer() { + @Override + public boolean isNeeded(String owner, String name, String desc, String sourceClass) { + return JAVA_LANG_SYSTEM.equals(owner) && name.equals("currentTimeMillis"); + } + + @Override + public void replace(MethodInformation mi) { + mi.name = "currentTimeMillis"; + mi.owner = Type.getInternalName(System_Delegate.class); + } + }); + + // Case 6: java.util.LinkedHashMap.eldest() METHOD_REPLACERS.add(new MethodReplacer() { private final String VOID_TO_MAP_ENTRY = @@ -157,7 +183,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor { } }); - // Case 6: android.content.Context.getClassLoader() in LayoutInflater + // Case 7: android.content.Context.getClassLoader() in LayoutInflater METHOD_REPLACERS.add(new MethodReplacer() { // When LayoutInflater asks for a class loader, we must return the class loader that // cannot return app's custom views/classes. This is so that in case of any failure diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java index 613c8d96b8628..be937445c33d4 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java @@ -18,12 +18,22 @@ package com.android.tools.layoutlib.java; import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + /** * Provides dummy implementation of methods that don't exist on the host VM. + * This also providers a time control that allows to set a specific system time. * * @see ReplaceMethodCallsAdapter */ +@SuppressWarnings("unused") public class System_Delegate { + // Current system time + private static AtomicLong mNanosTime = new AtomicLong(System.nanoTime()); + // Time that the system booted up in nanos + private static AtomicLong mBootNanosTime = new AtomicLong(System.nanoTime()); + public static void log(String message) { // ignore. } @@ -31,4 +41,28 @@ public class System_Delegate { public static void log(String message, Throwable th) { // ignore. } + + public static void setNanosTime(long nanos) { + mNanosTime.set(nanos); + } + + public static void setBootTimeNanos(long nanos) { + mBootNanosTime.set(nanos); + } + + public static long nanoTime() { + return mNanosTime.get(); + } + + public static long currentTimeMillis() { + return TimeUnit.NANOSECONDS.toMillis(mNanosTime.get()); + } + + public static long bootTime() { + return mBootNanosTime.get(); + } + + public static long bootTimeMillis() { + return TimeUnit.NANOSECONDS.toMillis(mBootNanosTime.get()); + } }