blob: b2b7e7084b7a8a8a8b7de3cad280639b1e2ae799 [file] [log] [blame]
/*
* Copyright (C) 2008 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 com.android.tools.layoutlib.create;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import com.android.tools.layoutlib.java.LinkedHashMap_Delegate;
import com.android.tools.layoutlib.java.Reference_Delegate;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
* Describes the work to be done by {@link AsmGenerator}.
*/
public final class CreateInfo implements ICreateInfo {
@Override
public MethodReplacer[] getMethodReplacers() {
return METHOD_REPLACERS;
}
@Override
public Class<?>[] getInjectedClasses() {
return INJECTED_CLASSES;
}
@Override
public String[] getDelegateMethods() {
return DELEGATE_METHODS;
}
@Override
public String[] getDelegateClassNatives() {
return DELEGATE_CLASS_NATIVES;
}
@Override
public String[] getDelegateClassNativesToNatives() {
return DELEGATE_CLASS_NATIVES_TO_NATIVES;
}
@Override
public boolean shouldKeepAllNativeClasses() {
return false;
}
@Override
public String[] getKeepClassNatives() {
return KEEP_CLASS_NATIVES;
}
@Override
public String[] getRenamedClasses() {
return RENAMED_CLASSES;
}
@Override
public String[] getDeleteReturns() {
return DELETE_RETURNS;
}
@Override
public String[] getJavaPkgClasses() {
return JAVA_PKG_CLASSES;
}
@Override
public String[] getRefactoredClasses() {
return REFACTOR_CLASSES;
}
@Override
public String[] getExcludedClasses() {
String[] refactoredClasses = getJavaPkgClasses();
int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
Set<String> excludedClasses = new HashSet<>(count);
for (int i = 0; i < refactoredClasses.length; i+=2) {
excludedClasses.add(refactoredClasses[i]);
}
excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES));
return excludedClasses.toArray(new String[0]);
}
@Override
public String[] getPromotedFields() {
return PROMOTED_FIELDS;
}
@Override
public String[] getPromotedMethods() {
return PROMOTED_METHODS;
}
@Override
public String[] getPromotedClasses() {
return PROMOTED_CLASSES;
}
@Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return INJECTED_METHODS;
}
@Override
public String[] getDeferredStaticInitializerClasses() {
return DEFERRED_STATIC_INITIALIZER_CLASSES;
}
@Override
public String[] getRemovedFinalModifierFields() {
return REMOVED_FINAL_MODIFIER_FIELDS;
}
//-----
private static final MethodReplacer[] METHOD_REPLACERS = new MethodReplacer[] {
new SystemLoadLibraryReplacer(),
new SystemArrayCopyReplacer(),
new LocaleGetDefaultReplacer(),
new SystemLogReplacer(),
new SystemNanoTimeReplacer(),
new SystemCurrentTimeMillisReplacer(),
new LinkedHashMapEldestReplacer(),
new ContextGetClassLoaderReplacer(),
new ImageReaderNativeInitReplacer(),
new NativeInitPathReplacer(),
new AdaptiveIconMaskReplacer(),
new ActivityThreadInAnimationReplacer(),
new ReferenceRefersToReplacer(),
new HtmlApplicationResourceReplacer(),
};
/**
* The list of class from layoutlib_create to inject in layoutlib.
*/
private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
OverrideMethod.class,
MethodListener.class,
MethodAdapter.class,
ICreateInfo.class,
CreateInfo.class,
LayoutlibDelegate.class,
InjectMethodRunnable.class,
InjectMethodRunnables.class,
/* Java package classes */
LinkedHashMap_Delegate.class,
Reference_Delegate.class,
};
/**
* The list of methods to rewrite as delegates.
*/
public final static String[] DELEGATE_METHODS = NativeConfig.DELEGATE_METHODS;
/**
* The list of classes on which to delegate all native methods.
*/
public final static String[] DELEGATE_CLASS_NATIVES = NativeConfig.DELEGATE_CLASS_NATIVES;
public final static String[] DELEGATE_CLASS_NATIVES_TO_NATIVES = new String[] {};
/**
* The list of classes on which NOT to delegate any native method.
*/
public final static String[] KEEP_CLASS_NATIVES = new String[] {
"android.animation.PropertyValuesHolder",
"android.content.res.StringBlock",
"android.content.res.XmlBlock",
"android.graphics.BaseCanvas",
"android.graphics.BaseRecordingCanvas",
"android.graphics.Bitmap",
"android.graphics.BitmapFactory",
"android.graphics.BitmapShader",
"android.graphics.BlendModeColorFilter",
"android.graphics.BlurMaskFilter",
"android.graphics.BlurShader",
"android.graphics.Camera",
"android.graphics.Canvas",
"android.graphics.CanvasProperty",
"android.graphics.Color",
"android.graphics.ColorFilter",
"android.graphics.ColorMatrixColorFilter",
"android.graphics.ColorSpace$Rgb",
"android.graphics.ComposePathEffect",
"android.graphics.ComposeShader",
"android.graphics.CornerPathEffect",
"android.graphics.DashPathEffect",
"android.graphics.DiscretePathEffect",
"android.graphics.DrawFilter",
"android.graphics.EmbossMaskFilter",
"android.graphics.FontFamily",
"android.graphics.ImageDecoder",
"android.graphics.Interpolator",
"android.graphics.LightingColorFilter",
"android.graphics.LinearGradient",
"android.graphics.MaskFilter",
"android.graphics.Matrix",
"android.graphics.NinePatch",
"android.graphics.Paint",
"android.graphics.PaintFlagsDrawFilter",
"android.graphics.Path",
"android.graphics.PathDashPathEffect",
"android.graphics.PathEffect",
"android.graphics.PathMeasure",
"android.graphics.Picture",
"android.graphics.PorterDuffColorFilter",
"android.graphics.RadialGradient",
"android.graphics.RecordingCanvas",
"android.graphics.Region",
"android.graphics.RegionIterator",
"android.graphics.RenderEffect",
"android.graphics.RenderNode",
"android.graphics.RuntimeShader",
"android.graphics.Shader",
"android.graphics.SumPathEffect",
"android.graphics.SweepGradient",
"android.graphics.TableMaskFilter",
"android.graphics.Typeface",
"android.graphics.YuvImage",
"android.graphics.animation.NativeInterpolatorFactory",
"android.graphics.animation.RenderNodeAnimator",
"android.graphics.drawable.AnimatedVectorDrawable",
"android.graphics.drawable.VectorDrawable",
"android.graphics.fonts.Font",
"android.graphics.fonts.Font$Builder",
"android.graphics.fonts.FontFamily",
"android.graphics.fonts.FontFamily$Builder",
"android.graphics.fonts.FontFileUtil",
"android.graphics.fonts.SystemFonts",
"android.graphics.text.PositionedGlyphs",
"android.graphics.text.LineBreaker",
"android.graphics.text.MeasuredText",
"android.graphics.text.MeasuredText$Builder",
"android.graphics.text.TextRunShaper",
"android.os.SystemProperties",
"android.os.Trace",
"android.text.AndroidCharacter",
"android.util.Log",
"android.util.PathParser",
"android.view.MotionEvent",
"android.view.Surface",
"com.android.internal.util.VirtualRefBasePtr",
};
/**
* The list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
*/
private final static String[] RENAMED_CLASSES =
new String[] {
"android.os.ServiceManager", "android.os._Original_ServiceManager",
"android.view.textservice.TextServicesManager", "android.view.textservice._Original_TextServicesManager",
"android.view.SurfaceView", "android.view._Original_SurfaceView",
"android.view.WindowManagerImpl", "android.view._Original_WindowManagerImpl",
"android.webkit.WebView", "android.webkit._Original_WebView",
};
/**
* The list of class references to update, must be an even list: the binary
* FQCN of class to replace followed by the new FQCN. The classes to
* replace are to be excluded from the output.
*/
private final static String[] JAVA_PKG_CLASSES =
new String[] {
"sun.misc.Cleaner", "com.android.layoutlib.bridge.libcore.util.Cleaner",
};
/**
* List of classes to refactor. This is similar to combining {@link #getRenamedClasses()} and
* {@link #getJavaPkgClasses()}.
* Classes included here will be renamed and then all their references in any other classes
* will be also modified.
* FQCN of class to refactor followed by its new FQCN.
*/
private final static String[] REFACTOR_CLASSES =
new String[] {
"android.os.Build", "android.os._Original_Build",
};
private final static String[] EXCLUDED_CLASSES =
new String[] {
"android.preference.PreferenceActivity",
"java.**",
"kotlin.**",
"org.kxml2.io.KXmlParser",
"org.xmlpull.**",
"sun.**",
};
/**
* List of fields for which we will update the visibility to be public. This is sometimes
* needed when access from the delegate classes is needed.
*/
private final static String[] PROMOTED_FIELDS = new String[] {
"android.animation.AnimationHandler#mDelayedCallbackStartTime",
"android.animation.AnimationHandler#mAnimationCallbacks",
"android.animation.AnimationHandler#mCommitCallbacks",
"android.animation.AnimatorSet#mLastFrameTime",
"android.animation.PropertyValuesHolder#sSetterPropertyMap",
"android.animation.PropertyValuesHolder#sGetterPropertyMap",
"android.animation.PropertyValuesHolder$IntPropertyValuesHolder#sJNISetterPropertyMap",
"android.animation.PropertyValuesHolder$FloatPropertyValuesHolder#sJNISetterPropertyMap",
"android.animation.PropertyValuesHolder$MultiFloatValuesHolder#sJNISetterPropertyMap",
"android.animation.PropertyValuesHolder$MultiIntValuesHolder#sJNISetterPropertyMap",
"android.graphics.ImageDecoder$InputStreamSource#mInputStream",
"android.graphics.Typeface#DEFAULT_FAMILY",
"android.graphics.Typeface#sDynamicTypefaceCache",
"android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorUI#mSet",
"android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#mPendingAnimationActions",
"android.graphics.drawable.AnimatedVectorDrawable#mAnimatorSet",
"android.graphics.drawable.DrawableInflater#mRes",
"android.hardware.input.InputManagerGlobal#sInstance",
"android.view.Choreographer#mCallbackQueues", // required for tests only
"android.view.Choreographer$CallbackQueue#mHead", // required for tests only
"android.view.ViewRootImpl#mTmpFrames",
"com.android.internal.util.ArrayUtils#sCache",
};
/**
* List of methods for which we will update the visibility to be public.
*/
private final static String[] PROMOTED_METHODS = new String[] {
"android.animation.AnimationHandler#doAnimationFrame",
"android.content.res.StringBlock#addParagraphSpan",
"android.content.res.StringBlock#getColor",
"android.graphics.Bitmap#setNinePatchChunk",
"android.graphics.Path#nInit",
"android.graphics.Typeface$Builder#createAssetUid",
"android.hardware.input.InputManagerGlobal#<init>",
"android.media.ImageReader#nativeClassInit",
"android.view.Choreographer#doFrame",
"android.view.Choreographer#postCallbackDelayedInternal",
"android.view.Choreographer#removeCallbacksInternal",
"android.view.ViewRootImpl#getRootMeasureSpec",
};
/**
* List of classes to be promoted to public visibility. Prefer using PROMOTED_FIELDS to this
* if possible.
*/
private final static String[] PROMOTED_CLASSES = new String[] {
"android.content.res.StringBlock$Height",
"android.graphics.ImageDecoder$InputStreamSource",
"android.graphics.ImageDecoder$ResourceSource",
"android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorUI",
"android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimator",
"android.view.Choreographer$CallbackQueue", // required for tests only
};
/**
* List of classes for which the methods returning them should be deleted.
* The array contains a list of null terminated section starting with the name of the class
* to rename in which the methods are deleted, followed by a list of return types identifying
* the methods to delete.
*/
private final static String[] DELETE_RETURNS =
new String[] {
null }; // separator, for next class/methods list.
private final static String[] DEFERRED_STATIC_INITIALIZER_CLASSES =
NativeConfig.DEFERRED_STATIC_INITIALIZER_CLASSES;
private final static Map<String, InjectMethodRunnable> INJECTED_METHODS = Map.of(
"android.content.Context", InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER);
/**
* List of fields for which we will remove the final modifier.
*/
private final static String[] REMOVED_FINAL_MODIFIER_FIELDS =
new String[]{"android.animation.AnimationHandler#sAnimatorHandler"};
public static class LinkedHashMapEldestReplacer implements MethodReplacer {
private final String VOID_TO_MAP_ENTRY =
Type.getMethodDescriptor(Type.getType(Map.Entry.class));
private final String LINKED_HASH_MAP = Type.getInternalName(LinkedHashMap.class);
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return LINKED_HASH_MAP.equals(owner) &&
"eldest".equals(name) &&
VOID_TO_MAP_ENTRY.equals(desc);
}
@Override
public void replace(MethodInformation mi) {
mi.opcode = Opcodes.INVOKESTATIC;
mi.owner = Type.getInternalName(LinkedHashMap_Delegate.class);
mi.desc = Type.getMethodDescriptor(
Type.getType(Map.Entry.class), Type.getType(LinkedHashMap.class));
}
}
private static class ContextGetClassLoaderReplacer implements 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
// or exception when instantiating the views, the IDE can replace it with a mock view
// and have proper error handling. However, if a custom view asks for the class
// loader, we must return a class loader that can find app's custom views as well.
// Thus, we rewrite the call to get class loader in LayoutInflater to
// getFrameworkClassLoader and inject a new method in Context. This leaves the normal
// method: Context.getClassLoader() free to be used by the apps.
private final String VOID_TO_CLASS_LOADER =
Type.getMethodDescriptor(Type.getType(ClassLoader.class));
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return owner.equals("android/content/Context") &&
sourceClass.equals("android/view/LayoutInflater") &&
name.equals("getClassLoader") &&
desc.equals(VOID_TO_CLASS_LOADER);
}
@Override
public void replace(MethodInformation mi) {
mi.name = "getFrameworkClassLoader";
}
}
private static class SystemCurrentTimeMillisReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return Type.getInternalName(System.class).equals(owner) && name.equals("currentTimeMillis");
}
@Override
public void replace(MethodInformation mi) {
mi.name = "currentTimeMillis";
mi.owner = "com/android/internal/lang/System_Delegate";
}
}
private static class SystemNanoTimeReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return Type.getInternalName(System.class).equals(owner) && name.equals("nanoTime");
}
@Override
public void replace(MethodInformation mi) {
mi.name = "nanoTime";
mi.owner = "com/android/internal/lang/System_Delegate";
}
}
public static class SystemLogReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return Type.getInternalName(System.class).equals(owner) && name.length() == 4
&& name.startsWith("log");
}
@Override
public void replace(MethodInformation mi) {
assert mi.desc.equals("(Ljava/lang/String;Ljava/lang/Throwable;)V")
|| mi.desc.equals("(Ljava/lang/String;)V");
mi.name = "log";
mi.owner = "com/android/internal/lang/System_Delegate";
}
}
/**
* Platform code should not loadLibrary on its own. Layoutlib loading infrastructure takes case
* of loading all the necessary native libraries (having the right paths etc.)
*/
public static class SystemLoadLibraryReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return Type.getInternalName(System.class).equals(owner) && name.equals("loadLibrary");
}
@Override
public void replace(MethodInformation mi) {
mi.owner = "com/android/internal/lang/System_Delegate";
}
}
/**
* This is to replace a static call to a dummy, so that ImageReader can be loaded and accessed
* during JNI loading
*/
public static class ImageReaderNativeInitReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return "android/media/ImageReader".equals(owner) && name.equals("nativeClassInit");
}
@Override
public void replace(MethodInformation mi) {
mi.owner = "android/media/ImageReader_Delegate";
mi.opcode = Opcodes.INVOKESTATIC;
}
}
private static class LocaleGetDefaultReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return Type.getInternalName(Locale.class).equals(owner)
&& "getDefault".equals(name)
&& desc.equals(Type.getMethodDescriptor(Type.getType(Locale.class)));
}
@Override
public void replace(MethodInformation mi) {
mi.owner = "com/android/layoutlib/bridge/android/AndroidLocale";
}
}
private static class SystemArrayCopyReplacer implements MethodReplacer {
/**
* Descriptors for specialized versions {@link System#arraycopy} that are not present on the
* Desktop VM.
*/
private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<>(Arrays.asList(
"([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
"([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return Type.getInternalName(System.class).equals(owner) && "arraycopy".equals(name) &&
ARRAYCOPY_DESCRIPTORS.contains(desc);
}
@Override
public void replace(MethodInformation mi) {
mi.desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
}
}
public static class NativeInitPathReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return "android/graphics/Path".equals(owner) &&
"nInit".equals(name) && "(J)J".equals(desc);
}
@Override
public void replace(MethodInformation mi) {
mi.owner = "android/graphics/Path_Delegate";
mi.opcode = Opcodes.INVOKESTATIC;
}
}
public static class AdaptiveIconMaskReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return "android/graphics/drawable/AdaptiveIconDrawable".equals(sourceClass) &&
"android/content/res/Resources".equals(owner) &&
name.equals("getString");
}
@Override
public void replace(MethodInformation mi) {
mi.owner = "android/graphics/drawable/AdaptiveIconDrawable_Delegate";
mi.name = "getResourceString";
mi.opcode = Opcodes.INVOKESTATIC;
mi.desc = "(Landroid/content/res/Resources;I)Ljava/lang/String;";
}
}
public static class ActivityThreadInAnimationReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return ("android/app/ActivityThread").equals(owner) &&
name.equals("getSystemUiContext") &&
sourceClass.equals("android/view/animation/Animation");
}
@Override
public void replace(MethodInformation mi) {
mi.owner = "android/app/ActivityThread_Delegate";
mi.opcode = Opcodes.INVOKESTATIC;
mi.desc = "()Landroid/content/Context;";
}
}
public static class ReferenceRefersToReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return Type.getInternalName(WeakReference.class).equals(owner) &&
"refersTo".equals(name);
}
@Override
public void replace(MethodInformation mi) {
mi.opcode = Opcodes.INVOKESTATIC;
mi.owner = Type.getInternalName(Reference_Delegate.class);
mi.desc = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Reference.class),
Type.getType(Object.class));
}
}
public static class HtmlApplicationResourceReplacer implements MethodReplacer {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return ("android/text/Html".equals(sourceClass) ||
"android/text/HtmlToSpannedConverter".equals(sourceClass)) &&
"android/app/Application".equals(owner) &&
name.equals("getResources");
}
@Override
public void replace(MethodInformation mi) {
mi.owner = "android/app/Application_Delegate";
mi.name = "getResources";
mi.opcode = Opcodes.INVOKESTATIC;
mi.desc = "(Landroid/app/Application;)Landroid/content/res/Resources;";
}
}
}