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
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Long, Method> ID_TO_METHOD = new HashMap<Long, Method>();
|
||||
private static final Map<String, Long> METHOD_NAME_TO_ID = new HashMap<String, Long>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
* <b>Note:</b> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Choreographer> mInstance = new AtomicReference<Choreographer>();
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
@@ -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<SessionParams> {
|
||||
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<SessionParams> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
@@ -428,6 +442,16 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:padding="16dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -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.
|
||||
* <p/>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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user