Add support for Choreographer animations

am: 29ed07524c

* commit '29ed07524ce0fc2e5950f5340d306247145d0efa':
  Add support for Choreographer animations
This commit is contained in:
Diego Perez
2015-12-01 10:38:17 +00:00
committed by android-build-merger
15 changed files with 309 additions and 84 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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:

View File

@@ -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;
}
/**

View File

@@ -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);
}
}

View File

@@ -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() {

View File

@@ -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() {
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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.

View File

@@ -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"

View File

@@ -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

View File

@@ -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());
}
}