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 0000000000000..9f266278c352d Binary files /dev/null and b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png differ 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 0000000000000..89009be843e72 Binary files /dev/null and b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png differ 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()); + } }