DO NOT MERGE - Merge Android 10 into master
Bug: 139893257
Change-Id: I6cf6c323b06b1b2f50112d30b162d1530ef816cf
diff --git a/.gitignore b/.gitignore
index a2b0c33..d886ce1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
bin
-/.idea/workspace.xml
/out
/bridge/out
-/.idea/kotlinc.xml
\ No newline at end of file
+/.idea/kotlinc.xml
+/.idea/shelf
+/.idea/workspace.xml
diff --git a/.idea/runConfigurations/All_in_bridge.xml b/.idea/runConfigurations/All_in_bridge.xml
index 0b22717..07e39cd 100644
--- a/.idea/runConfigurations/All_in_bridge.xml
+++ b/.idea/runConfigurations/All_in_bridge.xml
@@ -1,25 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All in bridge" type="JUnit" factoryName="JUnit" singleton="true">
- <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bridge" />
- <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
- <option name="ALTERNATIVE_JRE_PATH" value="" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
+ <option name="ALTERNATIVE_JRE_PATH" value="1.8.0_60-b23" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
- <option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
- <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
- <option name="ENV_VARIABLES" />
- <option name="PASS_PARENT_ENVS" value="true" />
- <option name="TEST_SEARCH_SCOPE">
- <value defaultName="singleModule" />
- </option>
- <envs />
- <patterns />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<RunnerSettings RunnerId="Run" />
<ConfigurationWrapper RunnerId="Run" />
- <method />
+ <method v="2">
+ <option name="Make" enabled="true" />
+ </method>
</configuration>
</component>
\ No newline at end of file
diff --git a/.idea/runConfigurations/Create.xml b/.idea/runConfigurations/Create.xml
index 0e7a6c4..a3fd3a1 100644
--- a/.idea/runConfigurations/Create.xml
+++ b/.idea/runConfigurations/Create.xml
@@ -4,7 +4,7 @@
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="com.android.tools.layoutlib.create.Main" />
<module name="create" />
- <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icudata-jarjar_intermediates/classes-jarjar.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icutzdata-jarjar_intermediates/classes-jarjar.jar" />
+ <option name="PROGRAM_PARAMETERS" value="--create-stub out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icudata-jarjar_intermediates/classes.jar out/host/common/obj/JAVA_LIBRARIES/icu4j-icutzdata-jarjar_intermediates/classes.jar" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/../.." />
<RunnerSettings RunnerId="Debug">
diff --git a/Android.bp b/Android.bp
index 99b156e..565a709 100644
--- a/Android.bp
+++ b/Android.bp
@@ -32,5 +32,5 @@
":icu4j-icudata-jarjar{.jar}", // HOST
":icu4j-icutzdata-jarjar{.jar}", // HOST
],
- cmd: "rm -f $(out) && $(location layoutlib_create) $(out) $(in)",
+ cmd: "rm -f $(out) && $(location layoutlib_create) --create-stub $(out) $(in)",
}
diff --git a/bridge/.classpath b/bridge/.classpath
deleted file mode 100644
index 9c4160c..0000000
--- a/bridge/.classpath
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry excluding="org/kxml2/io/" kind="src" path="src"/>
- <classpathentry kind="src" path="tests/src"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar" sourcepath="/ANDROID_SRC/tools/base/layoutlib-api/src/main"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/tools-common/tools-common-prebuilt.jar"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/icu4j/icu4j.jar"/>
- <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/sdk-common/sdk-common.jar"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/guavalib_intermediates/javalib.jar"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/bridge/.project b/bridge/.project
deleted file mode 100644
index e36e71b..0000000
--- a/bridge/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>layoutlib_bridge</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
diff --git a/bridge/Android.bp b/bridge/Android.bp
index 2d75058..f0958dd 100644
--- a/bridge/Android.bp
+++ b/bridge/Android.bp
@@ -18,11 +18,13 @@
name: "layoutlib",
srcs: ["src/**/*.java"],
+ java_version: "1.8",
java_resource_dirs: ["resources"],
libs: [
"layoutlib_api-prebuilt",
"tools-common-prebuilt",
+ "guava",
],
static_libs: [
@@ -35,3 +37,28 @@
targets: ["layoutlib"],
},
}
+
+java_library_host {
+ name: "layoutlib-no-framework",
+
+ srcs: ["src/**/*.java"],
+ java_version: "1.8",
+ java_resource_dirs: ["resources"],
+
+ libs: [
+ "temp_layoutlib",
+ "guava",
+ ],
+
+ static_libs: [
+ "layoutlib_create",
+ "layoutlib_api-prebuilt",
+ "tools-common-prebuilt",
+ "ninepatch-prebuilt",
+ "layoutlib-common",
+ ],
+
+ dist: {
+ targets: ["layoutlib"],
+ },
+}
diff --git a/bridge/bridge.iml b/bridge/bridge.iml
index 90de760..8a65f99 100644
--- a/bridge/bridge.iml
+++ b/bridge/bridge.iml
@@ -63,14 +63,14 @@
</SOURCES>
</library>
</orderEntry>
- <orderEntry type="module-library" scope="TEST">
+ <orderEntry type="module-library">
<library name="guava">
<CLASSES>
- <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0.jar!/" />
+ <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/22.0/guava-22.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
- <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/15.0/guava-15.0-sources.jar!/" />
+ <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/22.0/guava-22.0-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
diff --git a/bridge/resources/bars/status_bar.xml b/bridge/resources/bars/status_bar.xml
index 04571e1..920be50 100644
--- a/bridge/resources/bars/status_bar.xml
+++ b/bridge/resources/bars/status_bar.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <TextView
+ <!-- Spacer -->
+ <View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
diff --git a/bridge/resources/bars/v28/hdpi/ic_sysbar_back.png b/bridge/resources/bars/v28/hdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..aa9f6d4
--- /dev/null
+++ b/bridge/resources/bars/v28/hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/bridge/resources/bars/v28/hdpi/ic_sysbar_back_quick_step.png b/bridge/resources/bars/v28/hdpi/ic_sysbar_back_quick_step.png
new file mode 100644
index 0000000..eea819a
--- /dev/null
+++ b/bridge/resources/bars/v28/hdpi/ic_sysbar_back_quick_step.png
Binary files differ
diff --git a/bridge/resources/bars/v28/hdpi/ic_sysbar_home.png b/bridge/resources/bars/v28/hdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..613afce
--- /dev/null
+++ b/bridge/resources/bars/v28/hdpi/ic_sysbar_home.png
Binary files differ
diff --git a/bridge/resources/bars/v28/hdpi/ic_sysbar_home_quick_step.png b/bridge/resources/bars/v28/hdpi/ic_sysbar_home_quick_step.png
new file mode 100644
index 0000000..8e7d8cb
--- /dev/null
+++ b/bridge/resources/bars/v28/hdpi/ic_sysbar_home_quick_step.png
Binary files differ
diff --git a/bridge/resources/bars/v28/hdpi/ic_sysbar_recent.png b/bridge/resources/bars/v28/hdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..eb80426
--- /dev/null
+++ b/bridge/resources/bars/v28/hdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/bridge/resources/bars/v28/ldrtl-hdpi/ic_sysbar_back.png b/bridge/resources/bars/v28/ldrtl-hdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..2fcfdde
--- /dev/null
+++ b/bridge/resources/bars/v28/ldrtl-hdpi/ic_sysbar_back.png
Binary files differ
diff --git a/bridge/resources/bars/v28/ldrtl-mdpi/ic_sysbar_back.png b/bridge/resources/bars/v28/ldrtl-mdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..48708a5
--- /dev/null
+++ b/bridge/resources/bars/v28/ldrtl-mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/bridge/resources/bars/v28/ldrtl-xhdpi/ic_sysbar_back.png b/bridge/resources/bars/v28/ldrtl-xhdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..3d73184
--- /dev/null
+++ b/bridge/resources/bars/v28/ldrtl-xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/bridge/resources/bars/v28/ldrtl-xxhdpi/ic_sysbar_back.png b/bridge/resources/bars/v28/ldrtl-xxhdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..786935d
--- /dev/null
+++ b/bridge/resources/bars/v28/ldrtl-xxhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/bridge/resources/bars/v28/mdpi/ic_sysbar_back.png b/bridge/resources/bars/v28/mdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..34a11df
--- /dev/null
+++ b/bridge/resources/bars/v28/mdpi/ic_sysbar_back.png
Binary files differ
diff --git a/bridge/resources/bars/v28/mdpi/ic_sysbar_back_quick_step.png b/bridge/resources/bars/v28/mdpi/ic_sysbar_back_quick_step.png
new file mode 100644
index 0000000..d4e5a94
--- /dev/null
+++ b/bridge/resources/bars/v28/mdpi/ic_sysbar_back_quick_step.png
Binary files differ
diff --git a/bridge/resources/bars/v28/mdpi/ic_sysbar_home.png b/bridge/resources/bars/v28/mdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..7c25fc5
--- /dev/null
+++ b/bridge/resources/bars/v28/mdpi/ic_sysbar_home.png
Binary files differ
diff --git a/bridge/resources/bars/v28/mdpi/ic_sysbar_home_quick_step.png b/bridge/resources/bars/v28/mdpi/ic_sysbar_home_quick_step.png
new file mode 100644
index 0000000..0757799
--- /dev/null
+++ b/bridge/resources/bars/v28/mdpi/ic_sysbar_home_quick_step.png
Binary files differ
diff --git a/bridge/resources/bars/v28/mdpi/ic_sysbar_recent.png b/bridge/resources/bars/v28/mdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..1ee9cf5
--- /dev/null
+++ b/bridge/resources/bars/v28/mdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/bridge/resources/bars/v28/status_bar.xml b/bridge/resources/bars/v28/status_bar.xml
new file mode 100644
index 0000000..2c768f4
--- /dev/null
+++ b/bridge/resources/bars/v28/status_bar.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="5dp"
+ android:layout_marginTop="4dp"
+ android:layout_marginRight="5dp"
+ android:gravity="center_vertical"
+ android:textSize="16dp"
+ android:fontFamily="sans-serif-medium"/>
+ <!-- Spacer -->
+ <View
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <!-- The exact size of the wifi icon is specified in order to scale it properly.
+ Without scaling, it appeared huge. This is currently, 70% of the actual size. -->
+ <ImageView
+ android:layout_height="22.4dp"
+ android:layout_width="20.65dp"
+ android:layout_marginTop="1dp"/>
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_marginLeft="3dp"
+ android:layout_marginRight="8dp"
+ android:layout_marginTop="4dp"/>
+</merge>
diff --git a/bridge/resources/bars/v28/xhdpi/ic_sysbar_back.png b/bridge/resources/bars/v28/xhdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..987aac5
--- /dev/null
+++ b/bridge/resources/bars/v28/xhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/bridge/resources/bars/v28/xhdpi/ic_sysbar_back_quick_step.png b/bridge/resources/bars/v28/xhdpi/ic_sysbar_back_quick_step.png
new file mode 100644
index 0000000..407ef28
--- /dev/null
+++ b/bridge/resources/bars/v28/xhdpi/ic_sysbar_back_quick_step.png
Binary files differ
diff --git a/bridge/resources/bars/v28/xhdpi/ic_sysbar_home.png b/bridge/resources/bars/v28/xhdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..0e2a14d
--- /dev/null
+++ b/bridge/resources/bars/v28/xhdpi/ic_sysbar_home.png
Binary files differ
diff --git a/bridge/resources/bars/v28/xhdpi/ic_sysbar_home_quick_step.png b/bridge/resources/bars/v28/xhdpi/ic_sysbar_home_quick_step.png
new file mode 100644
index 0000000..a7fd3a6
--- /dev/null
+++ b/bridge/resources/bars/v28/xhdpi/ic_sysbar_home_quick_step.png
Binary files differ
diff --git a/bridge/resources/bars/v28/xhdpi/ic_sysbar_recent.png b/bridge/resources/bars/v28/xhdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..f810704
--- /dev/null
+++ b/bridge/resources/bars/v28/xhdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/bridge/resources/bars/v28/xxhdpi/ic_sysbar_back.png b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_back.png
new file mode 100644
index 0000000..be03cbe
--- /dev/null
+++ b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_back.png
Binary files differ
diff --git a/bridge/resources/bars/v28/xxhdpi/ic_sysbar_back_quick_step.png b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_back_quick_step.png
new file mode 100644
index 0000000..a1f44dc
--- /dev/null
+++ b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_back_quick_step.png
Binary files differ
diff --git a/bridge/resources/bars/v28/xxhdpi/ic_sysbar_home.png b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_home.png
new file mode 100644
index 0000000..f16aa48
--- /dev/null
+++ b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_home.png
Binary files differ
diff --git a/bridge/resources/bars/v28/xxhdpi/ic_sysbar_home_quick_step.png b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_home_quick_step.png
new file mode 100644
index 0000000..0fb93ca
--- /dev/null
+++ b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_home_quick_step.png
Binary files differ
diff --git a/bridge/resources/bars/v28/xxhdpi/ic_sysbar_recent.png b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_recent.png
new file mode 100644
index 0000000..109aeed
--- /dev/null
+++ b/bridge/resources/bars/v28/xxhdpi/ic_sysbar_recent.png
Binary files differ
diff --git a/bridge/src/android/animation/PropertyValuesHolder_Accessor.java b/bridge/src/android/animation/PropertyValuesHolder_Accessor.java
new file mode 100644
index 0000000..576d0ea
--- /dev/null
+++ b/bridge/src/android/animation/PropertyValuesHolder_Accessor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.animation.PropertyValuesHolder.FloatPropertyValuesHolder;
+import android.animation.PropertyValuesHolder.IntPropertyValuesHolder;
+import android.animation.PropertyValuesHolder.MultiFloatValuesHolder;
+import android.animation.PropertyValuesHolder.MultiIntValuesHolder;
+
+public class PropertyValuesHolder_Accessor {
+ /**
+ * Method used by layoutlib to ensure that {@link Class} instances are not cached by this
+ * class. Caching the {@link Class} instances will lead to leaking the {@link ClassLoader}
+ * used by that class.
+ * In layoutlib, these class loaders are instantiated dynamically to allow for new classes to
+ * be loaded when the user code changes.
+ */
+ public static void clearClassCaches() {
+ PropertyValuesHolder.sGetterPropertyMap.clear();
+ PropertyValuesHolder.sSetterPropertyMap.clear();
+ IntPropertyValuesHolder.sJNISetterPropertyMap.clear();
+ MultiIntValuesHolder.sJNISetterPropertyMap.clear();
+ FloatPropertyValuesHolder.sJNISetterPropertyMap.clear();
+ MultiFloatValuesHolder.sJNISetterPropertyMap.clear();
+ }
+}
diff --git a/bridge/src/android/content/res/AssetManager_Delegate.java b/bridge/src/android/content/res/AssetManager_Delegate.java
index 3b47ea7..6b71752 100644
--- a/bridge/src/android/content/res/AssetManager_Delegate.java
+++ b/bridge/src/android/content/res/AssetManager_Delegate.java
@@ -16,6 +16,7 @@
package android.content.res;
+import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.util.SparseArray;
@@ -32,6 +33,28 @@
*/
public class AssetManager_Delegate {
+ // ---- delegate manager ----
+
+ private static final DelegateManager<AssetManager_Delegate> sManager =
+ new DelegateManager<>(AssetManager_Delegate.class);
+
+ public static DelegateManager<AssetManager_Delegate> getDelegateManager() {
+ return sManager;
+ }
+
+ // ---- delegate methods. ----
+
+ @LayoutlibDelegate
+ /*package*/ static long nativeCreate() {
+ AssetManager_Delegate delegate = new AssetManager_Delegate();
+ return sManager.addNewDelegate(delegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeDestroy(long ptr) {
+ sManager.removeJavaReferenceFor(ptr);
+ }
+
@LayoutlibDelegate
public static InputStream open(AssetManager mgr, String fileName) throws IOException {
return mgr.open_Original(fileName);
@@ -47,13 +70,13 @@
}
@LayoutlibDelegate
- /*package*/ static long newTheme(AssetManager manager) {
+ /*package*/ static long nativeThemeCreate(long ptr) {
return Resources_Theme_Delegate.getDelegateManager()
.addNewDelegate(new Resources_Theme_Delegate());
}
@LayoutlibDelegate
- /*package*/ static void deleteTheme(AssetManager manager, long theme) {
+ /*package*/ static void nativeThemeDestroy(long theme) {
Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme);
}
@@ -61,4 +84,10 @@
/*package*/ static SparseArray<String> getAssignedPackageIdentifiers(AssetManager manager) {
return new SparseArray<>();
}
+
+ @LayoutlibDelegate
+ /*package*/ static String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid() {
+ // AssetManager requires this not to be null
+ return new String[0];
+ }
}
diff --git a/bridge/src/android/content/res/BridgeAssetManager.java b/bridge/src/android/content/res/BridgeAssetManager.java
index a1a4a19..b67d3b6 100644
--- a/bridge/src/android/content/res/BridgeAssetManager.java
+++ b/bridge/src/android/content/res/BridgeAssetManager.java
@@ -13,15 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.content.res;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import com.android.ide.common.rendering.api.AssetRepository;
import com.android.layoutlib.bridge.Bridge;
public class BridgeAssetManager extends AssetManager {
-
- private AssetRepository mAssetRepository;
+ @Nullable private AssetRepository mAssetRepository;
/**
* This initializes the static field {@link AssetManager#sSystem} which is used
@@ -48,11 +48,22 @@
AssetManager.sSystem = null;
}
- public void setAssetRepository(AssetRepository assetRepository) {
+ public void setAssetRepository(@NonNull AssetRepository assetRepository) {
mAssetRepository = assetRepository;
}
+ /**
+ * Clears the AssetRepository reference.
+ */
+ public void releaseAssetRepository() {
+ mAssetRepository = null;
+ }
+
+ @NonNull
public AssetRepository getAssetRepository() {
+ if (mAssetRepository == null) {
+ throw new IllegalStateException("Asset repository is not set");
+ }
return mAssetRepository;
}
diff --git a/bridge/src/android/content/res/BridgeTypedArray.java b/bridge/src/android/content/res/BridgeTypedArray.java
index 9505993..bbb6405 100644
--- a/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/bridge/src/android/content/res/BridgeTypedArray.java
@@ -20,13 +20,18 @@
import com.android.ide.common.rendering.api.AttrResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceNamespace.Resolver;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.internal.util.XmlUtils;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;
+import com.android.resources.ResourceUrl;
import android.annotation.Nullable;
import android.content.res.Resources.Theme;
@@ -68,49 +73,46 @@
private final Resources mBridgeResources;
private final BridgeContext mContext;
- private final boolean mPlatformFile;
private final int[] mResourceId;
private final ResourceValue[] mResourceData;
private final String[] mNames;
- private final boolean[] mIsFramework;
+ private final ResourceNamespace[] mNamespaces;
// Contains ids that are @empty. We still store null in mResourceData for that index, since we
// want to save on the check against empty, each time a resource value is requested.
@Nullable
private int[] mEmptyIds;
- public BridgeTypedArray(Resources resources, BridgeContext context, int len,
- boolean platformFile) {
+ public BridgeTypedArray(Resources resources, BridgeContext context, int len) {
super(resources);
mBridgeResources = resources;
mContext = context;
- mPlatformFile = platformFile;
mResourceId = new int[len];
mResourceData = new ResourceValue[len];
mNames = new String[len];
- mIsFramework = new boolean[len];
+ mNamespaces = new ResourceNamespace[len];
}
/**
* A bridge-specific method that sets a value in the type array
* @param index the index of the value in the TypedArray
* @param name the name of the attribute
- * @param isFramework whether the attribute is in the android namespace.
+ * @param namespace namespace of the attribute
* @param resourceId the reference id of this resource
* @param value the value of the attribute
*/
- public void bridgeSetValue(int index, String name, boolean isFramework, int resourceId,
+ public void bridgeSetValue(int index, String name, ResourceNamespace namespace, int resourceId,
ResourceValue value) {
mResourceId[index] = resourceId;
mResourceData[index] = value;
mNames[index] = name;
- mIsFramework[index] = isFramework;
+ mNamespaces[index] = namespace;
}
/**
* Seals the array after all calls to
- * {@link #bridgeSetValue(int, String, boolean, int, ResourceValue)} have been done.
+ * {@link #bridgeSetValue(int, String, ResourceNamespace, int, ResourceValue)} have been done.
* <p/>This allows to compute the list of non default values, permitting
* {@link #getIndexCount()} to return the proper value.
*/
@@ -128,7 +130,7 @@
} else if (REFERENCE_EMPTY.equals(dataValue)) {
mResourceData[i] = null;
if (emptyIds == null) {
- emptyIds = new ArrayList<Integer>(4);
+ emptyIds = new ArrayList<>(4);
}
emptyIds.add(i);
} else {
@@ -568,21 +570,11 @@
return mContext.getDynamicIdByStyle((StyleResourceValue)resValue);
}
- // if the attribute was a reference to a resource, and not a declaration of an id (@+id),
+ // If the attribute was a reference to a resource, and not a declaration of an id (@+id),
// then the xml attribute value was "resolved" which leads us to a ResourceValue with a
- // valid getType() and getName() returning a resource name.
- // (and getValue() returning null!). We need to handle this!
- if (resValue.getResourceType() != null) {
- // if this is a framework id
- if (mPlatformFile || resValue.isFramework()) {
- // look for idName in the android R classes
- return mContext.getFrameworkResourceValue(
- resValue.getResourceType(), resValue.getName(), defValue);
- }
-
- // look for idName in the project R class.
- return mContext.getProjectResourceValue(
- resValue.getResourceType(), resValue.getName(), defValue);
+ // valid type, name, namespace and a potentially null value.
+ if (!(resValue instanceof UnresolvedResourceValue)) {
+ return mContext.getResourceId(resValue.asReference(), defValue);
}
// else, try to get the value, and resolve it somehow.
@@ -592,15 +584,11 @@
}
value = value.trim();
- // if the value is just an integer, return it.
- try {
- int i = Integer.parseInt(value);
- if (Integer.toString(i).equals(value)) {
- return i;
- }
- } catch (NumberFormatException e) {
- // pass
- }
+
+ // `resValue` failed to be resolved. We extract the interesting bits and get rid of this
+ // broken object. The namespace and resolver come from where the XML attribute was defined.
+ ResourceNamespace contextNamespace = resValue.getNamespace();
+ Resolver namespaceResolver = resValue.getNamespaceResolver();
if (value.startsWith("#")) {
// this looks like a color, do not try to parse it
@@ -621,37 +609,32 @@
// classes exclusively.
// if this is a reference to an id, find it.
- if (value.startsWith("@id/") || value.startsWith("@+") ||
- value.startsWith("@android:id/")) {
+ ResourceUrl resourceUrl = ResourceUrl.parse(value);
+ if (resourceUrl != null) {
+ if (resourceUrl.type == ResourceType.ID) {
+ ResourceReference referencedId =
+ resourceUrl.resolve(contextNamespace, namespaceResolver);
- int pos = value.indexOf('/');
- String idName = value.substring(pos + 1);
- boolean create = value.startsWith("@+");
- boolean isFrameworkId =
- mPlatformFile || value.startsWith("@android") || value.startsWith("@+android");
-
- // Look for the idName in project or android R class depending on isPlatform.
- if (create) {
- Integer idValue;
- if (isFrameworkId) {
- idValue = Bridge.getResourceId(ResourceType.ID, idName);
- } else {
- idValue = mContext.getLayoutlibCallback().getResourceId(ResourceType.ID, idName);
+ // Look for the idName in project or android R class depending on isPlatform.
+ if (resourceUrl.isCreate()) {
+ int idValue;
+ if (referencedId.getNamespace() == ResourceNamespace.ANDROID) {
+ idValue = Bridge.getResourceId(ResourceType.ID, resourceUrl.name);
+ } else {
+ idValue = mContext.getLayoutlibCallback().getOrGenerateResourceId(referencedId);
+ }
+ return idValue;
}
- return idValue == null ? defValue : idValue;
+ // This calls the same method as in if(create), but doesn't create a dynamic id, if
+ // one is not found.
+ return mContext.getResourceId(referencedId, defValue);
}
- // This calls the same method as in if(create), but doesn't create a dynamic id, if
- // one is not found.
- if (isFrameworkId) {
- return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue);
- } else {
- return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue);
+ else if (resourceUrl.type == ResourceType.AAPT) {
+ ResourceReference referencedId =
+ resourceUrl.resolve(contextNamespace, namespaceResolver);
+ return mContext.getLayoutlibCallback().getOrGenerateResourceId(referencedId);
}
}
- else if (value.startsWith("@aapt:_aapt")) {
- return mContext.getLayoutlibCallback().getResourceId(ResourceType.AAPT, value);
- }
-
// not a direct id valid reference. First check if it's an enum (this is a corner case
// for attributes that have a reference|enum type), then fallback to resolve
// as an ID without prefix.
@@ -660,33 +643,6 @@
return enumValue;
}
- // Ok, not an enum, resolve as an ID
- Integer idValue;
-
- if (resValue.isFramework()) {
- idValue = Bridge.getResourceId(resValue.getResourceType(),
- resValue.getName());
- } else {
- idValue = mContext.getLayoutlibCallback().getResourceId(
- resValue.getResourceType(), resValue.getName());
- }
-
- if (idValue != null) {
- return idValue;
- }
-
- if ("text".equals(mNames[index])) {
- // In a TextView, if the text is set from the attribute android:text, the correct
- // behaviour is not to find a resourceId for the text, and to return the default value.
- // So in this case, do not log a warning.
- return defValue;
- }
-
- Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE,
- String.format(
- "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]),
- resValue);
-
return defValue;
}
@@ -707,6 +663,7 @@
* @return Drawable for the attribute, or null if not defined.
*/
@Override
+ @Nullable
public Drawable getDrawable(int index) {
if (!hasValue(index)) {
return null;
@@ -721,6 +678,7 @@
* @hide
*/
@Override
+ @Nullable
public Drawable getDrawableForDensity(int index, int density) {
return getDrawable(index);
}
@@ -760,7 +718,8 @@
if (resVal instanceof ArrayResourceValue) {
ArrayResourceValue array = (ArrayResourceValue) resVal;
int count = array.getElementCount();
- return count >= 0 ? Resources_Delegate.fillValues(mBridgeResources, array, new CharSequence[count]) :
+ return count >= 0 ?
+ Resources_Delegate.resolveValues(mBridgeResources, array) :
null;
}
int id = getResourceId(index, 0);
@@ -808,6 +767,16 @@
case TYPE_REFERENCE:
outValue.resourceId = mResourceId[index];
return true;
+ case TYPE_INT_COLOR_ARGB4:
+ case TYPE_INT_COLOR_ARGB8:
+ case TYPE_INT_COLOR_RGB4:
+ case TYPE_INT_COLOR_RGB8:
+ ColorStateList colorStateList = getColorStateList(index);
+ if (colorStateList == null) {
+ return false;
+ }
+ outValue.data = colorStateList.getDefaultColor();
+ return true;
default:
// For back-compatibility, parse as float.
String s = getString(index);
@@ -944,35 +913,49 @@
private Integer resolveEnumAttribute(int index) {
// Get the map of attribute-constant -> IntegerValue
Map<String, Integer> map = null;
- if (mIsFramework[index]) {
+ if (mNamespaces[index] == ResourceNamespace.ANDROID) {
map = Bridge.getEnumValues(mNames[index]);
} else {
// get the styleable matching the resolved name
RenderResources res = mContext.getRenderResources();
- ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]);
+ ResourceValue attr = res.getResolvedResource(
+ ResourceReference.attr(mNamespaces[index], mNames[index]));
if (attr instanceof AttrResourceValue) {
map = ((AttrResourceValue) attr).getAttributeValues();
}
}
- if (map != null) {
- // accumulator to store the value of the 1+ constants.
+ if (map != null && !map.isEmpty()) {
+ // Accumulator to store the value of the 1+ constants.
int result = 0;
boolean found = false;
- // split the value in case this is a mix of several flags.
- String[] keywords = mResourceData[index].getValue().split("\\|");
- for (String keyword : keywords) {
- Integer i = map.get(keyword.trim());
- if (i != null) {
- result |= i;
- found = true;
+ String value = mResourceData[index].getValue();
+ if (!value.isEmpty()) {
+ // Check if the value string is already representing an integer and return it if so.
+ // Resources coming from res.apk in an AAR may have flags and enums in integer form.
+ char c = value.charAt(0);
+ if (Character.isDigit(c) || c == '-' || c == '+') {
+ try {
+ return convertValueToInt(value, 0);
+ } catch (NumberFormatException e) {
+ // Ignore and continue.
+ }
}
- // TODO: We should act smartly and log a warning for incorrect keywords. However,
- // this method is currently called even if the resourceValue is not an enum.
- }
- if (found) {
- return result;
+ // Split the value in case it is a mix of several flags.
+ String[] keywords = value.split("\\|");
+ for (String keyword : keywords) {
+ Integer i = map.get(keyword.trim());
+ if (i != null) {
+ result |= i;
+ found = true;
+ }
+ // TODO: We should act smartly and log a warning for incorrect keywords. However,
+ // this method is currently called even if the resourceValue is not an enum.
+ }
+ if (found) {
+ return result;
+ }
}
}
@@ -1026,6 +1009,6 @@
}
static TypedArray obtain(Resources res, int len) {
- return new BridgeTypedArray(res, null, len, true);
+ return new BridgeTypedArray(res, null, len);
}
}
diff --git a/bridge/src/android/content/res/Resources_Delegate.java b/bridge/src/android/content/res/Resources_Delegate.java
index 77ae90f..f127992 100644
--- a/bridge/src/android/content/res/Resources_Delegate.java
+++ b/bridge/src/android/content/res/Resources_Delegate.java
@@ -18,16 +18,22 @@
import com.android.SdkConstants;
import com.android.ide.common.rendering.api.ArrayResourceValue;
+import com.android.ide.common.rendering.api.AssetRepository;
import com.android.ide.common.rendering.api.DensityBasedResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.PluralsResourceValue;
import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceNamespace.Resolver;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.ResourceValueImpl;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.layoutlib.bridge.util.NinePatchInputStream;
@@ -56,25 +62,21 @@
import android.view.DisplayAdjustments;
import android.view.ViewGroup.LayoutParams;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
import java.io.InputStream;
-import java.util.Iterator;
import java.util.Objects;
import java.util.WeakHashMap;
+import static android.content.res.AssetManager.ACCESS_STREAMING;
import static com.android.SdkConstants.ANDROID_PKG;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
@SuppressWarnings("deprecation")
public class Resources_Delegate {
- private static WeakHashMap<Resources, LayoutlibCallback> sLayoutlibCallbacks = new
- WeakHashMap<>();
- private static WeakHashMap<Resources, BridgeContext> sContexts = new
- WeakHashMap<>();
+ private static WeakHashMap<Resources, LayoutlibCallback> sLayoutlibCallbacks =
+ new WeakHashMap<>();
+ private static WeakHashMap<Resources, BridgeContext> sContexts = new WeakHashMap<>();
- private static boolean[] mPlatformResourceFlag = new boolean[1];
// TODO: This cache is cleared every time a render session is disposed. Look into making this
// more long lived.
private static LruCache<String, Drawable.ConstantState> sDrawableCache = new LruCache<>(50);
@@ -122,15 +124,13 @@
Resources.mSystem = null;
}
- public static BridgeTypedArray newTypeArray(Resources resources, int numEntries,
- boolean platformFile) {
- return new BridgeTypedArray(resources, getContext(resources), numEntries, platformFile);
+ public static BridgeTypedArray newTypeArray(Resources resources, int numEntries) {
+ return new BridgeTypedArray(resources, getContext(resources), numEntries);
}
- private static Pair<ResourceType, String> getResourceInfo(Resources resources, int id,
- boolean[] platformResFlag_out) {
+ private static ResourceReference getResourceInfo(Resources resources, int id) {
// first get the String related to this id in the framework
- Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+ ResourceReference resourceInfo = Bridge.resolveResourceId(id);
assert Resources.mSystem != null : "Resources_Delegate.initSystem wasn't called";
// Set the layoutlib callback and context for resources
@@ -140,37 +140,25 @@
sContexts.put(resources, getContext(Resources.mSystem));
}
- if (resourceInfo != null) {
- platformResFlag_out[0] = true;
- return resourceInfo;
+ if (resourceInfo == null) {
+ // Didn't find a match in the framework? Look in the project.
+ resourceInfo = getLayoutlibCallback(resources).resolveResourceId(id);
}
- // didn't find a match in the framework? look in the project.
- resourceInfo = getLayoutlibCallback(resources).resolveResourceId(id);
-
- if (resourceInfo != null) {
- platformResFlag_out[0] = false;
- return resourceInfo;
- }
- return null;
+ return resourceInfo;
}
- private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id,
- boolean[] platformResFlag_out) {
- Pair<ResourceType, String> resourceInfo =
- getResourceInfo(resources, id, platformResFlag_out);
+ private static Pair<String, ResourceValue> getResourceValue(Resources resources, int id) {
+ ResourceReference resourceInfo = getResourceInfo(resources, id);
if (resourceInfo != null) {
- String attributeName = resourceInfo.getSecond();
+ String attributeName = resourceInfo.getName();
RenderResources renderResources = getContext(resources).getRenderResources();
- ResourceValue value = platformResFlag_out[0] ?
- renderResources.getFrameworkResource(resourceInfo.getFirst(), attributeName) :
- renderResources.getProjectResource(resourceInfo.getFirst(), attributeName);
-
+ ResourceValue value = renderResources.getResolvedResource(resourceInfo);
if (value == null) {
- // Unable to resolve the attribute, just leave the unresolved value
- value = new ResourceValue(resourceInfo.getFirst(), attributeName, attributeName,
- platformResFlag_out[0]);
+ // Unable to resolve the attribute, just leave the unresolved value.
+ value = new ResourceValueImpl(resourceInfo.getNamespace(),
+ resourceInfo.getResourceType(), attributeName, attributeName);
}
return Pair.of(attributeName, value);
}
@@ -185,7 +173,7 @@
@LayoutlibDelegate
static Drawable getDrawable(Resources resources, int id, Theme theme) {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
String key = value.getSecond().getValue();
@@ -219,7 +207,7 @@
@LayoutlibDelegate
static int getColor(Resources resources, int id, Theme theme) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resourceValue = value.getSecond();
@@ -228,8 +216,9 @@
} catch (NumberFormatException e) {
// Check if the value passed is a file. If it is, mostly likely, user is referencing
// a color state list from a place where they should reference only a pure color.
+ AssetRepository repository = getAssetRepository(resources);
String message;
- if (new File(resourceValue.getValue()).isFile()) {
+ if (repository.isFileResource(resourceValue.getValue())) {
String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/"
+ resourceValue.getName();
message = "Hexadecimal color expected, found Color State List for " + resource;
@@ -255,8 +244,7 @@
@LayoutlibDelegate
static ColorStateList getColorStateList(Resources resources, int id, Theme theme)
throws NotFoundException {
- Pair<String, ResourceValue> resValue =
- getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> resValue = getResourceValue(resources, id);
if (resValue != null) {
ColorStateList stateList = ResourceHelper.getColorStateList(resValue.getSecond(),
@@ -275,7 +263,7 @@
@LayoutlibDelegate
static CharSequence getText(Resources resources, int id, CharSequence def) {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -294,7 +282,7 @@
@LayoutlibDelegate
static CharSequence getText(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -321,12 +309,13 @@
if (resValue == null) {
// Error already logged by getArrayResourceValue.
return new CharSequence[0];
- } else if (!(resValue instanceof ArrayResourceValue)) {
- return new CharSequence[]{
- resolveReference(resources, resValue.getValue(), resValue.isFramework())};
}
- ArrayResourceValue arv = ((ArrayResourceValue) resValue);
- return fillValues(resources, arv, new CharSequence[arv.getElementCount()]);
+ if (resValue instanceof ArrayResourceValue) {
+ ArrayResourceValue arrayValue = (ArrayResourceValue) resValue;
+ return resolveValues(resources, arrayValue);
+ }
+ RenderResources renderResources = getContext(resources).getRenderResources();
+ return new CharSequence[] { renderResources.resolveResValue(resValue).getValue() };
}
@LayoutlibDelegate
@@ -335,28 +324,28 @@
if (resValue == null) {
// Error already logged by getArrayResourceValue.
return new String[0];
- } else if (!(resValue instanceof ArrayResourceValue)) {
- return new String[]{
- resolveReference(resources, resValue.getValue(), resValue.isFramework())};
}
- ArrayResourceValue arv = ((ArrayResourceValue) resValue);
- return fillValues(resources, arv, new String[arv.getElementCount()]);
+ if (resValue instanceof ArrayResourceValue) {
+ ArrayResourceValue arv = (ArrayResourceValue) resValue;
+ return resolveValues(resources, arv);
+ }
+ return new String[] { resolveReference(resources, resValue) };
}
/**
- * Resolve each element in resValue and copy them to {@code values}. The values copied are
- * always Strings. The ideal signature for the method should be <T super String>, but java
- * generics don't support it.
+ * Resolves each element in resValue and returns an array of resolved values. The returned array
+ * may contain nulls.
*/
- static <T extends CharSequence> T[] fillValues(Resources resources, ArrayResourceValue resValue,
- T[] values) {
- int i = 0;
- for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
- @SuppressWarnings("unchecked")
- T s = (T) resolveReference(resources, iterator.next(), resValue.isFramework());
- values[i] = s;
+ @NonNull
+ static String[] resolveValues(@NonNull Resources resources,
+ @NonNull ArrayResourceValue resValue) {
+ String[] result = new String[resValue.getElementCount()];
+ for (int i = 0; i < resValue.getElementCount(); i++) {
+ String value = resValue.getElement(i);
+ result[i] = resolveReference(resources, value,
+ resValue.getNamespace(), resValue.getNamespaceResolver());
}
- return values;
+ return result;
}
@LayoutlibDelegate
@@ -365,39 +354,57 @@
if (rv == null) {
// Error already logged by getArrayResourceValue.
return new int[0];
- } else if (!(rv instanceof ArrayResourceValue)) {
- // This is an older IDE that can only give us the first element of the array.
- String firstValue = resolveReference(resources, rv.getValue(), rv.isFramework());
+ }
+ if (rv instanceof ArrayResourceValue) {
+ ArrayResourceValue resValue = (ArrayResourceValue) rv;
+ int n = resValue.getElementCount();
+ int[] values = new int[n];
+ for (int i = 0; i < n; i++) {
+ String element = resolveReference(resources, resValue.getElement(i),
+ resValue.getNamespace(), resValue.getNamespaceResolver());
+ if (element != null) {
+ try {
+ if (element.startsWith("#")) {
+ // This integer represents a color (starts with #).
+ values[i] = Color.parseColor(element);
+ } else {
+ values[i] = getInt(element);
+ }
+ } catch (NumberFormatException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: \"" + element +
+ "\"", null);
+ } catch (IllegalArgumentException e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains wrong color format: \"" + element +
+ "\"", null);
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: \"" +
+ resValue.getElement(i) + "\"", null);
+ }
+ }
+ return values;
+ }
+
+ // This is an older IDE that can only give us the first element of the array.
+ String firstValue = resolveReference(resources, rv);
+ if (firstValue != null) {
try {
return new int[]{getInt(firstValue)};
} catch (NumberFormatException e) {
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Integer resource array contains non-integer value: " +
- firstValue, null);
+ "Integer resource array contains non-integer value: \"" + firstValue + "\"",
+ null);
return new int[1];
}
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
+ "Integer resource array contains non-integer value: \"" +
+ rv.getValue() + "\"", null);
+ return new int[1];
}
- ArrayResourceValue resValue = ((ArrayResourceValue) rv);
- int[] values = new int[resValue.getElementCount()];
- int i = 0;
- for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) {
- String element = resolveReference(resources, iterator.next(), resValue.isFramework());
- try {
- if (element.startsWith("#")) {
- // This integer represents a color (starts with #)
- values[i] = Color.parseColor(element);
- } else {
- values[i] = getInt(element);
- }
- } catch (NumberFormatException e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Integer resource array contains non-integer value: " + element, null);
- } catch (IllegalArgumentException e2) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT,
- "Integer resource array contains wrong color format: " + element, null);
- }
- }
- return values;
}
/**
@@ -415,7 +422,7 @@
@Nullable
private static ResourceValue getArrayResourceValue(Resources resources, int id)
throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> v = getResourceValue(resources, id);
if (v != null) {
ResourceValue resValue = v.getSecond();
@@ -447,43 +454,47 @@
return null;
}
- @NonNull
- private static String resolveReference(Resources resources, @NonNull String ref,
- boolean forceFrameworkOnly) {
- if (ref.startsWith(PREFIX_RESOURCE_REF) || ref.startsWith
- (SdkConstants.PREFIX_THEME_REF)) {
- ResourceValue rv =
- getContext(resources).getRenderResources().findResValue(ref, forceFrameworkOnly);
- rv = getContext(resources).getRenderResources().resolveResValue(rv);
- if (rv != null) {
- return rv.getValue();
- }
+ @Nullable
+ private static String resolveReference(@NonNull Resources resources, @Nullable String value,
+ @NonNull ResourceNamespace contextNamespace,
+ @NonNull ResourceNamespace.Resolver resolver) {
+ if (value != null) {
+ ResourceValue resValue = new UnresolvedResourceValue(value, contextNamespace, resolver);
+ return resolveReference(resources, resValue);
}
- // Not a reference.
- return ref;
+ return null;
+ }
+
+ @Nullable
+ private static String resolveReference(@NonNull Resources resources,
+ @NonNull ResourceValue value) {
+ RenderResources renderResources = getContext(resources).getRenderResources();
+ ResourceValue resolvedValue = renderResources.resolveResValue(value);
+ return resolvedValue == null ? null : resolvedValue.getValue();
}
@LayoutlibDelegate
static XmlResourceParser getLayout(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> v = getResourceValue(resources, id);
if (v != null) {
ResourceValue value = v.getSecond();
try {
- return ResourceHelper.getXmlBlockParser(getContext(resources), value);
+ BridgeXmlBlockParser parser =
+ ResourceHelper.getXmlBlockParser(getContext(resources), value);
+ if (parser != null) {
+ return parser;
+ }
} catch (XmlPullParserException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value.getValue(), e, null /*data*/);
+ "Failed to parse " + value.getValue(), e, null /*data*/);
// we'll return null below.
- } catch (FileNotFoundException e) {
- // this shouldn't happen since we check above.
}
-
}
// id was not found or not resolved. Throw a NotFoundException.
- throwException(resources, id);
+ throwException(resources, id, "layout");
// this is not used since the method above always throws
return null;
@@ -491,7 +502,7 @@
@LayoutlibDelegate
static XmlResourceParser getAnimation(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> v = getResourceValue(resources, id);
if (v != null) {
ResourceValue value = v.getSecond();
@@ -500,12 +511,9 @@
return ResourceHelper.getXmlBlockParser(getContext(resources), value);
} catch (XmlPullParserException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value.getValue(), e, null /*data*/);
+ "Failed to parse " + value.getValue(), e, null /*data*/);
// we'll return null below.
- } catch (FileNotFoundException e) {
- // this shouldn't happen since we check above.
}
-
}
// id was not found or not resolved. Throw a NotFoundException.
@@ -528,12 +536,41 @@
@LayoutlibDelegate
static TypedArray obtainTypedArray(Resources resources, int id) throws NotFoundException {
- throw new UnsupportedOperationException();
+ BridgeContext context = getContext(resources);
+ ResourceReference reference = context.resolveId(id);
+ RenderResources renderResources = context.getRenderResources();
+ ResourceValue value = renderResources.getResolvedResource(reference);
+
+ if (!(value instanceof ArrayResourceValue)) {
+ throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id));
+ }
+
+ ArrayResourceValue arrayValue = (ArrayResourceValue) value;
+ int length = arrayValue.getElementCount();
+ ResourceNamespace namespace = arrayValue.getNamespace();
+ BridgeTypedArray typedArray = newTypeArray(resources, length);
+
+ for (int i = 0; i < length; i++) {
+ ResourceValue elementValue;
+ ResourceUrl resourceUrl = ResourceUrl.parse(arrayValue.getElement(i));
+ if (resourceUrl != null) {
+ ResourceReference elementRef =
+ resourceUrl.resolve(namespace, arrayValue.getNamespaceResolver());
+ elementValue = renderResources.getResolvedResource(elementRef);
+ } else {
+ elementValue = new ResourceValueImpl(namespace, ResourceType.STRING, "element" + i,
+ arrayValue.getElement(i));
+ }
+ typedArray.bridgeSetValue(i, elementValue.getName(), namespace, i, elementValue);
+ }
+
+ typedArray.sealArray();
+ return typedArray;
}
@LayoutlibDelegate
static float getDimension(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -567,7 +604,7 @@
@LayoutlibDelegate
static int getDimensionPixelOffset(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -596,7 +633,7 @@
@LayoutlibDelegate
static int getDimensionPixelSize(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -625,7 +662,7 @@
@LayoutlibDelegate
static int getInteger(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -652,7 +689,7 @@
@LayoutlibDelegate
static float getFloat(Resources resources, int id) {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -672,7 +709,7 @@
@LayoutlibDelegate
static boolean getBoolean(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resValue = value.getSecond();
@@ -694,29 +731,21 @@
@LayoutlibDelegate
static String getResourceEntryName(Resources resources, int resid) throws NotFoundException {
- Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
+ ResourceReference resourceInfo = getResourceInfo(resources, resid);
if (resourceInfo != null) {
- return resourceInfo.getSecond();
+ return resourceInfo.getName();
}
throwException(resid, null);
return null;
-
}
@LayoutlibDelegate
static String getResourceName(Resources resources, int resid) throws NotFoundException {
- boolean[] platformOut = new boolean[1];
- Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
- String packageName;
+ ResourceReference resourceInfo = getResourceInfo(resources, resid);
if (resourceInfo != null) {
- if (platformOut[0]) {
- packageName = SdkConstants.ANDROID_NS_NAME;
- } else {
- packageName = getContext(resources).getPackageName();
- packageName = packageName == null ? SdkConstants.APP_PREFIX : packageName;
- }
- return packageName + ':' + resourceInfo.getFirst().getName() + '/' +
- resourceInfo.getSecond();
+ String packageName = getPackageName(resourceInfo, resources);
+ return packageName + ':' + resourceInfo.getResourceType().getName() + '/' +
+ resourceInfo.getName();
}
throwException(resid, null);
return null;
@@ -724,14 +753,9 @@
@LayoutlibDelegate
static String getResourcePackageName(Resources resources, int resid) throws NotFoundException {
- boolean[] platformOut = new boolean[1];
- Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, platformOut);
+ ResourceReference resourceInfo = getResourceInfo(resources, resid);
if (resourceInfo != null) {
- if (platformOut[0]) {
- return SdkConstants.ANDROID_NS_NAME;
- }
- String packageName = getContext(resources).getPackageName();
- return packageName == null ? SdkConstants.APP_PREFIX : packageName;
+ return getPackageName(resourceInfo, resources);
}
throwException(resid, null);
return null;
@@ -739,14 +763,25 @@
@LayoutlibDelegate
static String getResourceTypeName(Resources resources, int resid) throws NotFoundException {
- Pair<ResourceType, String> resourceInfo = getResourceInfo(resources, resid, new boolean[1]);
+ ResourceReference resourceInfo = getResourceInfo(resources, resid);
if (resourceInfo != null) {
- return resourceInfo.getFirst().getName();
+ return resourceInfo.getResourceType().getName();
}
throwException(resid, null);
return null;
}
+ private static String getPackageName(ResourceReference resourceInfo, Resources resources) {
+ String packageName = resourceInfo.getNamespace().getPackageName();
+ if (packageName == null) {
+ packageName = getContext(resources).getPackageName();
+ if (packageName == null) {
+ packageName = SdkConstants.APP_PREFIX;
+ }
+ }
+ return packageName;
+ }
+
@LayoutlibDelegate
static String getString(Resources resources, int id, Object... formatArgs)
throws NotFoundException {
@@ -765,7 +800,7 @@
@LayoutlibDelegate
static String getString(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null && value.getSecond().getValue() != null) {
return value.getSecond().getValue();
@@ -781,7 +816,7 @@
@LayoutlibDelegate
static String getQuantityString(Resources resources, int id, int quantity) throws
NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
if (value.getSecond() instanceof PluralsResourceValue) {
@@ -823,7 +858,7 @@
@LayoutlibDelegate
static Typeface getFont(Resources resources, int id) throws
NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
return ResourceHelper.getFont(value.getSecond(), getContext(resources), null);
}
@@ -837,22 +872,24 @@
@LayoutlibDelegate
static Typeface getFont(Resources resources, TypedValue outValue, int id) throws
NotFoundException {
- Resources_Delegate.getValue(resources, id, outValue, true);
- if (outValue.string != null) {
- return ResourceHelper.getFont(outValue.string.toString(), getContext(resources), null,
- mPlatformResourceFlag[0]);
+ ResourceValue resVal = getResourceValue(resources, id, outValue);
+ if (resVal != null) {
+ return ResourceHelper.getFont(resVal, getContext(resources), null);
}
throwException(resources, id);
-
- // this is not used since the method above always throws
- return null;
+ return null; // This is not used since the method above always throws.
}
@LayoutlibDelegate
static void getValue(Resources resources, int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ getResourceValue(resources, id, outValue);
+ }
+
+ private static ResourceValue getResourceValue(Resources resources, int id, TypedValue outValue)
+ throws NotFoundException {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
ResourceValue resVal = value.getSecond();
@@ -861,7 +898,7 @@
if (v != null) {
if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue,
false /*requireUnit*/)) {
- return;
+ return resVal;
}
if (resVal instanceof DensityBasedResourceValue) {
outValue.density =
@@ -871,12 +908,13 @@
// else it's a string
outValue.type = TypedValue.TYPE_STRING;
outValue.string = v;
- return;
+ return resVal;
}
}
// id was not found or not resolved. Throw a NotFoundException.
throwException(resources, id);
+ return null; // This is not used since the method above always throws.
}
@LayoutlibDelegate
@@ -892,8 +930,14 @@
}
@LayoutlibDelegate
+ static int getAttributeSetSourceResId(@Nullable AttributeSet set) {
+ // Not supported in layoutlib
+ return Resources.ID_NULL;
+ }
+
+ @LayoutlibDelegate
static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> v = getResourceValue(resources, id);
if (v != null) {
ResourceValue value = v.getSecond();
@@ -902,10 +946,8 @@
return ResourceHelper.getXmlBlockParser(getContext(resources), value);
} catch (XmlPullParserException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value.getValue(), e, null /*data*/);
+ "Failed to parse " + value.getValue(), e, null /*data*/);
// we'll return null below.
- } catch (FileNotFoundException e) {
- // this shouldn't happen since we check above.
}
}
@@ -928,49 +970,35 @@
// even though we know the XML file to load directly, we still need to resolve the
// id so that we can know if it's a platform or project resource.
// (mPlatformResouceFlag will get the result and will be used later).
- getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> result = getResourceValue(resources, id);
- File f = new File(file);
+ ResourceNamespace layoutNamespace;
+ if (result != null && result.getSecond() != null) {
+ layoutNamespace = result.getSecond().getNamespace();
+ } else {
+ // We need to pick something, even though the resource system never heard about a layout
+ // with this numeric id.
+ layoutNamespace = ResourceNamespace.RES_AUTO;
+ }
+
try {
- XmlPullParser parser = ParserFactory.create(f);
-
- return new BridgeXmlBlockParser(parser, getContext(resources), mPlatformResourceFlag[0]);
+ XmlPullParser parser = ParserFactory.create(file);
+ return new BridgeXmlBlockParser(parser, getContext(resources), layoutNamespace);
} catch (XmlPullParserException e) {
NotFoundException newE = new NotFoundException();
newE.initCause(e);
throw newE;
- } catch (FileNotFoundException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
}
}
@LayoutlibDelegate
static InputStream openRawResource(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> value = getResourceValue(resources, id);
if (value != null) {
String path = value.getSecond().getValue();
-
if (path != null) {
- // check this is a file
- File f = new File(path);
- if (f.isFile()) {
- try {
- // if it's a nine-patch return a custom input stream so that
- // other methods (mainly bitmap factory) can detect it's a 9-patch
- // and actually load it as a 9-patch instead of a normal bitmap
- if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
- return new NinePatchInputStream(f);
- }
- return new FileInputStream(f);
- } catch (FileNotFoundException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- }
- }
+ return openRawResource(resources, path);
}
}
@@ -982,43 +1010,46 @@
}
@LayoutlibDelegate
- static InputStream openRawResource(Resources resources, int id, TypedValue value) throws
- NotFoundException {
+ static InputStream openRawResource(Resources resources, int id, TypedValue value)
+ throws NotFoundException {
getValue(resources, id, value, true);
String path = value.string.toString();
+ return openRawResource(resources, path);
+ }
- File f = new File(path);
- if (f.isFile()) {
- try {
- // if it's a nine-patch return a custom input stream so that
- // other methods (mainly bitmap factory) can detect it's a 9-patch
- // and actually load it as a 9-patch instead of a normal bitmap
- if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
- return new NinePatchInputStream(f);
- }
- return new FileInputStream(f);
- } catch (FileNotFoundException e) {
- NotFoundException exception = new NotFoundException();
- exception.initCause(e);
- throw exception;
+ private static InputStream openRawResource(Resources resources, String path)
+ throws NotFoundException {
+ AssetRepository repository = getAssetRepository(resources);
+ try {
+ InputStream stream = repository.openNonAsset(0, path, ACCESS_STREAMING);
+ if (stream == null) {
+ throw new NotFoundException(path);
}
+ // If it's a nine-patch return a custom input stream so that
+ // other methods (mainly bitmap factory) can detect it's a 9-patch
+ // and actually load it as a 9-patch instead of a normal bitmap.
+ if (path.toLowerCase().endsWith(NinePatch.EXTENSION_9PATCH)) {
+ return new NinePatchInputStream(stream);
+ }
+ return stream;
+ } catch (IOException e) {
+ NotFoundException exception = new NotFoundException();
+ exception.initCause(e);
+ throw exception;
}
-
- throw new NotFoundException();
}
@LayoutlibDelegate
- static AssetFileDescriptor openRawResourceFd(Resources resources, int id) throws
- NotFoundException {
+ static AssetFileDescriptor openRawResourceFd(Resources resources, int id)
+ throws NotFoundException {
throw new UnsupportedOperationException();
}
@VisibleForTesting
@Nullable
- static ResourceUrl resourceUrlFromName(@NonNull String name, @Nullable String defType,
- @Nullable
- String defPackage) {
+ static ResourceUrl resourceUrlFromName(
+ @NonNull String name, @Nullable String defType, @Nullable String defPackage) {
int colonIdx = name.indexOf(':');
int slashIdx = name.indexOf('/');
@@ -1068,13 +1099,18 @@
}
ResourceUrl url = resourceUrlFromName(name, defType, defPackage);
- Integer id = null;
if (url != null) {
- id = ANDROID_PKG.equals(url.namespace) ? Bridge.getResourceId(url.type, url.name) :
- getLayoutlibCallback(resources).getResourceId(url.type, url.name);
+ if (ANDROID_PKG.equals(url.namespace)) {
+ return Bridge.getResourceId(url.type, url.name);
+ }
+
+ if (getContext(resources).getPackageName().equals(url.namespace)) {
+ return getLayoutlibCallback(resources).getOrGenerateResourceId(
+ new ResourceReference(ResourceNamespace.RES_AUTO, url.type, url.name));
+ }
}
- return id != null ? id : 0;
+ return 0;
}
/**
@@ -1082,23 +1118,36 @@
* type.
*
* @param id the id of the resource
+ * @param expectedType the type of resource that was expected
*
* @throws NotFoundException
*/
- private static void throwException(Resources resources, int id) throws NotFoundException {
- throwException(id, getResourceInfo(resources, id, new boolean[1]));
+ private static void throwException(Resources resources, int id, @Nullable String expectedType)
+ throws NotFoundException {
+ throwException(id, getResourceInfo(resources, id), expectedType);
}
- private static void throwException(int id, @Nullable Pair<ResourceType, String> resourceInfo) {
+ private static void throwException(Resources resources, int id) throws NotFoundException {
+ throwException(resources, id, null);
+ }
+
+ private static void throwException(int id, @Nullable ResourceReference resourceInfo) {
+ throwException(id, resourceInfo, null);
+ }
+ private static void throwException(int id, @Nullable ResourceReference resourceInfo,
+ @Nullable String expectedType) {
String message;
if (resourceInfo != null) {
message = String.format(
"Could not find %1$s resource matching value 0x%2$X (resolved name: %3$s) in current configuration.",
- resourceInfo.getFirst(), id, resourceInfo.getSecond());
+ resourceInfo.getResourceType(), id, resourceInfo.getName());
} else {
message = String.format("Could not resolve resource value: 0x%1$X.", id);
}
+ if (expectedType != null) {
+ message += " Or the resolved value was not of type " + expectedType + " as expected.";
+ }
throw new NotFoundException(message);
}
@@ -1112,4 +1161,10 @@
}
return Integer.parseInt(v, radix);
}
+
+ private static AssetRepository getAssetRepository(Resources resources) {
+ BridgeContext context = getContext(resources);
+ BridgeAssetManager assetManager = context.getAssets();
+ return assetManager.getAssetRepository();
+ }
}
diff --git a/bridge/src/android/content/res/Resources_Theme_Delegate.java b/bridge/src/android/content/res/Resources_Theme_Delegate.java
index 8aa9216..4740fac 100644
--- a/bridge/src/android/content/res/Resources_Theme_Delegate.java
+++ b/bridge/src/android/content/res/Resources_Theme_Delegate.java
@@ -142,12 +142,6 @@
}
BridgeContext context = RenderSessionImpl.getCurrentContext();
ResourceReference theme = context.resolveId(nativeResid);
- if (theme.isFramework()) {
- return (StyleResourceValue) context.getRenderResources()
- .getFrameworkResource(ResourceType.STYLE, theme.getName());
- } else {
- return (StyleResourceValue) context.getRenderResources()
- .getProjectResource(ResourceType.STYLE, theme.getName());
- }
+ return (StyleResourceValue) context.getRenderResources().getResolvedResource(theme);
}
}
diff --git a/bridge/src/android/graphics/BaseCanvas_Delegate.java b/bridge/src/android/graphics/BaseCanvas_Delegate.java
index 9260099..a7e36f0 100644
--- a/bridge/src/android/graphics/BaseCanvas_Delegate.java
+++ b/bridge/src/android/graphics/BaseCanvas_Delegate.java
@@ -26,10 +26,13 @@
import android.annotation.Nullable;
import android.text.TextUtils;
+import android.util.imagepool.ImagePool;
+import android.util.imagepool.ImagePoolProvider;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
+import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
@@ -75,10 +78,10 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
+ /*package*/ static void nDrawBitmap(long nativeCanvas, long bitmapHandle, float left, float top,
long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) {
// get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmapHandle);
if (bitmapDelegate == null) {
return;
}
@@ -93,11 +96,12 @@
}
@LayoutlibDelegate
- /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop,
- float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
- float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity) {
+ /*package*/ static void nDrawBitmap(long nativeCanvas, long bitmapHandle, float srcLeft,
+ float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, long nativePaintOrZero, int screenDensity,
+ int bitmapDensity) {
// get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmapHandle);
if (bitmapDelegate == null) {
return;
}
@@ -112,7 +116,7 @@
final float x, final float y, int width, int height, boolean hasAlpha,
long nativePaintOrZero) {
// create a temp BufferedImage containing the content.
- final BufferedImage image = new BufferedImage(width, height,
+ final ImagePool.Image image = ImagePoolProvider.get().acquire(width, height,
hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
image.setRGB(0, 0, width, height, colors, offset, stride);
@@ -123,7 +127,7 @@
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
- graphics.drawImage(image, (int) x, (int) y, null);
+ image.drawImage(graphics, (int) x, (int) y, null);
});
}
@@ -155,6 +159,12 @@
}
@LayoutlibDelegate
+ /*package*/ static void nDrawColor(long nativeCanvas, long nativeColorSpace, long color,
+ int mode) {
+ nDrawColor(nativeCanvas, Color.toArgb(color), mode);
+ }
+
+ @LayoutlibDelegate
/*package*/ static void nDrawPaint(long nativeCanvas, long paint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -315,6 +325,49 @@
}
@LayoutlibDelegate
+ /*package*/ static void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft,
+ float outerTop, float outerRight, float outerBottom, float outerRx, float outerRy,
+ float innerLeft, float innerTop, float innerRight, float innerBottom, float innerRx,
+ float innerRy, long nativePaint) {
+ nDrawDoubleRoundRect(nativeCanvas, outerLeft, outerTop, outerRight, outerBottom,
+ new float[]{outerRx, outerRy, outerRx, outerRy, outerRx, outerRy, outerRx, outerRy},
+ innerLeft, innerTop, innerRight, innerBottom,
+ new float[]{innerRx, innerRy, innerRx, innerRy, innerRx, innerRy, innerRx, innerRy},
+ nativePaint);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nDrawDoubleRoundRect(long nativeCanvas, float outerLeft,
+ float outerTop, float outerRight, float outerBottom, float[] outerRadii,
+ float innerLeft, float innerTop, float innerRight, float innerBottom,
+ float[] innerRadii, long nativePaint) {
+ draw(nativeCanvas, nativePaint, false /*compositeOnly*/, false /*forceSrcMode*/,
+ (graphics, paintDelegate) -> {
+ RoundRectangle innerRect = new RoundRectangle(innerLeft, innerTop,
+ innerRight - innerLeft, innerBottom - innerTop, innerRadii);
+ RoundRectangle outerRect = new RoundRectangle(outerLeft, outerTop,
+ outerRight - outerLeft, outerBottom - outerTop, outerRadii);
+
+ int style = paintDelegate.getStyle();
+
+ // draw
+ if (style == Paint.Style.STROKE.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ graphics.draw(innerRect);
+ graphics.draw(outerRect);
+ }
+
+ if (style == Paint.Style.FILL.nativeInt ||
+ style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+ Area outerArea = new Area(outerRect);
+ Area innerArea = new Area(innerRect);
+ outerArea.subtract(innerArea);
+ graphics.fill(outerArea);
+ }
+ });
+ }
+
+ @LayoutlibDelegate
public static void nDrawPath(long nativeCanvas, long path, long paint) {
final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
if (pathDelegate == null) {
@@ -417,7 +470,7 @@
}
@LayoutlibDelegate
- /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
+ /*package*/ static void nDrawBitmapMatrix(long nCanvas, long bitmapHandle,
long nMatrix, long nPaint) {
// get the delegate from the native int.
BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
@@ -429,7 +482,7 @@
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
// get the delegate from the native int.
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmapHandle);
if (bitmapDelegate == null) {
return;
}
@@ -455,7 +508,7 @@
}
@LayoutlibDelegate
- /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap,
+ /*package*/ static void nDrawBitmapMesh(long nCanvas, long bitmapHandle,
int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
int colorOffset, long nPaint) {
// FIXME
@@ -508,7 +561,7 @@
/*package*/ static void nDrawTextRun(long nativeCanvas, char[] text,
int start, int count, int contextStart, int contextCount,
float x, float y, boolean isRtl, long paint,
- long nativeMeasuredText, int measuredTextOffset) {
+ long nativeMeasuredText) {
drawText(nativeCanvas, text, start, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, paint);
}
diff --git a/bridge/src/android/graphics/BidiRenderer.java b/bridge/src/android/graphics/BidiRenderer.java
index 9a102c1..204022d 100644
--- a/bridge/src/android/graphics/BidiRenderer.java
+++ b/bridge/src/android/graphics/BidiRenderer.java
@@ -154,7 +154,6 @@
}
while (start < limit) {
- boolean foundFont = false;
int canDisplayUpTo = preferredFont.canDisplayUpTo(mText, start, limit);
if (canDisplayUpTo == -1) {
// We can draw all characters in the text.
@@ -166,38 +165,59 @@
render(start, canDisplayUpTo, preferredFont, flag, advances, advancesIndex, draw);
advancesIndex += canDisplayUpTo - start;
start = canDisplayUpTo;
- }
+ } else {
+ // We can display everything with the preferred font. Search for the font that
+ // allows us to display the maximum number of chars
+ List<FontInfo> fontInfos = mPaint.getFonts();
+ Font bestFont = null;
+ int highestUpTo = canDisplayUpTo;
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < fontInfos.size(); i++) {
+ Font font = fontInfos.get(i).mFont;
- // The current character cannot be drawn with the preferred font. Cycle through all the
- // fonts to check which one can draw it.
- int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1;
- List<FontInfo> fontInfos = mPaint.getFonts();
- //noinspection ForLoopReplaceableByForEach (avoid iterator allocation)
- for (int i = 0; i < fontInfos.size(); i++) {
- Font font = fontInfos.get(i).mFont;
- if (font == null) {
- logFontWarning();
- continue;
+ if (preferredFont == font) {
+ // We know this font won't work since we've already tested it at the
+ // beginning of the loop
+ continue;
+ }
+
+ if (font == null) {
+ logFontWarning();
+ continue;
+ }
+
+ canDisplayUpTo = font.canDisplayUpTo(mText, start, limit);
+ if (canDisplayUpTo == -1) {
+ // This font can dis
+ highestUpTo = limit;
+ bestFont = font;
+ break;
+ } else if (canDisplayUpTo > highestUpTo) {
+ highestUpTo = canDisplayUpTo;
+ bestFont = font;
+ // Keep searching in case there is a font that allows to display even
+ // more text
+ }
}
- canDisplayUpTo = font.canDisplayUpTo(mText, start, start + charCount);
- if (canDisplayUpTo == -1) {
- render(start, start+charCount, font, flag, advances, advancesIndex, draw);
+
+ if (bestFont != null) {
+ render(start, highestUpTo, bestFont, flag, advances, advancesIndex, draw);
+ advancesIndex += highestUpTo - start;
+ start = highestUpTo;
+ } else {
+ int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1;
+
+ // No font can display this char. Use the preferred font and skip this char.
+ // The char will most probably appear as a box or a blank space. We could,
+ // probably, use some heuristics and break the character into the base
+ // character and diacritics and then draw it, but it's probably not worth the
+ // effort.
+ render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
+ draw);
start += charCount;
advancesIndex += charCount;
- foundFont = true;
- break;
}
}
- if (!foundFont) {
- // No font can display this char. Use the preferred font. The char will most
- // probably appear as a box or a blank space. We could, probably, use some
- // heuristics and break the character into the base character and diacritics and
- // then draw it, but it's probably not worth the effort.
- render(start, start + charCount, preferredFont, flag, advances, advancesIndex,
- draw);
- start += charCount;
- advancesIndex += charCount;
- }
}
}
@@ -313,19 +333,29 @@
// TODO: Replace this method with one which returns the font based on the scriptCode.
@NonNull
private static Font getScriptFont(char[] text, int start, int limit, List<FontInfo> fonts) {
- for (FontInfo fontInfo : fonts) {
- if (fontInfo.mFont.canDisplayUpTo(text, start, limit) == -1) {
- return fontInfo.mFont;
- }
- }
-
if (fonts.isEmpty()) {
logFontWarning();
// Fallback font in case no font can be loaded
return Font.getFont(Font.SERIF);
}
- return fonts.get(0).mFont;
+ // From all the fonts, select the one that can display the highest number of characters
+ Font bestFont = fonts.get(0).mFont;
+ int bestFontCount = 0;
+ for (FontInfo fontInfo : fonts) {
+ int count = fontInfo.mFont.canDisplayUpTo(text, start, limit);
+ if (count == -1) {
+ // This font can display everything, return this one
+ return fontInfo.mFont;
+ }
+
+ if (count > bestFontCount) {
+ bestFontCount = count;
+ bestFont = fontInfo.mFont;
+ }
+ }
+
+ return bestFont;
}
private static int getIcuFlags(int bidiFlag) {
diff --git a/bridge/src/android/graphics/BitmapFactory_Delegate.java b/bridge/src/android/graphics/BitmapFactory_Delegate.java
index ee099e1..80c6761 100644
--- a/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -49,7 +49,8 @@
@LayoutlibDelegate
/*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage,
- @Nullable Rect padding, @Nullable Options opts) {
+ @Nullable Rect padding, @Nullable Options opts, long inBitmapHandle,
+ long colorSpaceHandle) {
Bitmap bm = null;
Density density = Density.MEDIUM;
@@ -100,7 +101,7 @@
@LayoutlibDelegate
/*package*/ static Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
- Rect padding, Options opts) {
+ Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle) {
if (opts != null) {
opts.inBitmap = null;
}
@@ -108,7 +109,8 @@
}
@LayoutlibDelegate
- /*package*/ static Bitmap nativeDecodeAsset(long asset, Rect padding, Options opts) {
+ /*package*/ static Bitmap nativeDecodeAsset(long asset, Rect padding, Options opts,
+ long inBitmapHandle, long colorSpaceHandle) {
if (opts != null) {
opts.inBitmap = null;
}
@@ -117,7 +119,7 @@
@LayoutlibDelegate
/*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset,
- int length, Options opts) {
+ int length, Options opts, long inBitmapHandle, long colorSpaceHandle) {
if (opts != null) {
opts.inBitmap = null;
}
diff --git a/bridge/src/android/graphics/BitmapShader_Delegate.java b/bridge/src/android/graphics/BitmapShader_Delegate.java
index 891b07f..0e3e9a8 100644
--- a/bridge/src/android/graphics/BitmapShader_Delegate.java
+++ b/bridge/src/android/graphics/BitmapShader_Delegate.java
@@ -77,9 +77,9 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long nativeCreate(long nativeMatrix, Bitmap androidBitmap,
+ /*package*/ static long nativeCreate(long nativeMatrix, long bitmapHandle,
int shaderTileModeX, int shaderTileModeY) {
- Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(androidBitmap);
+ Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(bitmapHandle);
if (bitmap == null) {
return 0;
}
diff --git a/bridge/src/android/graphics/Bitmap_Delegate.java b/bridge/src/android/graphics/Bitmap_Delegate.java
index 6c72cb2..c3d9665 100644
--- a/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -16,6 +16,7 @@
package android.graphics;
+import com.android.ide.common.rendering.api.AssetRepository;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
@@ -29,11 +30,11 @@
import android.annotation.Nullable;
import android.graphics.Bitmap.Config;
+import android.hardware.HardwareBuffer;
import android.os.Parcel;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -45,6 +46,8 @@
import javax.imageio.ImageIO;
import libcore.util.NativeAllocationRegistry_Delegate;
+import static android.content.res.AssetManager.ACCESS_STREAMING;
+
/**
* Delegate implementing the native methods of android.graphics.Bitmap
*
@@ -60,7 +63,6 @@
*/
public final class Bitmap_Delegate {
-
public enum BitmapCreateFlags {
NONE, PREMULTIPLIED, MUTABLE
}
@@ -79,6 +81,7 @@
private boolean mHasMipMap = false; // TODO: check the default.
private boolean mIsPremultiplied = true;
private int mGenerationId = 0;
+ private boolean mIsMutable;
// ---- Public Helper methods ----
@@ -90,23 +93,18 @@
return sManager.getDelegate(native_bitmap);
}
- @Nullable
- public static Bitmap_Delegate getDelegate(@Nullable Bitmap bitmap) {
- return bitmap == null ? null : getDelegate(bitmap.getNativeInstance());
- }
-
/**
- * Creates and returns a {@link Bitmap} initialized with the given file content.
+ * Creates and returns a {@link Bitmap} initialized with the given stream content.
*
- * @param input the file from which to read the bitmap content
+ * @param input the stream from which to read the bitmap content
* @param isMutable whether the bitmap is mutable
* @param density the density associated with the bitmap
*
* @see Bitmap#isMutable()
* @see Bitmap#getDensity()
*/
- public static Bitmap createBitmap(File input, boolean isMutable, Density density)
- throws IOException {
+ public static Bitmap createBitmap(@Nullable InputStream input, boolean isMutable,
+ Density density) throws IOException {
return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
}
@@ -120,58 +118,30 @@
* @see Bitmap#isMutable()
* @see Bitmap#getDensity()
*/
- private static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
+ static Bitmap createBitmap(@Nullable InputStream input, Set<BitmapCreateFlags> createFlags,
Density density) throws IOException {
// create a delegate with the content of the file.
- BufferedImage image = ImageIO.read(input);
- if (image == null && input.exists()) {
+ BufferedImage image = input == null ? null : ImageIO.read(input);
+ if (image == null) {
// There was a problem decoding the image, or the decoder isn't registered. Webp maybe.
// Replace with a broken image icon.
BridgeContext currentContext = RenderAction.getCurrentContext();
if (currentContext != null) {
RenderResources resources = currentContext.getRenderResources();
- ResourceValue broken = resources.getFrameworkResource(ResourceType.DRAWABLE,
- "ic_menu_report_image");
- File brokenFile = new File(broken.getValue());
- if (brokenFile.exists()) {
- image = ImageIO.read(brokenFile);
+ ResourceValue broken = resources.getResolvedResource(
+ BridgeContext.createFrameworkResourceReference(
+ ResourceType.DRAWABLE, "ic_menu_report_image"));
+ AssetRepository assetRepository = currentContext.getAssets().getAssetRepository();
+ try (InputStream stream =
+ assetRepository.openNonAsset(0, broken.getValue(), ACCESS_STREAMING)) {
+ if (stream != null) {
+ image = ImageIO.read(stream);
+ }
}
}
}
Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
-
- return createBitmap(delegate, createFlags, density.getDpiValue());
- }
-
- /**
- * Creates and returns a {@link Bitmap} initialized with the given stream content.
- *
- * @param input the stream from which to read the bitmap content
- * @param isMutable whether the bitmap is mutable
- * @param density the density associated with the bitmap
- *
- * @see Bitmap#isMutable()
- * @see Bitmap#getDensity()
- */
- public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
- throws IOException {
- return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
- }
-
- /**
- * Creates and returns a {@link Bitmap} initialized with the given stream content.
- *
- * @param input the stream from which to read the bitmap content
- * @param density the density associated with the bitmap
- *
- * @see Bitmap#isPremultiplied()
- * @see Bitmap#isMutable()
- * @see Bitmap#getDensity()
- */
- public static Bitmap createBitmap(InputStream input, Set<BitmapCreateFlags> createFlags,
- Density density) throws IOException {
- // create a delegate with the content of the stream.
- Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
+ delegate.mIsMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
return createBitmap(delegate, createFlags, density.getDpiValue());
}
@@ -204,6 +174,7 @@
Density density) {
// create a delegate with the given image.
Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
+ delegate.mIsMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
return createBitmap(delegate, createFlags, density.getDpiValue());
}
@@ -248,8 +219,7 @@
@LayoutlibDelegate
/*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
- int height, int nativeConfig, boolean isMutable, @Nullable float[] xyzD50,
- @Nullable ColorSpace.Rgb.TransferParameters p) {
+ int height, int nativeConfig, boolean isMutable, long nativeColorSpace) {
int imageType = getBufferedImageType();
// create the image
@@ -261,6 +231,7 @@
// create a delegate with the content of the stream.
Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
+ delegate.mIsMutable = isMutable;
return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
Bitmap.getDefaultDensity());
@@ -290,6 +261,7 @@
// create a delegate with the content of the stream.
Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
+ delegate.mIsMutable = isMutable;
return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
Bitmap.getDefaultDensity());
@@ -320,9 +292,8 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nativeRecycle(long nativeBitmap) {
- // In our case reycle() is a no-op. We will let the finalizer to dispose the bitmap.
- return true;
+ /*package*/ static void nativeRecycle(long nativeBitmap) {
+ // In our case recycle() is a no-op. We will let the finalizer to dispose the bitmap.
}
@LayoutlibDelegate
@@ -361,6 +332,11 @@
}
@LayoutlibDelegate
+ /*package*/ static void nativeErase(long nativeBitmap, long colorSpacePtr, long color) {
+ nativeErase(nativeBitmap, Color.toArgb(color));
+ }
+
+ @LayoutlibDelegate
/*package*/ static int nativeRowBytes(long nativeBitmap) {
// get the delegate from the native int.
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
@@ -410,6 +386,11 @@
}
@LayoutlibDelegate
+ /*package*/ static long nativeGetColor(long nativeBitmap, int x, int y) {
+ return nativeGetPixel(nativeBitmap, x, y);
+ }
+
+ @LayoutlibDelegate
/*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset,
int stride, int x, int y, int width, int height) {
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
@@ -509,6 +490,7 @@
// create the delegate. The actual Bitmap config is only an alpha channel
Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
+ delegate.mIsMutable = true;
// the density doesn't matter, it's set by the Java method.
return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE),
@@ -633,15 +615,17 @@
// create a delegate with the content of the stream.
Bitmap_Delegate delegate = new Bitmap_Delegate(image, srcBmpDelegate.getConfig());
+ delegate.mIsMutable = srcBmpDelegate.mIsMutable;
return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.NONE),
Bitmap.getDefaultDensity());
}
@LayoutlibDelegate
- /*package*/ static Bitmap nativeCreateHardwareBitmap(GraphicBuffer buffer) {
+ /*package*/ static Bitmap nativeWrapHardwareBufferBitmap(HardwareBuffer buffer,
+ long nativeColorSpace) {
Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Bitmap.nativeCreateHardwareBitmap() is not supported", null /*data*/);
+ "Bitmap.nativeWrapHardwareBufferBitmap() is not supported", null, null, null);
return null;
}
@@ -660,16 +644,41 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params) {
+ /*package*/ static ColorSpace nativeComputeColorSpace(long nativePtr) {
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "Color spaces are not supported", null /*data*/);
+ return null;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeSetColorSpace(long nativePtr, long nativeColorSpace) {
+ Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
+ "Color spaces are not supported", null /*data*/);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeIsSRGBLinear(long nativePtr) {
Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
"Color spaces are not supported", null /*data*/);
return false;
}
@LayoutlibDelegate
- /*package*/ static void nativeCopyColorSpace(long srcBitmap, long dstBitmap) {
- Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
- "Color spaces are not supported", null /*data*/);
+ /*package*/ static void nativeSetImmutable(long nativePtr) {
+ Bitmap_Delegate bmpDelegate = sManager.getDelegate(nativePtr);
+ if (bmpDelegate == null) {
+ return;
+ }
+ bmpDelegate.mIsMutable = false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean nativeIsImmutable(long nativePtr) {
+ Bitmap_Delegate bmpDelegate = sManager.getDelegate(nativePtr);
+ if (bmpDelegate == null) {
+ return false;
+ }
+ return !bmpDelegate.mIsMutable;
}
// ---- Private delegate/helper methods ----
@@ -686,12 +695,11 @@
int width = delegate.mImage.getWidth();
int height = delegate.mImage.getHeight();
- boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
// and create/return a new Bitmap with it
- return new Bitmap(nativeInt, width, height, density, isMutable,
- isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
+ return new Bitmap(nativeInt, width, height, density, isPremultiplied,
+ null /*ninePatchChunk*/, null /* layoutBounds */, true /* fromMalloc */);
}
private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) {
diff --git a/bridge/src/android/graphics/BlendModeColorFilter_Delegate.java b/bridge/src/android/graphics/BlendModeColorFilter_Delegate.java
new file mode 100644
index 0000000..fc25903
--- /dev/null
+++ b/bridge/src/android/graphics/BlendModeColorFilter_Delegate.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.BlendModeColorFilter
+ *
+ * Through the layoutlib_create tool, the original native methods of BlendModeColorFilter have
+ * been replaced by calls to methods of the same name in this delegate class.
+ *
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between
+ * it and the original PorterDuffColorFilter class.
+ *
+ * Because this extends {@link ColorFilter_Delegate}, there's no need to use a
+ * {@link DelegateManager}, as all the Shader classes will be added to the manager
+ * owned by {@link ColorFilter_Delegate}.
+ *
+ * @see ColorFilter_Delegate
+ *
+ */
+public class BlendModeColorFilter_Delegate extends ColorFilter_Delegate {
+
+ @Override
+ public String getSupportMessage() {
+ return "BlendMode Color Filters are not supported.";
+ }
+
+ // ---- native methods ----
+
+ @LayoutlibDelegate
+ /*package*/ static long native_CreateBlendModeFilter(int srcColor, int blendmode) {
+ return PorterDuffColorFilter_Delegate.native_CreateBlendModeFilter(srcColor, blendmode);
+ }
+}
diff --git a/bridge/src/android/graphics/Canvas_Delegate.java b/bridge/src/android/graphics/Canvas_Delegate.java
index a23244b..2d18d7f 100644
--- a/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/bridge/src/android/graphics/Canvas_Delegate.java
@@ -97,14 +97,10 @@
}
@LayoutlibDelegate
- /*package*/ static long nInitRaster(@Nullable Bitmap bitmap) {
- long nativeBitmapOrZero = 0;
- if (bitmap != null) {
- nativeBitmapOrZero = bitmap.getNativeInstance();
- }
- if (nativeBitmapOrZero > 0) {
+ /*package*/ static long nInitRaster(long bitmapHandle) {
+ if (bitmapHandle > 0) {
// get the Bitmap from the int
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmapHandle);
// create a new Canvas_Delegate with the given bitmap and return its new native int.
Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate);
@@ -119,10 +115,10 @@
}
@LayoutlibDelegate
- public static void nSetBitmap(long canvas, Bitmap bitmap) {
+ public static void nSetBitmap(long canvas, long bitmapHandle) {
Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
- Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
- if (canvasDelegate == null || bitmapDelegate==null) {
+ Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmapHandle);
+ if (canvasDelegate == null || bitmapDelegate == null) {
return;
}
canvasDelegate.mBitmap = bitmapDelegate;
@@ -184,15 +180,17 @@
}
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
- if (paintDelegate == null) {
- return 0;
- }
return canvasDelegate.saveLayer(new RectF(l, t, r, b),
paintDelegate, layerFlags);
}
@LayoutlibDelegate
+ public static int nSaveUnclippedLayer(long nativeCanvas, int l, int t, int r, int b) {
+ return nSaveLayer(nativeCanvas, l, t, r, b, 0, 0);
+ }
+
+ @LayoutlibDelegate
public static int nSaveLayerAlpha(long nativeCanvas, float l,
float t, float r, float b,
int alpha, int layerFlags) {
diff --git a/bridge/src/android/graphics/ColorSpace_Rgb_Delegate.java b/bridge/src/android/graphics/ColorSpace_Rgb_Delegate.java
new file mode 100644
index 0000000..3637055
--- /dev/null
+++ b/bridge/src/android/graphics/ColorSpace_Rgb_Delegate.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 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.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.ColorSpace
+ *
+ * Through the layoutlib_create tool, the original native methods of ColorSpace have been replaced
+ * by calls to methods of the same name in this delegate class.
+ */
+public class ColorSpace_Rgb_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<ColorSpace_Rgb_Delegate> sManager =
+ new DelegateManager<>(ColorSpace_Rgb_Delegate.class);
+ private static long sFinalizer = -1;
+
+ @LayoutlibDelegate
+ /*package*/ static long nativeGetNativeFinalizer() {
+ synchronized (ColorSpace_Rgb_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nativeCreate(float a, float b, float c, float d,
+ float e, float f, float g, float[] xyz) {
+ // Layoutlib does not support color spaces, but a native object is required
+ // for ColorSpace$Rgb. This creates an empty delegate for it.
+ return sManager.addNewDelegate(new ColorSpace_Rgb_Delegate());
+ }
+}
diff --git a/bridge/src/android/graphics/Color_Delegate.java b/bridge/src/android/graphics/Color_Delegate.java
new file mode 100644
index 0000000..afd24f0
--- /dev/null
+++ b/bridge/src/android/graphics/Color_Delegate.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 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.graphics;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.Color
+ *
+ * Through the layoutlib_create tool, the original native methods of Color have been replaced
+ * by calls to methods of the same name in this delegate class.
+ */
+public class Color_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeRGBToHSV(int red, int greed, int blue, float hsv[]) {
+ java.awt.Color.RGBtoHSB(red, greed, blue, hsv);
+ hsv[0] = hsv[0] * 360;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nativeHSVToColor(int alpha, float hsv[]) {
+ java.awt.Color rgb = new java.awt.Color(java.awt.Color.HSBtoRGB(hsv[0] / 360, pin(hsv[1]), pin(hsv[2])));
+ return Color.argb(alpha, rgb.getRed(), rgb.getGreen(), rgb.getBlue());
+ }
+
+ private static float pin(float value) {
+ return Math.max(0, Math.min(1, value));
+ }
+}
diff --git a/bridge/src/android/graphics/FontFamily_Delegate.java b/bridge/src/android/graphics/FontFamily_Delegate.java
index 1ad1f8f..db2cd1b 100644
--- a/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -44,6 +44,10 @@
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
+import java.util.logging.Logger;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+import sun.font.FontUtilities;
import static android.graphics.Typeface.RESOLVE_BY_FONT_TABLE;
import static android.graphics.Typeface_Delegate.SYSTEM_FONTS;
@@ -92,11 +96,11 @@
/**
* A class associating {@link Font} with its metadata.
*/
- private static final class FontInfo {
+ public static final class FontInfo {
@Nullable
- Font mFont;
- int mWeight;
- boolean mIsItalic;
+ public Font mFont;
+ public int mWeight;
+ public boolean mIsItalic;
@Override
public boolean equals(Object o) {
@@ -124,6 +128,7 @@
// ---- delegate manager ----
private static final DelegateManager<FontFamily_Delegate> sManager =
new DelegateManager<FontFamily_Delegate>(FontFamily_Delegate.class);
+ private static long sFamilyFinalizer = -1;
// ---- delegate helper data ----
private static String sFontLocation;
@@ -218,17 +223,16 @@
} else {
int bestMatch = Integer.MAX_VALUE;
- //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
for (FontInfo font : mFonts.keySet()) {
int match = computeMatch(font, desiredStyle);
if (match < bestMatch) {
bestMatch = match;
bestFont = font;
+ if (bestMatch == 0) {
+ break;
+ }
}
}
-
- // This would mean that we already had the font so it should be in the set
- assert bestMatch != 0;
}
if (bestFont == null) {
@@ -284,7 +288,7 @@
}
@Nullable
- /*package*/ static String getFontLocation() {
+ public static String getFontLocation() {
return sFontLocation;
}
@@ -323,10 +327,14 @@
}
@LayoutlibDelegate
- /*package*/ static void nUnrefFamily(long nativePtr) {
- // Removing the java reference for the object doesn't mean that it's freed for garbage
- // collection. Typeface_Delegate may still hold a reference for it.
- sManager.removeJavaReferenceFor(nativePtr);
+ /*package*/ static long nGetFamilyReleaseFunc() {
+ synchronized (FontFamily_Delegate.class) {
+ if (sFamilyFinalizer == -1) {
+ sFamilyFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFamilyFinalizer;
}
@LayoutlibDelegate
@@ -406,12 +414,16 @@
fontInfo = new FontInfo();
fontInfo.mFont = font;
if (weight == RESOLVE_BY_FONT_TABLE) {
- fontInfo.mWeight = font.isBold() ? BOLD_FONT_WEIGHT : DEFAULT_FONT_WEIGHT;
+ fontInfo.mWeight = FontUtilities.getFont2D(font).getWeight();
} else {
fontInfo.mWeight = weight;
}
- fontInfo.mIsItalic = isItalic == RESOLVE_BY_FONT_TABLE ? font.isItalic() :
- isItalic == 1;
+ if (isItalic == RESOLVE_BY_FONT_TABLE) {
+ fontInfo.mIsItalic =
+ (FontUtilities.getFont2D(font).getStyle() & Font.ITALIC) != 0;
+ } else {
+ fontInfo.mIsItalic = isItalic == 1;
+ }
ffd.addFont(fontInfo);
return true;
} catch (IOException e) {
@@ -447,8 +459,10 @@
}
@LayoutlibDelegate
- /*package*/ static void nAbort(long builderPtr) {
- sManager.removeJavaReferenceFor(builderPtr);
+ /*package*/ static long nGetBuilderReleaseFunc() {
+ // Layoutlib uses the same reference for the builder and the font family,
+ // so it should not release that reference at the builder stage.
+ return -1;
}
// ---- private helper methods ----
@@ -476,6 +490,7 @@
private boolean addFont(@NonNull String path, int weight, int italic) {
if (path.startsWith(SYSTEM_FONTS) &&
!SDK_FONTS.contains(path.substring(SYSTEM_FONTS.length()))) {
+ Logger.getLogger(FontFamily_Delegate.class.getSimpleName()).warning("Unable to load font " + path);
return mValid = false;
}
// Set valid to true, even if the font fails to load.
@@ -499,10 +514,10 @@
/**
* Compute matching metric between two styles - 0 is an exact match.
*/
- private static int computeMatch(@NonNull FontInfo font1, @NonNull FontInfo font2) {
- int score = Math.abs(font1.mWeight - font2.mWeight);
+ public static int computeMatch(@NonNull FontInfo font1, @NonNull FontInfo font2) {
+ int score = Math.abs(font1.mWeight / 100 - font2.mWeight / 100);
if (font1.mIsItalic != font2.mIsItalic) {
- score += 200;
+ score += 2;
}
return score;
}
@@ -514,9 +529,8 @@
* @param srcFont the source font
* @param outFont contains the desired font style. Updated to contain the derived font and
* its style
- * @return outFont
*/
- private void deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
+ public static void deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
int desiredWeight = outFont.mWeight;
int srcWeight = srcFont.mWeight;
assert srcFont.mFont != null;
diff --git a/bridge/src/android/graphics/Gradient_Delegate.java b/bridge/src/android/graphics/Gradient_Delegate.java
index 64410e4..8aed4b4 100644
--- a/bridge/src/android/graphics/Gradient_Delegate.java
+++ b/bridge/src/android/graphics/Gradient_Delegate.java
@@ -18,6 +18,8 @@
import android.graphics.Shader.TileMode;
+import java.util.Arrays;
+
/**
* Base class for true Gradient shader delegate.
*/
@@ -47,7 +49,7 @@
* corresponding color in the colors array. If this is null, the
* the colors are distributed evenly along the gradient line.
*/
- protected Gradient_Delegate(long nativeMatrix, int colors[], float positions[]) {
+ protected Gradient_Delegate(long nativeMatrix, long[] colors, float[] positions) {
super(nativeMatrix);
assert colors.length >= 2 : "needs >= 2 number of colors";
@@ -62,9 +64,13 @@
} else {
assert colors.length == positions.length :
"color and position " + "arrays must be of equal length";
+ positions[0] = Math.min(Math.max(0, positions[0]), 1);
+ for (int i = 1; i < positions.length; i++) {
+ positions[i] = Math.min(Math.max(positions[i-1], positions[i]), 1);
+ }
}
- mColors = colors;
+ mColors = Arrays.stream(colors).mapToInt(Color::toArgb).toArray();
mPositions = positions;
}
@@ -107,14 +113,24 @@
for (int i = 0 ; i <= GRADIENT_SIZE ; i++) {
// compute current position
float currentPos = (float)i/GRADIENT_SIZE;
- while (currentPos > mPositions[nextPos]) {
+
+ if (currentPos < mPositions[0]) {
+ mGradient[i] = mColors[0];
+ continue;
+ }
+
+ while (nextPos < mPositions.length && currentPos >= mPositions[nextPos]) {
prevPos = nextPos++;
}
- float percent = (currentPos - mPositions[prevPos]) /
- (mPositions[nextPos] - mPositions[prevPos]);
+ if (nextPos == mPositions.length || currentPos == prevPos) {
+ mGradient[i] = mColors[prevPos];
+ } else {
+ float percent = (currentPos - mPositions[prevPos]) /
+ (mPositions[nextPos] - mPositions[prevPos]);
- mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
+ mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
+ }
}
}
}
diff --git a/bridge/src/android/graphics/ImageDecoder.java b/bridge/src/android/graphics/ImageDecoder.java
index 506eab5..eefdb2e 100644
--- a/bridge/src/android/graphics/ImageDecoder.java
+++ b/bridge/src/android/graphics/ImageDecoder.java
@@ -513,9 +513,7 @@
*
* @param allocator Type of allocator to use.
*/
- public ImageDecoder setAllocator(@Allocator int allocator) {
- return this;
- }
+ public void setAllocator(@Allocator int allocator) { }
/**
* Specify whether the {@link Bitmap} should have unpremultiplied pixels.
@@ -546,9 +544,7 @@
* {@link Canvas} will be recorded immediately and then applied to each
* frame.</p>
*/
- public ImageDecoder setPostProcessor(@Nullable PostProcessor p) {
- return this;
- }
+ public void setPostProcessor(@Nullable PostProcessor p) { }
/**
* Set (replace) the {@link OnPartialImageListener} on this object.
@@ -556,9 +552,7 @@
* Will be called if there is an error in the input. Without one, a
* partial {@link Bitmap} will be created.
*/
- public ImageDecoder setOnPartialImageListener(@Nullable OnPartialImageListener l) {
- return this;
- }
+ public void setOnPartialImageListener(@Nullable OnPartialImageListener l) { }
/**
* Crop the output to {@code subset} of the (possibly) scaled image.
@@ -572,9 +566,7 @@
* {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
* but merely crops the output.</p>
*/
- public ImageDecoder setCrop(@Nullable Rect subset) {
- return this;
- }
+ public void setCrop(@Nullable Rect subset) { }
/**
* Set a Rect for retrieving nine patch padding.
@@ -584,9 +576,8 @@
*
* @hide
*/
- public ImageDecoder setOutPaddingRect(@NonNull Rect outPadding) {
+ public void setOutPaddingRect(@NonNull Rect outPadding) {
mOutPaddingRect = outPadding;
- return this;
}
/**
@@ -701,4 +692,77 @@
public static Bitmap decodeBitmap(@NonNull Source src) throws IOException {
return decodeBitmap(src, null);
}
+
+ public static final class DecodeException extends IOException {
+ /**
+ * An Exception was thrown reading the {@link Source}.
+ */
+ public static final int SOURCE_EXCEPTION = 1;
+
+ /**
+ * The encoded data was incomplete.
+ */
+ public static final int SOURCE_INCOMPLETE = 2;
+
+ /**
+ * The encoded data contained an error.
+ */
+ public static final int SOURCE_MALFORMED_DATA = 3;
+
+ @Error final int mError;
+ @NonNull final Source mSource;
+
+ DecodeException(@Error int error, @Nullable Throwable cause, @NonNull Source source) {
+ super(errorMessage(error, cause), cause);
+ mError = error;
+ mSource = source;
+ }
+
+ /**
+ * Private method called by JNI.
+ */
+ @SuppressWarnings("unused")
+ DecodeException(@Error int error, @Nullable String msg, @Nullable Throwable cause,
+ @NonNull Source source) {
+ super(msg + errorMessage(error, cause), cause);
+ mError = error;
+ mSource = source;
+ }
+
+ /**
+ * Retrieve the reason that decoding was interrupted.
+ *
+ * <p>If the error is {@link #SOURCE_EXCEPTION}, the underlying
+ * {@link java.lang.Throwable} can be retrieved with
+ * {@link java.lang.Throwable#getCause}.</p>
+ */
+ @Error
+ public int getError() {
+ return mError;
+ }
+
+ /**
+ * Retrieve the {@link Source Source} that was interrupted.
+ *
+ * <p>This can be used for equality checking to find the Source which
+ * failed to completely decode.</p>
+ */
+ @NonNull
+ public Source getSource() {
+ return mSource;
+ }
+
+ private static String errorMessage(@Error int error, @Nullable Throwable cause) {
+ switch (error) {
+ case SOURCE_EXCEPTION:
+ return "Exception in input: " + cause;
+ case SOURCE_INCOMPLETE:
+ return "Input was incomplete.";
+ case SOURCE_MALFORMED_DATA:
+ return "Input contained an error.";
+ default:
+ return "";
+ }
+ }
+ }
}
diff --git a/bridge/src/android/graphics/LinearGradient_Delegate.java b/bridge/src/android/graphics/LinearGradient_Delegate.java
index 477705c..8dcf374 100644
--- a/bridge/src/android/graphics/LinearGradient_Delegate.java
+++ b/bridge/src/android/graphics/LinearGradient_Delegate.java
@@ -59,22 +59,14 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long nativeCreate1(LinearGradient thisGradient, long matrix,
- float x0, float y0, float x1, float y1,
- int colors[], float positions[], int tileMode) {
+ /*package*/ static long nativeCreate(LinearGradient thisGradient, long matrix,
+ float x0, float y0, float x1, float y1, long[] colors, float[] positions,
+ int tileMode, long colorSpaceHandle) {
LinearGradient_Delegate newDelegate = new LinearGradient_Delegate(matrix, x0, y0,
x1, y1, colors, positions, Shader_Delegate.getTileMode(tileMode));
return sManager.addNewDelegate(newDelegate);
}
- @LayoutlibDelegate
- /*package*/ static long nativeCreate2(LinearGradient thisGradient, long matrix,
- float x0, float y0, float x1, float y1,
- int color0, int color1, int tileMode) {
- return nativeCreate1(thisGradient, matrix, x0, y0, x1, y1, new int[] { color0, color1},
- null /*positions*/, tileMode);
- }
-
// ---- Private delegate/helper methods ----
/**
@@ -92,7 +84,7 @@
* @param tile The Shader tiling mode
*/
private LinearGradient_Delegate(long nativeMatrix, float x0, float y0, float x1,
- float y1, int colors[], float positions[], TileMode tile) {
+ float y1, long[] colors, float[] positions, TileMode tile) {
super(nativeMatrix, colors, positions);
mJavaPaint = new LinearGradientPaint(x0, y0, x1, y1, mColors, mPositions, tile);
}
@@ -111,8 +103,8 @@
private final float mDy;
private final float mDSize2;
- public LinearGradientPaint(float x0, float y0, float x1, float y1, int colors[],
- float positions[], TileMode tile) {
+ public LinearGradientPaint(float x0, float y0, float x1, float y1, int[] colors,
+ float[] positions, TileMode tile) {
super(colors, positions, tile);
mX0 = x0;
mY0 = y0;
diff --git a/bridge/src/android/graphics/Matrix_Delegate.java b/bridge/src/android/graphics/Matrix_Delegate.java
index 5ae181d..64d8864 100644
--- a/bridge/src/android/graphics/Matrix_Delegate.java
+++ b/bridge/src/android/graphics/Matrix_Delegate.java
@@ -616,15 +616,25 @@
return false;
}
- try {
- AffineTransform affineTransform = d.getAffineTransform();
- AffineTransform inverseTransform = affineTransform.createInverse();
- setValues(inverseTransform, inv_mtx.mValues);
+ float det = d.mValues[0] * (d.mValues[4] * d.mValues[8] - d.mValues[5] * d.mValues[7])
+ + d.mValues[1] * (d.mValues[5] * d.mValues[6] - d.mValues[3] * d.mValues[8])
+ + d.mValues[2] * (d.mValues[3] * d.mValues[7] - d.mValues[4] * d.mValues[6]);
- return true;
- } catch (NoninvertibleTransformException e) {
+ if (det == 0.0) {
return false;
}
+
+ inv_mtx.mValues[0] = (d.mValues[4] * d.mValues[8] - d.mValues[5] * d.mValues[7]) / det;
+ inv_mtx.mValues[1] = (d.mValues[2] * d.mValues[7] - d.mValues[1] * d.mValues[8]) / det;
+ inv_mtx.mValues[2] = (d.mValues[1] * d.mValues[5] - d.mValues[2] * d.mValues[4]) / det;
+ inv_mtx.mValues[3] = (d.mValues[5] * d.mValues[6] - d.mValues[3] * d.mValues[8]) / det;
+ inv_mtx.mValues[4] = (d.mValues[0] * d.mValues[8] - d.mValues[2] * d.mValues[6]) / det;
+ inv_mtx.mValues[5] = (d.mValues[2] * d.mValues[3] - d.mValues[0] * d.mValues[5]) / det;
+ inv_mtx.mValues[6] = (d.mValues[3] * d.mValues[7] - d.mValues[4] * d.mValues[6]) / det;
+ inv_mtx.mValues[7] = (d.mValues[1] * d.mValues[6] - d.mValues[0] * d.mValues[7]) / det;
+ inv_mtx.mValues[8] = (d.mValues[0] * d.mValues[4] - d.mValues[1] * d.mValues[3]) / det;
+
+ return true;
}
@LayoutlibDelegate
diff --git a/bridge/src/android/graphics/NinePatch_Delegate.java b/bridge/src/android/graphics/NinePatch_Delegate.java
index ce2c18b..a6cd51e 100644
--- a/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -108,12 +108,10 @@
*/
public static NinePatchChunk getChunk(byte[] array) {
SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array);
- NinePatchChunk chunk = chunkRef.get();
+ NinePatchChunk chunk = chunkRef == null ? null : chunkRef.get();
if (chunk == null) {
ByteArrayInputStream bais = new ByteArrayInputStream(array);
- ObjectInputStream ois = null;
- try {
- ois = new ObjectInputStream(bais);
+ try (ObjectInputStream ois = new ObjectInputStream(bais)) {
chunk = (NinePatchChunk) ois.readObject();
// put back the chunk in the cache
@@ -128,13 +126,6 @@
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
"Failed to deserialize NinePatchChunk class.", e, null /*data*/);
return null;
- } finally {
- if (ois != null) {
- try {
- ois.close();
- } catch (IOException ignored) {
- }
- }
}
}
@@ -170,7 +161,8 @@
@LayoutlibDelegate
- /*package*/ static long nativeGetTransparentRegion(Bitmap bitmap, long chunk, Rect location) {
+ /*package*/ static long nativeGetTransparentRegion(long bitmapHandle, long chunk,
+ Rect location) {
return 0;
}
@@ -182,4 +174,7 @@
return null;
}
+ public static void clearCache() {
+ sChunkCache.clear();
+ }
}
diff --git a/bridge/src/android/graphics/Paint_Delegate.java b/bridge/src/android/graphics/Paint_Delegate.java
index dae966d..df22d63 100644
--- a/bridge/src/android/graphics/Paint_Delegate.java
+++ b/bridge/src/android/graphics/Paint_Delegate.java
@@ -37,6 +37,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@@ -98,7 +99,8 @@
private float mTextScaleX;
private float mTextSkewX;
private int mHintingMode = Paint.HINTING_ON;
- private int mHyphenEdit;
+ private int mStartHyphenEdit;
+ private int mEndHyphenEdit;
private float mLetterSpacing; // not used in actual text rendering.
private float mWordSpacing; // not used in actual text rendering.
// Variant of the font. A paint's variant can only be compact or elegant.
@@ -145,6 +147,7 @@
List<FontInfo> infoList = StreamSupport.stream(typeface.getFonts(mFontVariant).spliterator
(), false)
+ .filter(Objects::nonNull)
.map(font -> getFontInfo(font, mTextSize, affineTransform))
.collect(Collectors.toList());
mFonts = Collections.unmodifiableList(infoList);
@@ -374,20 +377,18 @@
}
@LayoutlibDelegate
- /*package*/ static int nGetColor(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ /*package*/ static void nSetColor(long paintPtr, long colorSpaceHandle, long color) {
+ Paint_Delegate delegate = sManager.getDelegate(paintPtr);
if (delegate == null) {
- return 0;
+ return;
}
- return delegate.mColor;
+ delegate.mColor = Color.toArgb(color);
}
@LayoutlibDelegate
- /*package*/ static void nSetColor(long nativePaint, int color) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ /*package*/ static void nSetColor(long paintPtr, int color) {
+ Paint_Delegate delegate = sManager.getDelegate(paintPtr);
if (delegate == null) {
return;
}
@@ -396,17 +397,6 @@
}
@LayoutlibDelegate
- /*package*/ static int nGetAlpha(long nativePaint) {
- // get the delegate from the native int.
- Paint_Delegate delegate = sManager.getDelegate(nativePaint);
- if (delegate == null) {
- return 0;
- }
-
- return delegate.getAlpha();
- }
-
- @LayoutlibDelegate
/*package*/ static void nSetAlpha(long nativePaint, int a) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -462,9 +452,9 @@
}
@LayoutlibDelegate
- /*package*/ static void nSetShadowLayer(long paint, float radius, float dx, float dy,
- int color) {
- // FIXME
+ /*package*/ static void nSetShadowLayer(long paintPtr,
+ float radius, float dx, float dy, long colorSpaceHandle,
+ long shadowColor) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Paint.setShadowLayer is not supported.", null, null /*data*/);
}
@@ -1032,7 +1022,7 @@
}
@LayoutlibDelegate
- /*package*/ static void nGetCharArrayBounds(long nativePaint, char[] text, int index,
+ public static void nGetCharArrayBounds(long nativePaint, char[] text, int index,
int count, int bidiFlags, Rect bounds) {
// get the delegate from the native int.
@@ -1100,21 +1090,39 @@
}
@LayoutlibDelegate
- /*package*/ static int nGetHyphenEdit(long nativePaint) {
+ /*package*/ static int nGetStartHyphenEdit(long nativePaint) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
- return delegate.mHyphenEdit;
+ return delegate.mStartHyphenEdit;
}
@LayoutlibDelegate
- /*package*/ static void nSetHyphenEdit(long nativePaint, int hyphen) {
+ /*package*/ static void nSetStartHyphenEdit(long nativePaint, int hyphen) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return;
}
- delegate.mHyphenEdit = hyphen;
+ delegate.mStartHyphenEdit = hyphen;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nGetEndHyphenEdit(long nativePaint) {
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null) {
+ return 0;
+ }
+ return delegate.mEndHyphenEdit;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nSetEndHyphenEdit(long nativePaint, int hyphen) {
+ Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+ if (delegate == null) {
+ return;
+ }
+ delegate.mEndHyphenEdit = hyphen;
}
@LayoutlibDelegate
diff --git a/bridge/src/android/graphics/Path_Delegate.java b/bridge/src/android/graphics/Path_Delegate.java
index 0979017..2f86026 100644
--- a/bridge/src/android/graphics/Path_Delegate.java
+++ b/bridge/src/android/graphics/Path_Delegate.java
@@ -38,6 +38,8 @@
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
/**
* Delegate implementing the native methods of android.graphics.Path
*
@@ -59,6 +61,8 @@
private static final float EPSILON = 1e-4f;
+ private static long sFinalizer = -1;
+
// ---- delegate data ----
private FillType mFillType = FillType.WINDING;
private Path2D mPath = new Path2D.Double();
@@ -476,8 +480,14 @@
}
@LayoutlibDelegate
- /*package*/ static void nFinalize(long nPath) {
- sManager.removeJavaReferenceFor(nPath);
+ /*package*/ static long nGetFinalizer() {
+ synchronized (Path_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
}
@LayoutlibDelegate
diff --git a/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
index ff3f19f..0dc6f61 100644
--- a/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
+++ b/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
@@ -74,7 +74,7 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) {
+ /*package*/ static long native_CreateBlendModeFilter(int srcColor, int porterDuffMode) {
PorterDuffColorFilter_Delegate newDelegate =
new PorterDuffColorFilter_Delegate(srcColor, porterDuffMode);
return sManager.addNewDelegate(newDelegate);
diff --git a/bridge/src/android/graphics/RadialGradient_Delegate.java b/bridge/src/android/graphics/RadialGradient_Delegate.java
index 25521d2..a42d654 100644
--- a/bridge/src/android/graphics/RadialGradient_Delegate.java
+++ b/bridge/src/android/graphics/RadialGradient_Delegate.java
@@ -59,20 +59,13 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long nativeCreate1(long matrix, float x, float y, float radius,
- int colors[], float positions[], int tileMode) {
+ /*package*/ static long nativeCreate(long matrix, float x, float y, float radius,
+ long[] colors, float[] positions, int tileMode, long colorSpaceHandle) {
RadialGradient_Delegate newDelegate = new RadialGradient_Delegate(matrix, x, y, radius,
colors, positions, Shader_Delegate.getTileMode(tileMode));
return sManager.addNewDelegate(newDelegate);
}
- @LayoutlibDelegate
- /*package*/ static long nativeCreate2(long matrix, float x, float y, float radius,
- int color0, int color1, int tileMode) {
- return nativeCreate1(matrix, x, y, radius, new int[] { color0, color1 },
- null /*positions*/, tileMode);
- }
-
// ---- Private delegate/helper methods ----
/**
@@ -91,7 +84,7 @@
* @param tile The Shader tiling mode
*/
private RadialGradient_Delegate(long nativeMatrix, float x, float y, float radius,
- int colors[], float positions[], TileMode tile) {
+ long[] colors, float[] positions, TileMode tile) {
super(nativeMatrix, colors, positions);
mJavaPaint = new RadialGradientPaint(x, y, radius, mColors, mPositions, tile);
}
diff --git a/bridge/src/android/view/RenderNode_Delegate.java b/bridge/src/android/graphics/RenderNode_Delegate.java
similarity index 98%
rename from bridge/src/android/view/RenderNode_Delegate.java
rename to bridge/src/android/graphics/RenderNode_Delegate.java
index 152878b..ae08b00 100644
--- a/bridge/src/android/view/RenderNode_Delegate.java
+++ b/bridge/src/android/graphics/RenderNode_Delegate.java
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package android.view;
+package android.graphics;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import android.graphics.Matrix;
-
import libcore.util.NativeAllocationRegistry_Delegate;
/**
@@ -172,7 +170,7 @@
/*package*/ static void getMatrix(RenderNode renderNode, Matrix outMatrix) {
outMatrix.reset();
if (renderNode != null) {
- float rotation = renderNode.getRotation();
+ float rotation = renderNode.getRotationZ();
float translationX = renderNode.getTranslationX();
float translationY = renderNode.getTranslationY();
float pivotX = renderNode.getPivotX();
diff --git a/bridge/src/android/graphics/Shader_Delegate.java b/bridge/src/android/graphics/Shader_Delegate.java
index eefa929..d606e2d 100644
--- a/bridge/src/android/graphics/Shader_Delegate.java
+++ b/bridge/src/android/graphics/Shader_Delegate.java
@@ -49,6 +49,7 @@
// ---- delegate data ----
private Matrix_Delegate mLocalMatrix = null;
+ private float mAlpha = 1.0f;
// ---- Public Helper methods ----
@@ -107,4 +108,11 @@
return new java.awt.geom.AffineTransform();
}
+ public void setAlpha(float alpha) {
+ mAlpha = alpha;
+ }
+
+ public float getAlpha() {
+ return mAlpha;
+ }
}
diff --git a/bridge/src/android/graphics/SweepGradient_Delegate.java b/bridge/src/android/graphics/SweepGradient_Delegate.java
index 6e8aca3..d29307a 100644
--- a/bridge/src/android/graphics/SweepGradient_Delegate.java
+++ b/bridge/src/android/graphics/SweepGradient_Delegate.java
@@ -56,19 +56,13 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long nativeCreate1(long matrix, float x, float y, int colors[], float
- positions[]) {
+ /*package*/ static long nativeCreate(long matrix, float x, float y, long[] colors,
+ float[] positions, long colorSpaceHandle) {
SweepGradient_Delegate newDelegate = new SweepGradient_Delegate(matrix, x, y, colors,
positions);
return sManager.addNewDelegate(newDelegate);
}
- @LayoutlibDelegate
- /*package*/ static long nativeCreate2(long matrix, float x, float y, int color0, int color1) {
- return nativeCreate1(matrix, x, y, new int[] { color0, color1 },
- null /*positions*/);
- }
-
// ---- Private delegate/helper methods ----
/**
@@ -87,7 +81,7 @@
* spaced evenly.
*/
private SweepGradient_Delegate(long nativeMatrix, float cx, float cy,
- int colors[], float positions[]) {
+ long[] colors, float[] positions) {
super(nativeMatrix, colors, positions);
mJavaPaint = new SweepGradientPaint(cx, cy, mColors, mPositions);
}
diff --git a/bridge/src/android/graphics/Typeface_Builder_Delegate.java b/bridge/src/android/graphics/Typeface_Builder_Delegate.java
new file mode 100644
index 0000000..9d9d844
--- /dev/null
+++ b/bridge/src/android/graphics/Typeface_Builder_Delegate.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.graphics;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.graphics.Typeface.Builder;
+import android.graphics.fonts.FontVariationAxis;
+
+public class Typeface_Builder_Delegate {
+
+ /**
+ * Creates a unique id for a given AssetManager and asset path.
+ *
+ * @param mgr AssetManager instance
+ * @param path The path for the asset.
+ * @param ttcIndex The TTC index for the font.
+ * @param axes The font variation settings.
+ * @return Unique id for a given AssetManager and asset path.
+ */
+ @LayoutlibDelegate
+ public static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
+ @Nullable FontVariationAxis[] axes, int weight, int italic, String fallback) {
+ return Builder.createAssetUid_Original(mgr, path, ttcIndex, axes, weight, italic, fallback);
+ }
+}
diff --git a/bridge/src/android/graphics/Typeface_Delegate.java b/bridge/src/android/graphics/Typeface_Delegate.java
index d793ade..c728513 100644
--- a/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/bridge/src/android/graphics/Typeface_Delegate.java
@@ -18,12 +18,12 @@
import com.android.SdkConstants;
import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceNamespace;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.RenderAction;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
@@ -34,27 +34,22 @@
import android.annotation.Nullable;
import android.content.res.FontResourcesParser;
import android.graphics.FontFamily_Delegate.FontVariant;
+import android.graphics.fonts.FontFamily_Builder_Delegate;
import android.graphics.fonts.FontVariationAxis;
-import android.text.FontConfig;
-import android.util.ArrayMap;
import java.awt.Font;
-import java.io.File;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.lang.ref.SoftReference;
-import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
-import java.util.EnumMap;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Spliterator;
import java.util.Spliterators;
-import static android.graphics.FontFamily_Delegate.getFontLocation;
+import libcore.util.NativeAllocationRegistry_Delegate;
/**
* Delegate implementing the native methods of android.graphics.Typeface
@@ -72,25 +67,32 @@
public static final String SYSTEM_FONTS = "/system/fonts/";
+ public static final Map<String, FontFamily_Delegate[]> sGenericNativeFamilies = new HashMap<>();
+
// ---- delegate manager ----
private static final DelegateManager<Typeface_Delegate> sManager =
new DelegateManager<>(Typeface_Delegate.class);
-
+ private static long sFinalizer = -1;
// ---- delegate data ----
private static long sDefaultTypeface;
@NonNull
private final FontFamily_Delegate[] mFontFamilies; // the reference to FontFamily_Delegate.
+ @NonNull
+ private final FontFamily_Builder_Delegate[] mFontFamilyBuilders; // the reference to
+ // FontFamily_Builder_Delegate.
/** @see Font#getStyle() */
private final int mStyle;
private final int mWeight;
- private SoftReference<EnumMap<FontVariant, List<Font>>> mFontsCache = new SoftReference<>(null);
// ---- Public Helper methods ----
- public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) {
+ private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies,
+ @NonNull FontFamily_Builder_Delegate[] fontFamilyBuilders, int style,
+ int weight) {
mFontFamilies = fontFamilies;
+ mFontFamilyBuilders = fontFamilyBuilders;
mStyle = style;
mWeight = weight;
}
@@ -99,18 +101,6 @@
return sManager.getDelegate(nativeTypeface);
}
- /**
- * Clear the default typefaces when disposing bridge.
- */
- public static void resetDefaults() {
- // Sometimes this is called before the Bridge is initialized. In that case, we don't want to
- // initialize Typeface because the SDK fonts location hasn't been set.
- if (FontFamily_Delegate.getFontLocation() != null) {
- Typeface.sDefaults = null;
- }
- }
-
-
// ---- native methods ----
@LayoutlibDelegate
@@ -124,7 +114,8 @@
}
return sManager.addNewDelegate(
- new Typeface_Delegate(delegate.mFontFamilies, style, delegate.mWeight));
+ new Typeface_Delegate(delegate.mFontFamilies, delegate.mFontFamilyBuilders, style,
+ delegate.mWeight));
}
@LayoutlibDelegate
@@ -141,7 +132,8 @@
int style = weight >= 600 ? (italic ? Typeface.BOLD_ITALIC : Typeface.BOLD) :
(italic ? Typeface.ITALIC : Typeface.NORMAL);
return sManager.addNewDelegate(
- new Typeface_Delegate(delegate.mFontFamilies, style, weight));
+ new Typeface_Delegate(delegate.mFontFamilies, delegate.mFontFamilyBuilders, style,
+ weight));
}
@LayoutlibDelegate
@@ -172,16 +164,23 @@
return 0;
}
Typeface_Delegate weightAlias =
- new Typeface_Delegate(delegate.mFontFamilies, delegate.mStyle, weight);
+ new Typeface_Delegate(delegate.mFontFamilies, delegate.mFontFamilyBuilders,
+ delegate.mStyle,
+ weight);
return sManager.addNewDelegate(weightAlias);
}
@LayoutlibDelegate
/*package*/ static synchronized long nativeCreateFromArray(long[] familyArray, int weight,
int italic) {
- FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length];
- for (int i = 0; i < familyArray.length; i++) {
- fontFamilies[i] = FontFamily_Delegate.getDelegate(familyArray[i]);
+ List<FontFamily_Delegate> fontFamilies = new ArrayList<>();
+ List<FontFamily_Builder_Delegate> fontFamilyBuilders = new ArrayList<>();
+ for (long aFamilyArray : familyArray) {
+ try {
+ fontFamilies.add(FontFamily_Delegate.getDelegate(aFamilyArray));
+ } catch (ClassCastException e) {
+ fontFamilyBuilders.add(FontFamily_Builder_Delegate.getDelegate(aFamilyArray));
+ }
}
if (weight == Typeface.RESOLVE_BY_FONT_TABLE) {
weight = 400;
@@ -191,13 +190,22 @@
}
int style = weight >= 600 ? (italic == 1 ? Typeface.BOLD_ITALIC : Typeface.BOLD) :
(italic == 1 ? Typeface.ITALIC : Typeface.NORMAL);
- Typeface_Delegate delegate = new Typeface_Delegate(fontFamilies, style, weight);
+ Typeface_Delegate delegate =
+ new Typeface_Delegate(fontFamilies.toArray(new FontFamily_Delegate[0]),
+ fontFamilyBuilders.toArray(new FontFamily_Builder_Delegate[0]),
+ style, weight);
return sManager.addNewDelegate(delegate);
}
@LayoutlibDelegate
- /*package*/ static void nativeUnref(long native_instance) {
- sManager.removeJavaReferenceFor(native_instance);
+ /*package*/ static long nativeGetReleaseFunc() {
+ synchronized (Typeface_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
}
@LayoutlibDelegate
@@ -224,26 +232,6 @@
return delegate.mWeight;
}
- @LayoutlibDelegate
- /*package*/ static void buildSystemFallback(String xmlPath, String fontDir,
- ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
- Typeface.buildSystemFallback_Original(getFontLocation() + "/fonts.xml", fontDir, fontMap,
- fallbackMap);
- }
-
- @LayoutlibDelegate
- /*package*/ static FontFamily createFontFamily(String familyName, List<FontConfig.Font> fonts,
- String[] languageTags, int variant, Map<String, ByteBuffer> cache, String fontDir) {
- FontFamily fontFamily = new FontFamily(languageTags, variant);
- for (FontConfig.Font font : fonts) {
- String fullPathName = fontDir + font.getFontName();
- FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, fullPathName, font.getWeight(),
- font.isItalic());
- }
- fontFamily.freeze();
- return fontFamily;
- }
-
/**
* Loads a single font or font family from disk
*/
@@ -261,27 +249,18 @@
// create a block parser for the file
Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
- XmlPullParser parser = null;
+ XmlPullParser parser;
if (psiParserSupport != null && psiParserSupport) {
- parser = context.getLayoutlibCallback().getXmlFileParser(path);
+ parser = context.getLayoutlibCallback().createXmlParserForPsiFile(path);
} else {
- File f = new File(path);
- if (f.isFile()) {
- try {
- parser = ParserFactory.create(f);
- } catch (XmlPullParserException | FileNotFoundException e) {
- // this is an error and not warning since the file existence is checked
- // before
- // attempting to parse it.
- Bridge.getLog().error(null, "Failed to parse file " + path, e,
- null /*data*/);
- }
- }
+ parser = context.getLayoutlibCallback().createXmlParserForFile(path);
}
if (parser != null) {
+ // TODO(namespaces): The aapt namespace should not matter for parsing font files?
BridgeXmlBlockParser blockParser =
- new BridgeXmlBlockParser(parser, context, isFramework);
+ new BridgeXmlBlockParser(
+ parser, context, ResourceNamespace.fromBoolean(isFramework));
try {
FontResourcesParser.FamilyResourceEntry entry =
FontResourcesParser.parse(blockParser, context.getResources());
@@ -297,7 +276,7 @@
null /*data*/);
}
} else {
- typeface = Typeface.createFromResources(context.getAssets(), path, 0);
+ typeface = new Typeface.Builder(context.getAssets(), path).build();
}
return typeface;
@@ -324,59 +303,17 @@
return Typeface.create_Original(family, style, isItalic);
}
- // ---- Private delegate/helper methods ----
-
- private static List<Font> computeFonts(FontVariant variant, FontFamily_Delegate[] fontFamilies,
- int inputWeight, int inputStyle) {
- // Calculate the required weight based on style and weight of this typeface.
- int weight = inputWeight + 50 +
- ((inputStyle & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
- if (weight > 1000) {
- weight = 1000;
- } else if (weight < 100) {
- weight = 100;
+ @LayoutlibDelegate
+ /*package*/ static void nativeRegisterGenericFamily(String str, long nativePtr) {
+ Typeface_Delegate delegate = sManager.getDelegate(nativePtr);
+ if (delegate == null) {
+ return;
}
- final boolean isItalic = (inputStyle & Font.ITALIC) != 0;
- List<Font> fonts = new ArrayList<Font>(fontFamilies.length);
- for (int i = 0; i < fontFamilies.length; i++) {
- FontFamily_Delegate ffd = fontFamilies[i];
- if (ffd != null && ffd.isValid()) {
- Font font = ffd.getFont(weight, isItalic);
- if (font != null) {
- FontVariant ffdVariant = ffd.getVariant();
- if (ffdVariant == FontVariant.NONE) {
- fonts.add(font);
- continue;
- }
- // We cannot open each font and get locales supported, etc to match the fonts.
- // As a workaround, we hardcode certain assumptions like Elegant and Compact
- // always appear in pairs.
- assert i < fontFamilies.length - 1;
- FontFamily_Delegate ffd2 = fontFamilies[++i];
- assert ffd2 != null;
- FontVariant ffd2Variant = ffd2.getVariant();
- Font font2 = ffd2.getFont(weight, isItalic);
- assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant &&
- font2 != null;
- // Add the font with the matching variant to the list.
- if (variant == ffd.getVariant()) {
- fonts.add(font);
- } else {
- fonts.add(font2);
- }
- } else {
- // The FontFamily is valid but doesn't contain any matching font. This means
- // that the font failed to load. We add null to the list of fonts. Don't throw
- // the warning just yet. If this is a non-english font, we don't want to warn
- // users who are trying to render only english text.
- fonts.add(null);
- }
- }
- }
-
- return fonts;
+ sGenericNativeFamilies.put(str, delegate.mFontFamilies);
}
+ // ---- Private delegate/helper methods ----
+
/**
* Return an Iterable of fonts that match the style and variant. The list is ordered
* according to preference of fonts.
@@ -392,11 +329,12 @@
public Iterable<Font> getFonts(final FontVariant variant) {
assert variant != FontVariant.NONE;
- return new FontsIterator(mFontFamilies, variant, mWeight, mStyle);
+ return new FontsIterator(mFontFamilies, mFontFamilyBuilders, variant, mWeight, mStyle);
}
private static class FontsIterator implements Iterator<Font>, Iterable<Font> {
private final FontFamily_Delegate[] fontFamilies;
+ private final FontFamily_Builder_Delegate[] fontFamilyBuilders;
private final int weight;
private final boolean isItalic;
private final FontVariant variant;
@@ -404,6 +342,7 @@
private int index = 0;
private FontsIterator(@NonNull FontFamily_Delegate[] fontFamilies,
+ @NonNull FontFamily_Builder_Delegate[] fontFamilyBuilders,
@NonNull FontVariant variant, int weight, int style) {
// Calculate the required weight based on style and weight of this typeface.
int boldExtraWeight =
@@ -411,23 +350,36 @@
this.weight = Math.min(Math.max(100, weight + 50 + boldExtraWeight), 1000);
this.isItalic = (style & Font.ITALIC) != 0;
this.fontFamilies = fontFamilies;
+ this.fontFamilyBuilders = fontFamilyBuilders;
this.variant = variant;
}
@Override
public boolean hasNext() {
- return index < fontFamilies.length;
+ return index < (fontFamilies.length + fontFamilyBuilders.length);
}
@Override
@Nullable
public Font next() {
- FontFamily_Delegate ffd = fontFamilies[index++];
- if (ffd == null || !ffd.isValid()) {
- return null;
+ Font font;
+ FontVariant ffdVariant;
+ if (index < fontFamilies.length) {
+ FontFamily_Delegate ffd = fontFamilies[index++];
+ if (ffd == null || !ffd.isValid()) {
+ return null;
+ }
+ font = ffd.getFont(weight, isItalic);
+ ffdVariant = ffd.getVariant();
+ } else {
+ FontFamily_Builder_Delegate ffd = fontFamilyBuilders[index++ - fontFamilies.length];
+ if (ffd == null) {
+ return null;
+ }
+ font = ffd.getFont(weight, isItalic);
+ ffdVariant = ffd.getVariant();
}
- Font font = ffd.getFont(weight, isItalic);
if (font == null) {
// The FontFamily is valid but doesn't contain any matching font. This means
// that the font failed to load. We add null to the list of fonts. Don't throw
@@ -436,27 +388,26 @@
return null;
}
- FontVariant ffdVariant = ffd.getVariant();
- if (ffdVariant == FontVariant.NONE) {
+ if (ffdVariant == FontVariant.NONE || ffdVariant == variant) {
return font;
}
// We cannot open each font and get locales supported, etc to match the fonts.
// As a workaround, we hardcode certain assumptions like Elegant and Compact
// always appear in pairs.
- assert index < fontFamilies.length - 1;
- FontFamily_Delegate ffd2 = fontFamilies[index++];
- assert ffd2 != null;
+ if (index < fontFamilies.length) {
+ assert index < fontFamilies.length - 1;
+ FontFamily_Delegate ffd2 = fontFamilies[index++];
+ assert ffd2 != null;
- if (ffdVariant == variant) {
- return font;
+ return ffd2.getFont(weight, isItalic);
+ } else {
+ assert index < fontFamilies.length + fontFamilyBuilders.length - 1;
+ FontFamily_Builder_Delegate ffd2 = fontFamilyBuilders[index++ - fontFamilies.length];
+ assert ffd2 != null;
+
+ return ffd2.getFont(weight, isItalic);
}
-
- FontVariant ffd2Variant = ffd2.getVariant();
- Font font2 = ffd2.getFont(weight, isItalic);
- assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant && font2 != null;
- // Add the font with the matching variant to the list.
- return variant == ffd.getVariant() ? font : font2;
}
@NonNull
@@ -467,7 +418,8 @@
@Override
public Spliterator<Font> spliterator() {
- return Spliterators.spliterator(iterator(), fontFamilies.length,
+ return Spliterators.spliterator(iterator(),
+ fontFamilies.length + fontFamilyBuilders.length,
Spliterator.IMMUTABLE | Spliterator.SIZED);
}
}
diff --git a/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
index d9f8692..8102894 100644
--- a/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -32,6 +32,7 @@
import android.graphics.Paint.Join;
import android.graphics.Paint_Delegate;
import android.graphics.Path;
+import android.graphics.Path.FillType;
import android.graphics.PathMeasure;
import android.graphics.Path_Delegate;
import android.graphics.Rect;
@@ -1144,6 +1145,7 @@
mRenderPath.reset();
if (VPath.isClipPath()) {
+ mRenderPath.setFillType(FillType.WINDING);
mRenderPath.addPath(path, mFinalPathMatrix);
Canvas_Delegate.nClipPath(canvasPtr, mRenderPath.mNativePath, Op
.INTERSECT.nativeInt);
@@ -1194,6 +1196,7 @@
// If there is a shader, apply the local transformation to make sure
// the gradient is transformed to match the viewport
shaderDelegate.setLocalMatrix(mFinalPathMatrix.native_instance);
+ shaderDelegate.setAlpha(fullPath.mFillAlpha);
}
fillPaintDelegate.setShader(fullPath.mFillGradient);
@@ -1232,6 +1235,11 @@
strokePaintDelegate.setColorFilter(filterPtr);
final float finalStrokeScale = minScale * matrixScale;
strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
+ Shader_Delegate strokeShaderDelegate =
+ Shader_Delegate.getDelegate(fullPath.mStrokeGradient);
+ if (strokeShaderDelegate != null) {
+ strokeShaderDelegate.setAlpha(fullPath.mStrokeAlpha);
+ }
strokePaintDelegate.setShader(fullPath.mStrokeGradient);
BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
.getNativeInstance());
diff --git a/bridge/src/android/graphics/fonts/FontFamily_Builder_Delegate.java b/bridge/src/android/graphics/fonts/FontFamily_Builder_Delegate.java
new file mode 100644
index 0000000..06096ff
--- /dev/null
+++ b/bridge/src/android/graphics/fonts/FontFamily_Builder_Delegate.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2018 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.graphics.fonts;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.FontFamily_Delegate.FontInfo;
+import android.graphics.FontFamily_Delegate.FontVariant;
+import android.graphics.Paint;
+
+import java.awt.Font;
+import java.io.ByteArrayInputStream;
+import java.nio.ByteBuffer;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+import static android.graphics.FontFamily_Delegate.computeMatch;
+import static android.graphics.FontFamily_Delegate.deriveFont;
+
+/**
+ * Delegate implementing the native methods of android.graphics.fonts.FontFamily$Builder
+ * <p>
+ * Through the layoutlib_create tool, the original native methods of FontFamily$Builder have been
+ * replaced by calls to methods of the same name in this delegate class.
+ * <p>
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between it
+ * and the original FontFamily$Builder class.
+ *
+ * @see DelegateManager
+ */
+public class FontFamily_Builder_Delegate {
+ private static final DelegateManager<FontFamily_Builder_Delegate> sBuilderManager =
+ new DelegateManager<>(FontFamily_Builder_Delegate.class);
+
+ private static long sFontFamilyFinalizer = -1;
+
+ // Order does not really matter but we use a LinkedHashMap to get reproducible results across
+ // render calls
+ private Map<FontInfo, Font> mFonts = new LinkedHashMap<>();
+ /**
+ * The variant of the Font Family - compact or elegant.
+ * <p/>
+ * 0 is unspecified, 1 is compact and 2 is elegant. This needs to be kept in sync with values in
+ * android.graphics.FontFamily
+ *
+ * @see Paint#setElegantTextHeight(boolean)
+ */
+ private FontVariant mVariant;
+ private boolean mIsCustomFallback;
+
+ @LayoutlibDelegate
+ /*package*/ static long nInitBuilder() {
+ return sBuilderManager.addNewDelegate(new FontFamily_Builder_Delegate());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nAddFont(long builderPtr, long fontPtr) {
+ FontFamily_Builder_Delegate builder = sBuilderManager.getDelegate(builderPtr);
+ Font_Builder_Delegate font = Font_Builder_Delegate.sBuilderManager.getDelegate(fontPtr);
+ if (builder != null && font != null) {
+ builder.addFont(font.mBuffer, font.mTtcIndex, font.mWeight, font.mItalic);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nBuild(long builderPtr, String langTags, int variant,
+ boolean isCustomFallback) {
+ FontFamily_Builder_Delegate builder = sBuilderManager.getDelegate(builderPtr);
+ if (builder != null) {
+ assert variant < 3;
+ builder.mVariant = FontVariant.values()[variant];
+ builder.mIsCustomFallback = isCustomFallback;
+ }
+ return builderPtr;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nGetReleaseNativeFamily() {
+ synchronized (Font_Builder_Delegate.class) {
+ if (sFontFamilyFinalizer == -1) {
+ sFontFamilyFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sBuilderManager::removeJavaReferenceFor);
+ }
+ }
+ return sFontFamilyFinalizer;
+ }
+
+ public static FontFamily_Builder_Delegate getDelegate(long nativeFontFamily) {
+ return sBuilderManager.getDelegate(nativeFontFamily);
+ }
+
+ @Nullable
+ public Font getFont(int desiredWeight, boolean isItalic) {
+ FontInfo desiredStyle = new FontInfo();
+ desiredStyle.mWeight = desiredWeight;
+ desiredStyle.mIsItalic = isItalic;
+
+ Font cachedFont = mFonts.get(desiredStyle);
+ if (cachedFont != null) {
+ return cachedFont;
+ }
+
+ FontInfo bestFont = null;
+
+ if (mFonts.size() == 1) {
+ // No need to compute the match since we only have one candidate
+ bestFont = mFonts.keySet().iterator().next();
+ } else {
+ int bestMatch = Integer.MAX_VALUE;
+
+ for (FontInfo font : mFonts.keySet()) {
+ int match = computeMatch(font, desiredStyle);
+ if (match < bestMatch) {
+ bestMatch = match;
+ bestFont = font;
+ if (bestMatch == 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (bestFont == null) {
+ return null;
+ }
+
+
+ // Derive the font as required and add it to the list of Fonts.
+ deriveFont(bestFont, desiredStyle);
+ addFont(desiredStyle);
+ return desiredStyle.mFont;
+ }
+
+ public FontVariant getVariant() {
+ return mVariant;
+ }
+
+ // ---- private helper methods ----
+
+ private void addFont(final ByteBuffer buffer, int ttcIndex, int weight, boolean italic) {
+ addFont(buffer, weight, italic);
+ }
+
+ private void addFont(@NonNull ByteBuffer buffer, int weight, boolean italic) {
+ // Set valid to true, even if the font fails to load.
+ Font font = loadFont(buffer);
+ if (font == null) {
+ return;
+ }
+ FontInfo fontInfo = new FontInfo();
+ fontInfo.mFont = font;
+ fontInfo.mWeight = weight;
+ fontInfo.mIsItalic = italic;
+ addFont(fontInfo);
+ }
+
+ private void addFont(@NonNull FontInfo fontInfo) {
+ mFonts.putIfAbsent(fontInfo, fontInfo.mFont);
+ }
+
+ private static Font loadFont(@NonNull ByteBuffer buffer) {
+ try {
+ byte[] byteArray = new byte[buffer.limit()];
+ buffer.get(byteArray);
+ buffer.rewind();
+ return Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(byteArray));
+ } catch (Exception e) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, "Unable to load font",
+ e, null);
+ }
+
+ return null;
+ }
+}
diff --git a/bridge/src/android/graphics/fonts/Font_Builder_Delegate.java b/bridge/src/android/graphics/fonts/Font_Builder_Delegate.java
new file mode 100644
index 0000000..afa7dca
--- /dev/null
+++ b/bridge/src/android/graphics/fonts/Font_Builder_Delegate.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2018 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.graphics.fonts;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.content.res.AssetManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate implementing the native methods of android.graphics.fonts.Font$Builder
+ * <p>
+ * Through the layoutlib_create tool, the original native methods of Font$Builder have been
+ * replaced by calls to methods of the same name in this delegate class.
+ * <p>
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between it
+ * and the original Font$Builder class.
+ *
+ * @see DelegateManager
+ */
+public class Font_Builder_Delegate {
+ protected static final DelegateManager<Font_Builder_Delegate> sBuilderManager =
+ new DelegateManager<>(Font_Builder_Delegate.class);
+ private static final DelegateManager<String> sAssetManager =
+ new DelegateManager<>(String.class);
+ private static long sFontFinalizer = -1;
+ private static long sAssetFinalizer = -1;
+
+ protected ByteBuffer mBuffer;
+ protected int mWeight;
+ protected boolean mItalic;
+ protected int mTtcIndex;
+ protected String filePath;
+
+ @LayoutlibDelegate
+ /*package*/ static long nInitBuilder() {
+ return sBuilderManager.addNewDelegate(new Font_Builder_Delegate());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nGetNativeAsset(
+ @NonNull AssetManager am, @NonNull String path, boolean isAsset, int cookie) {
+ return sAssetManager.addNewDelegate(path);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static ByteBuffer nGetAssetBuffer(long nativeAsset) {
+ String fullPath = sAssetManager.getDelegate(nativeAsset);
+ if (fullPath == null) {
+ return null;
+ }
+ try {
+ byte[] byteArray = Files.readAllBytes(new File(fullPath).toPath());
+ return ByteBuffer.wrap(byteArray);
+ } catch (IOException e) {
+ Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET,
+ "Error mapping font file " + fullPath, null, null, null);
+ return null;
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nGetReleaseNativeAssetFunc() {
+ synchronized (Font_Builder_Delegate.class) {
+ if (sAssetFinalizer == -1) {
+ sAssetFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sAssetManager::removeJavaReferenceFor);
+ }
+ }
+ return sAssetFinalizer;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nAddAxis(long builderPtr, int tag, float value) {
+ Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+ "Font$Builder.nAddAxis is not supported.", null, null, null);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nBuild(long builderPtr, ByteBuffer buffer, String filePath, int weight,
+ boolean italic, int ttcIndex) {
+ Font_Builder_Delegate font = sBuilderManager.getDelegate(builderPtr);
+ if (font != null) {
+ font.mBuffer = buffer;
+ font.mWeight = weight;
+ font.mItalic = italic;
+ font.mTtcIndex = ttcIndex;
+ font.filePath = filePath;
+ }
+ return builderPtr;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nGetReleaseNativeFont() {
+ synchronized (Font_Builder_Delegate.class) {
+ if (sFontFinalizer == -1) {
+ sFontFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sBuilderManager::removeJavaReferenceFor);
+ }
+ }
+ return sFontFinalizer;
+ }
+}
diff --git a/bridge/src/android/graphics/fonts/SystemFonts_Delegate.java b/bridge/src/android/graphics/fonts/SystemFonts_Delegate.java
new file mode 100644
index 0000000..6937093
--- /dev/null
+++ b/bridge/src/android/graphics/fonts/SystemFonts_Delegate.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 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.graphics.fonts;
+
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.text.FontConfig;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+
+import static android.graphics.FontFamily_Delegate.getFontLocation;
+
+/**
+ * Delegate implementing the native methods of android.graphics.fonts.SystemFonts
+ * <p>
+ * Through the layoutlib_create tool, the original native methods of SystemFonts have been
+ * replaced by calls to methods of the same name in this delegate class.
+ * <p>
+ * This class behaves like the original native implementation, but in Java, keeping previously
+ * native data into its own objects and mapping them to int that are sent back and forth between it
+ * and the original SystemFonts class.
+ *
+ * @see DelegateManager
+ */
+public class SystemFonts_Delegate {
+
+ @LayoutlibDelegate
+ /*package*/ static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath,
+ @NonNull String fontDir,
+ @NonNull FontCustomizationParser.Result oemCustomization,
+ @NonNull ArrayMap<String, FontFamily[]> fallbackMap,
+ @NonNull ArrayList<Font> availableFonts) {
+ Bridge.sIsTypefaceInitialized = true;
+ return SystemFonts.buildSystemFallback_Original(getFontLocation() + "/fonts.xml",
+ getFontLocation() + "/", oemCustomization, fallbackMap, availableFonts);
+ }
+}
diff --git a/bridge/src/android/text/LineBreaker.java b/bridge/src/android/graphics/text/BaseLineBreaker.java
similarity index 60%
rename from bridge/src/android/text/LineBreaker.java
rename to bridge/src/android/graphics/text/BaseLineBreaker.java
index 06e9c84..61fa216 100644
--- a/bridge/src/android/text/LineBreaker.java
+++ b/bridge/src/android/graphics/text/BaseLineBreaker.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,30 +14,40 @@
* limitations under the License.
*/
-package android.text;
+package android.graphics.text;
import android.annotation.NonNull;
-import android.text.StaticLayout.LineBreaks;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// Based on the native implementation of LineBreaker in
// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
-public abstract class LineBreaker {
+public abstract class BaseLineBreaker {
protected static final int TAB_MASK = 0x20000000; // keep in sync with StaticLayout
protected final @NonNull List<Primitive> mPrimitives;
- protected final @NonNull LineWidth mLineWidth;
- protected final @NonNull TabStops mTabStops;
+ protected final @NonNull
+ LineWidth mLineWidth;
+ protected final @NonNull
+ TabStops mTabStops;
- public LineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
+ public BaseLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
@NonNull TabStops tabStops) {
mPrimitives = Collections.unmodifiableList(primitives);
mLineWidth = lineWidth;
mTabStops = tabStops;
}
- public abstract void computeBreaks(@NonNull LineBreaks breakInfo);
+ public abstract Result computeBreaks();
+
+ public static class Result {
+ List<Integer> mLineBreakOffset = new ArrayList<>();
+ List<Float> mLineWidths = new ArrayList<>();
+ List<Float> mLineAscents = new ArrayList<>();
+ List<Float> mLineDescents = new ArrayList<>();
+ List<Integer> mLineFlags = new ArrayList<>();
+ }
}
diff --git a/bridge/src/android/text/GreedyLineBreaker.java b/bridge/src/android/graphics/text/GreedyLineBreaker.java
similarity index 67%
rename from bridge/src/android/text/GreedyLineBreaker.java
rename to bridge/src/android/graphics/text/GreedyLineBreaker.java
index 50289e9..940e235 100644
--- a/bridge/src/android/text/GreedyLineBreaker.java
+++ b/bridge/src/android/graphics/text/GreedyLineBreaker.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,20 +14,18 @@
* limitations under the License.
*/
-package android.text;
+package android.graphics.text;
import android.annotation.NonNull;
-import android.text.Primitive.PrimitiveType;
-import android.text.StaticLayout.LineBreaks;
+import android.graphics.text.Primitive.PrimitiveType;
-import java.util.ArrayList;
import java.util.List;
-import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY;
+import static android.graphics.text.Primitive.PrimitiveType.PENALTY_INFINITY;
// Based on the native implementation of GreedyLineBreaker in
// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
-public class GreedyLineBreaker extends LineBreaker {
+public class GreedyLineBreaker extends BaseLineBreaker {
public GreedyLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
@NonNull TabStops tabStops) {
@@ -35,8 +33,7 @@
}
@Override
- public void computeBreaks(@NonNull LineBreaks lineBreaks) {
- BreakInfo breakInfo = new BreakInfo();
+ public Result computeBreaks() {
int lineNum = 0;
float width = 0, printedWidth = 0;
boolean breakFound = false, goodBreakFound = false;
@@ -47,6 +44,7 @@
float maxWidth = mLineWidth.getLineWidth(lineNum);
int numPrimitives = mPrimitives.size();
+ Result result = new Result();
// greedily fit as many characters as possible on each line
// loop over all primitives, and choose the best break point
// (if possible, a break point without splitting a word)
@@ -77,18 +75,22 @@
i = goodBreakIndex; // no +1 because of i++
lineNum++;
maxWidth = mLineWidth.getLineWidth(lineNum);
- breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location);
- breakInfo.mWidthsList.add(goodBreakWidth);
- breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex);
+ result.mLineBreakOffset.add(mPrimitives.get(goodBreakIndex).location);
+ result.mLineWidths.add(goodBreakWidth);
+ result.mLineAscents.add(0f);
+ result.mLineDescents.add(0f);
+ result.mLineFlags.add(firstTabIndex < goodBreakIndex ? TAB_MASK : 0);
firstTabIndex = Integer.MAX_VALUE;
} else {
// must split a word because there is no other option
i = breakIndex; // no +1 because of i++
lineNum++;
maxWidth = mLineWidth.getLineWidth(lineNum);
- breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location);
- breakInfo.mWidthsList.add(breakWidth);
- breakInfo.mFlagsList.add(firstTabIndex < breakIndex);
+ result.mLineBreakOffset.add(mPrimitives.get(breakIndex).location);
+ result.mLineWidths.add(breakWidth);
+ result.mLineAscents.add(0f);
+ result.mLineDescents.add(0f);
+ result.mLineFlags.add(firstTabIndex < breakIndex ? TAB_MASK : 0);
firstTabIndex = Integer.MAX_VALUE;
}
printedWidth = width = 0;
@@ -111,9 +113,11 @@
if (p.penalty == -PENALTY_INFINITY) {
lineNum++;
maxWidth = mLineWidth.getLineWidth(lineNum);
- breakInfo.mBreaksList.add(p.location);
- breakInfo.mWidthsList.add(printedWidth);
- breakInfo.mFlagsList.add(firstTabIndex < i);
+ result.mLineBreakOffset.add(p.location);
+ result.mLineWidths.add(printedWidth);
+ result.mLineAscents.add(0f);
+ result.mLineDescents.add(0f);
+ result.mLineFlags.add(firstTabIndex < i ? TAB_MASK : 0);
firstTabIndex = Integer.MAX_VALUE;
printedWidth = width = 0;
goodBreakFound = breakFound = false;
@@ -144,49 +148,19 @@
if (breakFound || goodBreakFound) {
// output last break if there are more characters to output
if (goodBreakFound) {
- breakInfo.mBreaksList.add(mPrimitives.get(goodBreakIndex).location);
- breakInfo.mWidthsList.add(goodBreakWidth);
- breakInfo.mFlagsList.add(firstTabIndex < goodBreakIndex);
+ result.mLineBreakOffset.add(mPrimitives.get(goodBreakIndex).location);
+ result.mLineWidths.add(goodBreakWidth);
+ result.mLineAscents.add(0f);
+ result.mLineDescents.add(0f);
+ result.mLineFlags.add(firstTabIndex < goodBreakIndex ? TAB_MASK : 0);
} else {
- breakInfo.mBreaksList.add(mPrimitives.get(breakIndex).location);
- breakInfo.mWidthsList.add(breakWidth);
- breakInfo.mFlagsList.add(firstTabIndex < breakIndex);
+ result.mLineBreakOffset.add(mPrimitives.get(breakIndex).location);
+ result.mLineWidths.add(breakWidth);
+ result.mLineAscents.add(0f);
+ result.mLineDescents.add(0f);
+ result.mLineFlags.add(firstTabIndex < breakIndex ? TAB_MASK : 0);
}
}
- breakInfo.copyTo(lineBreaks);
- }
-
- private static class BreakInfo {
- List<Integer> mBreaksList = new ArrayList<Integer>();
- List<Float> mWidthsList = new ArrayList<Float>();
- List<Boolean> mFlagsList = new ArrayList<Boolean>();
-
- public void copyTo(LineBreaks lineBreaks) {
- if (lineBreaks.breaks.length != mBreaksList.size()) {
- lineBreaks.breaks = new int[mBreaksList.size()];
- lineBreaks.widths = new float[mWidthsList.size()];
- lineBreaks.flags = new int[mFlagsList.size()];
- }
-
- int i = 0;
- for (int b : mBreaksList) {
- lineBreaks.breaks[i] = b;
- i++;
- }
- i = 0;
- for (float b : mWidthsList) {
- lineBreaks.widths[i] = b;
- i++;
- }
- i = 0;
- for (boolean b : mFlagsList) {
- lineBreaks.flags[i] = b ? TAB_MASK : 0;
- i++;
- }
-
- mBreaksList = null;
- mWidthsList = null;
- mFlagsList = null;
- }
+ return result;
}
}
diff --git a/bridge/src/android/text/StaticLayout_Delegate.java b/bridge/src/android/graphics/text/LineBreaker_Delegate.java
similarity index 61%
rename from bridge/src/android/text/StaticLayout_Delegate.java
rename to bridge/src/android/graphics/text/LineBreaker_Delegate.java
index d7cb596..92c99cc 100644
--- a/bridge/src/android/text/StaticLayout_Delegate.java
+++ b/bridge/src/android/graphics/text/LineBreaker_Delegate.java
@@ -1,4 +1,20 @@
-package android.text;
+/*
+ * Copyright (C) 2018 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.graphics.text;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
@@ -6,16 +22,17 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.icu.text.BreakIterator;
+import android.text.Layout;
import android.text.Layout.BreakStrategy;
import android.text.Layout.HyphenationFrequency;
-import android.text.Primitive.PrimitiveType;
-import android.text.StaticLayout.LineBreaks;
+import android.graphics.text.Primitive.PrimitiveType;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.List;
import javax.swing.text.Segment;
+import libcore.util.NativeAllocationRegistry_Delegate;
/**
* Delegate that provides implementation for native methods in {@link android.text.StaticLayout}
@@ -24,7 +41,7 @@
* by calls to methods of the same name in this delegate class.
*
*/
-public class StaticLayout_Delegate {
+public class LineBreaker_Delegate {
private static final char CHAR_SPACE = 0x20;
private static final char CHAR_TAB = 0x09;
@@ -33,28 +50,38 @@
// ---- Builder delegate manager ----
private static final DelegateManager<Builder> sBuilderManager =
- new DelegateManager<Builder>(Builder.class);
+ new DelegateManager<>(Builder.class);
+ private static long sFinalizer = -1;
+
+ // ---- Result delegate manager ----
+ private static final DelegateManager<Result> sResultManager =
+ new DelegateManager<>(Result.class);
+ private static long sResultFinalizer = -1;
@LayoutlibDelegate
/*package*/ static long nInit(
@BreakStrategy int breakStrategy,
@HyphenationFrequency int hyphenationFrequency,
boolean isJustified,
- @Nullable int[] indents,
- @Nullable int[] leftPaddings,
- @Nullable int[] rightPaddings) {
+ @Nullable int[] indents) {
Builder builder = new Builder();
builder.mBreakStrategy = breakStrategy;
return sBuilderManager.addNewDelegate(builder);
}
@LayoutlibDelegate
- /*package*/ static void nFinish(long nativePtr) {
- sBuilderManager.removeJavaReferenceFor(nativePtr);
+ /*package*/ static long nGetReleaseFunc() {
+ synchronized (MeasuredText_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sBuilderManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
}
@LayoutlibDelegate
- /*package*/ static int nComputeLineBreaks(
+ /*package*/ static long nComputeLineBreaks(
/* non zero */ long nativePtr,
// Inputs
@@ -64,19 +91,9 @@
float firstWidth,
int firstWidthLineCount,
float restWidth,
- @Nullable int[] variableTabStops,
- int defaultTabStop,
- int indentsOffset,
-
- // Outputs
- @NonNull LineBreaks recycle,
- int recycleLength,
- @NonNull int[] recycleBreaks,
- @NonNull float[] recycleWidths,
- @NonNull float[] recycleAscents,
- @NonNull float[] recycleDescents,
- @NonNull int[] recycleFlags,
- @NonNull float[] charWidths) {
+ @Nullable float[] variableTabStops,
+ float defaultTabStop,
+ int indentsOffset) {
Builder builder = sBuilderManager.getDelegate(nativePtr);
if (builder == null) {
return 0;
@@ -87,7 +104,7 @@
builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
- MeasuredParagraph_Delegate.computeRuns(measuredTextPtr, builder);
+ MeasuredText_Delegate.computeRuns(measuredTextPtr, builder);
// compute all possible breakpoints.
BreakIterator it = BreakIterator.getLineInstance();
@@ -120,9 +137,55 @@
builder.mLineBreaker = new GreedyLineBreaker(primitives, builder.mLineWidth,
builder.mTabStopCalculator);
}
- builder.mLineBreaker.computeBreaks(recycle);
- System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length);
- return recycle.breaks.length;
+ Result result = new Result(builder.mLineBreaker.computeBreaks());
+ return sResultManager.addNewDelegate(result);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nGetLineCount(long ptr) {
+ Result result = sResultManager.getDelegate(ptr);
+ return result.mResult.mLineBreakOffset.size();
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nGetLineBreakOffset(long ptr, int idx) {
+ Result result = sResultManager.getDelegate(ptr);
+ return result.mResult.mLineBreakOffset.get(idx);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetLineWidth(long ptr, int idx) {
+ Result result = sResultManager.getDelegate(ptr);
+ return result.mResult.mLineWidths.get(idx);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetLineAscent(long ptr, int idx) {
+ Result result = sResultManager.getDelegate(ptr);
+ return result.mResult.mLineAscents.get(idx);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetLineDescent(long ptr, int idx) {
+ Result result = sResultManager.getDelegate(ptr);
+ return result.mResult.mLineDescents.get(idx);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nGetLineFlag(long ptr, int idx) {
+ Result result = sResultManager.getDelegate(ptr);
+ return result.mResult.mLineFlags.get(idx);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nGetReleaseResultFunc() {
+ synchronized (MeasuredText_Delegate.class) {
+ if (sResultFinalizer == -1) {
+ sResultFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sBuilderManager::removeJavaReferenceFor);
+ }
+ }
+ return sResultFinalizer;
}
/**
@@ -173,7 +236,7 @@
public static class Builder {
char[] mText;
float[] mWidths;
- private LineBreaker mLineBreaker;
+ private BaseLineBreaker mLineBreaker;
private int mBreakStrategy;
private LineWidth mLineWidth;
private TabStops mTabStopCalculator;
@@ -190,4 +253,11 @@
abstract void addTo(Builder builder);
}
+
+ public static class Result {
+ final BaseLineBreaker.Result mResult;
+ public Result(BaseLineBreaker.Result result) {
+ mResult = result;
+ }
+ }
}
diff --git a/bridge/src/android/text/LineWidth.java b/bridge/src/android/graphics/text/LineWidth.java
similarity index 92%
rename from bridge/src/android/text/LineWidth.java
rename to bridge/src/android/graphics/text/LineWidth.java
index 2ea886d..809b967 100644
--- a/bridge/src/android/text/LineWidth.java
+++ b/bridge/src/android/graphics/text/LineWidth.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.text;
+package android.graphics.text;
// Based on the native implementation of LineWidth in
// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
diff --git a/bridge/src/android/text/MeasuredParagraph_Delegate.java b/bridge/src/android/graphics/text/MeasuredText_Builder_Delegate.java
similarity index 60%
rename from bridge/src/android/text/MeasuredParagraph_Delegate.java
rename to bridge/src/android/graphics/text/MeasuredText_Builder_Delegate.java
index 4694890..f9abbe1 100644
--- a/bridge/src/android/text/MeasuredParagraph_Delegate.java
+++ b/bridge/src/android/graphics/text/MeasuredText_Builder_Delegate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.text;
+package android.graphics.text;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
@@ -24,41 +24,37 @@
import android.graphics.Paint;
import android.graphics.Paint_Delegate;
import android.graphics.RectF;
-import android.text.StaticLayout_Delegate.Builder;
-import android.text.StaticLayout_Delegate.Run;
+import android.graphics.text.LineBreaker_Delegate.Builder;
+import android.graphics.text.LineBreaker_Delegate.Run;
import java.util.ArrayList;
import java.util.Arrays;
-import libcore.util.NativeAllocationRegistry_Delegate;
-
/**
- * Delegate that provides implementation for native methods in {@link android.text.MeasuredParagraph}
+ * Delegate that provides implementation for native methods in
+ * {@link android.graphics.text.MeasuredText}
* <p/>
* Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
* by calls to methods of the same name in this delegate class.
*
*/
-public class MeasuredParagraph_Delegate {
-
+public class MeasuredText_Builder_Delegate {
// ---- Builder delegate manager ----
- private static final DelegateManager<MeasuredParagraphBuilder> sBuilderManager =
- new DelegateManager<>(MeasuredParagraphBuilder.class);
- private static final DelegateManager<MeasuredParagraph_Delegate> sManager =
- new DelegateManager<>(MeasuredParagraph_Delegate.class);
- private static long sFinalizer = -1;
+ protected static final DelegateManager<MeasuredText_Builder_Delegate>
+ sBuilderManager =
+ new DelegateManager<>(MeasuredText_Builder_Delegate.class);
- private long mNativeBuilderPtr;
+ protected final ArrayList<Run> mRuns = new ArrayList<>();
@LayoutlibDelegate
/*package*/ static long nInitBuilder() {
- return sBuilderManager.addNewDelegate(new MeasuredParagraphBuilder());
+ return sBuilderManager.addNewDelegate(new MeasuredText_Builder_Delegate());
}
/**
* Apply style to make native measured text.
*
- * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+ * @param nativeBuilderPtr The native NativeMeasuredParagraph builder pointer.
* @param paintPtr The native paint pointer to be applied.
* @param start The start offset in the copied buffer.
* @param end The end offset in the copied buffer.
@@ -67,7 +63,7 @@
@LayoutlibDelegate
/*package*/ static void nAddStyleRun(long nativeBuilderPtr, long paintPtr, int start,
int end, boolean isRtl) {
- MeasuredParagraphBuilder builder = sBuilderManager.getDelegate(nativeBuilderPtr);
+ MeasuredText_Builder_Delegate builder = sBuilderManager.getDelegate(nativeBuilderPtr);
if (builder == null) {
return;
}
@@ -77,7 +73,7 @@
/**
* Apply ReplacementRun to make native measured text.
*
- * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+ * @param nativeBuilderPtr The native NativeMeasuredParagraph builder pointer.
* @param paintPtr The native paint pointer to be applied.
* @param start The start offset in the copied buffer.
* @param end The end offset in the copied buffer.
@@ -86,7 +82,7 @@
@LayoutlibDelegate
/*package*/ static void nAddReplacementRun(long nativeBuilderPtr, long paintPtr, int start,
int end, float width) {
- MeasuredParagraphBuilder builder = sBuilderManager.getDelegate(nativeBuilderPtr);
+ MeasuredText_Builder_Delegate builder = sBuilderManager.getDelegate(nativeBuilderPtr);
if (builder == null) {
return;
}
@@ -94,11 +90,11 @@
}
@LayoutlibDelegate
- /*package*/ static long nBuildNativeMeasuredParagraph(long nativeBuilderPtr,
+ /*package*/ static long nBuildMeasuredText(long nativeBuilderPtr, long hintMtPtr,
@NonNull char[] text, boolean computeHyphenation, boolean computeLayout) {
- MeasuredParagraph_Delegate delegate = new MeasuredParagraph_Delegate();
+ MeasuredText_Delegate delegate = new MeasuredText_Delegate();
delegate.mNativeBuilderPtr = nativeBuilderPtr;
- return sManager.addNewDelegate(delegate);
+ return MeasuredText_Delegate.sManager.addNewDelegate(delegate);
}
@LayoutlibDelegate
@@ -106,29 +102,6 @@
sBuilderManager.removeJavaReferenceFor(nativeBuilderPtr);
}
- @LayoutlibDelegate
- /*package*/ static float nGetWidth(long nativePtr, int start, int end) {
- // Ignore as it is not used for the layoutlib implementation
- return 0.0f;
- }
-
- @LayoutlibDelegate
- /*package*/ static long nGetReleaseFunc() {
- synchronized (MeasuredParagraph_Delegate.class) {
- if (sFinalizer == -1) {
- sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
- sManager::removeJavaReferenceFor);
- }
- }
- return sFinalizer;
- }
-
- @LayoutlibDelegate
- /*package*/ static int nGetMemoryUsage(long nativePtr) {
- // Ignore as it is not used for the layoutlib implementation
- return 0;
- }
-
private static float measureText(long nativePaint, char[] text, int index, int count,
float[] widths, int bidiFlags) {
Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
@@ -138,20 +111,6 @@
return bounds.right - bounds.left;
}
- public static void computeRuns(long measuredTextPtr, Builder staticLayoutBuilder) {
- MeasuredParagraph_Delegate delegate = sManager.getDelegate(measuredTextPtr);
- if (delegate == null) {
- return;
- }
- MeasuredParagraphBuilder builder = sBuilderManager.getDelegate(delegate.mNativeBuilderPtr);
- if (builder == null) {
- return;
- }
- for (Run run: builder.mRuns) {
- run.addTo(staticLayoutBuilder);
- }
- }
-
private static class StyleRun extends Run {
private final long mNativePaint;
private final boolean mIsRtl;
@@ -184,8 +143,4 @@
Arrays.fill(builder.mWidths, mStart + 1, mEnd, 0.0f);
}
}
-
- private static class MeasuredParagraphBuilder {
- private final ArrayList<Run> mRuns = new ArrayList<>();
- }
}
diff --git a/bridge/src/android/graphics/text/MeasuredText_Delegate.java b/bridge/src/android/graphics/text/MeasuredText_Delegate.java
new file mode 100644
index 0000000..02e2845
--- /dev/null
+++ b/bridge/src/android/graphics/text/MeasuredText_Delegate.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.graphics.text;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.graphics.Rect;
+import android.graphics.text.LineBreaker_Delegate.Builder;
+import android.graphics.text.LineBreaker_Delegate.Run;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate that provides implementation for native methods in
+ * {@link android.graphics.text.MeasuredText}
+ * <p/>
+ * Through the layoutlib_create tool, selected methods of StaticLayout have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class MeasuredText_Delegate {
+
+ // ---- Builder delegate manager ----
+ protected static final DelegateManager<MeasuredText_Delegate> sManager =
+ new DelegateManager<>(MeasuredText_Delegate.class);
+ private static long sFinalizer = -1;
+
+ protected long mNativeBuilderPtr;
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetWidth(long nativePtr, int start, int end) {
+ // Ignore as it is not used for the layoutlib implementation
+ return 0.0f;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nGetReleaseFunc() {
+ synchronized (MeasuredText_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static int nGetMemoryUsage(long nativePtr) {
+ // Ignore as it is not used for the layoutlib implementation
+ return 0;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nGetBounds(long nativePtr, char[] buf, int start, int end, Rect rect) {
+ // Ignore as it is not used for the layoutlib implementation
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static float nGetCharWidthAt(long nativePtr, int offset) {
+ // Ignore as it is not used for the layoutlib implementation
+ return 0.0f;
+ }
+
+ public static void computeRuns(long measuredTextPtr, Builder staticLayoutBuilder) {
+ MeasuredText_Delegate delegate = sManager.getDelegate(measuredTextPtr);
+ if (delegate == null) {
+ return;
+ }
+ MeasuredText_Builder_Delegate builder =
+ MeasuredText_Builder_Delegate.sBuilderManager.getDelegate(delegate.mNativeBuilderPtr);
+ if (builder == null) {
+ return;
+ }
+ for (Run run: builder.mRuns) {
+ run.addTo(staticLayoutBuilder);
+ }
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/android/text/OptimizingLineBreaker.java b/bridge/src/android/graphics/text/OptimizingLineBreaker.java
similarity index 84%
rename from bridge/src/android/text/OptimizingLineBreaker.java
rename to bridge/src/android/graphics/text/OptimizingLineBreaker.java
index ed8e33a..95e0920 100644
--- a/bridge/src/android/text/OptimizingLineBreaker.java
+++ b/bridge/src/android/graphics/text/OptimizingLineBreaker.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,17 +14,17 @@
* limitations under the License.
*/
-package android.text;
+package android.graphics.text;
import android.annotation.NonNull;
-import android.text.Primitive.PrimitiveType;
-import android.text.StaticLayout.LineBreaks;
+import android.graphics.text.Primitive.PrimitiveType;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
-import static android.text.Primitive.PrimitiveType.PENALTY_INFINITY;
+import static android.graphics.text.Primitive.PrimitiveType.PENALTY_INFINITY;
// Based on the native implementation of OptimizingLineBreaker in
@@ -33,7 +33,7 @@
* A more complex version of line breaking where we try to prevent the right edge from being too
* jagged.
*/
-public class OptimizingLineBreaker extends LineBreaker {
+public class OptimizingLineBreaker extends BaseLineBreaker {
public OptimizingLineBreaker(@NonNull List<Primitive> primitives, @NonNull LineWidth lineWidth,
@NonNull TabStops tabStops) {
@@ -41,17 +41,20 @@
}
@Override
- public void computeBreaks(@NonNull LineBreaks breakInfo) {
+ public Result computeBreaks() {
+ Result result = new Result();
int numBreaks = mPrimitives.size();
assert numBreaks > 0;
if (numBreaks == 1) {
// This can be true only if it's an empty paragraph.
Primitive p = mPrimitives.get(0);
assert p.type == PrimitiveType.PENALTY;
- breakInfo.breaks = new int[]{0};
- breakInfo.widths = new float[]{p.width};
- breakInfo.flags = new int[]{0};
- return;
+ result.mLineBreakOffset.add(0);
+ result.mLineWidths.add(p.width);
+ result.mLineAscents.add(0f);
+ result.mLineDescents.add(0f);
+ result.mLineFlags.add(0);
+ return result;
}
Node[] opt = new Node[numBreaks];
opt[0] = new Node(-1, 0, 0, 0, false);
@@ -120,35 +123,21 @@
}
int idx = numBreaks - 1;
- int count = opt[idx].mPrevCount;
- resize(breakInfo, count);
while (opt[idx].mPrev != -1) {
- count--;
- assert count >=0;
-
- breakInfo.breaks[count] = mPrimitives.get(idx).location;
- breakInfo.widths[count] = opt[idx].mWidth;
- breakInfo.flags [count] = opt[idx].mHasTabs ? TAB_MASK : 0;
+ result.mLineBreakOffset.add(mPrimitives.get(idx).location);
+ result.mLineWidths.add(opt[idx].mWidth);
+ result.mLineAscents.add(0f);
+ result.mLineDescents.add(0f);
+ result.mLineFlags.add(opt[idx].mHasTabs ? TAB_MASK : 0);
idx = opt[idx].mPrev;
}
- }
- private static void resize(LineBreaks lineBreaks, int size) {
- if (lineBreaks.breaks.length == size) {
- return;
- }
- int[] breaks = new int[size];
- float[] widths = new float[size];
- int[] flags = new int[size];
-
- int toCopy = Math.min(size, lineBreaks.breaks.length);
- System.arraycopy(lineBreaks.breaks, 0, breaks, 0, toCopy);
- System.arraycopy(lineBreaks.widths, 0, widths, 0, toCopy);
- System.arraycopy(lineBreaks.flags, 0, flags, 0, toCopy);
-
- lineBreaks.breaks = breaks;
- lineBreaks.widths = widths;
- lineBreaks.flags = flags;
+ Collections.reverse(result.mLineBreakOffset);
+ Collections.reverse(result.mLineWidths);
+ Collections.reverse(result.mLineAscents);
+ Collections.reverse(result.mLineDescents);
+ Collections.reverse(result.mLineFlags);
+ return result;
}
@NonNull
diff --git a/bridge/src/android/text/Primitive.java b/bridge/src/android/graphics/text/Primitive.java
similarity index 96%
rename from bridge/src/android/text/Primitive.java
rename to bridge/src/android/graphics/text/Primitive.java
index 37ed072..b8157ec 100644
--- a/bridge/src/android/text/Primitive.java
+++ b/bridge/src/android/graphics/text/Primitive.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.text;
+package android.graphics.text;
import android.annotation.NonNull;
diff --git a/bridge/src/android/text/TabStops.java b/bridge/src/android/graphics/text/TabStops.java
similarity index 72%
rename from bridge/src/android/text/TabStops.java
rename to bridge/src/android/graphics/text/TabStops.java
index 6c2f1e1..6ede24c 100644
--- a/bridge/src/android/text/TabStops.java
+++ b/bridge/src/android/graphics/text/TabStops.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.text;
+package android.graphics.text;
import android.annotation.Nullable;
@@ -22,23 +22,23 @@
// frameworks/base/core/jni/android_text_StaticLayout.cpp revision b808260
public class TabStops {
@Nullable
- private int[] mStops;
- private final int mTabWidth;
+ private float[] mStops;
+ private final float mTabWidth;
- public TabStops(@Nullable int[] stops, int defaultTabWidth) {
+ public TabStops(@Nullable float[] stops, float defaultTabWidth) {
mTabWidth = defaultTabWidth;
mStops = stops;
}
public float width(float widthSoFar) {
if (mStops != null) {
- for (int i : mStops) {
- if (i > widthSoFar) {
- return i;
+ for (float f : mStops) {
+ if (f > widthSoFar) {
+ return f;
}
}
}
// find the next tabStop after widthSoFar.
- return (int) ((widthSoFar + mTabWidth) / mTabWidth) * mTabWidth;
+ return ((widthSoFar + mTabWidth) / mTabWidth) * mTabWidth;
}
}
diff --git a/bridge/src/android/provider/DeviceConfig_Delegate.java b/bridge/src/android/provider/DeviceConfig_Delegate.java
new file mode 100644
index 0000000..daf86dc
--- /dev/null
+++ b/bridge/src/android/provider/DeviceConfig_Delegate.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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.provider;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate that provides alternative implementation for methods in {@link DeviceConfig}
+ * <p/>
+ * Through the layoutlib_create tool, selected methods of DeviceConfig have been replaced by
+ * calls to methods of the same name in this delegate class.
+ */
+public class DeviceConfig_Delegate {
+
+ @LayoutlibDelegate
+ public static String getString(String namespace, String name, String defaultValue) {
+ return defaultValue;
+ }
+
+ @LayoutlibDelegate
+ public static boolean getBoolean(String namespace, String name, boolean defaultValue) {
+ return defaultValue;
+ }
+
+ @LayoutlibDelegate
+ public static int getInt(String namespace, String name, int defaultValue) {
+ return defaultValue;
+ }
+
+ @LayoutlibDelegate
+ public static long getLong(String namespace, String name, long defaultValue) {
+ return defaultValue;
+ }
+
+ @LayoutlibDelegate
+ public static float getFloat(String namespace, String name, float defaultValue) {
+ return defaultValue;
+ }
+}
diff --git a/bridge/src/android/util/BridgeXmlPullAttributes.java b/bridge/src/android/util/BridgeXmlPullAttributes.java
index f1af1d1..2f4b0ca 100644
--- a/bridge/src/android/util/BridgeXmlPullAttributes.java
+++ b/bridge/src/android/util/BridgeXmlPullAttributes.java
@@ -13,22 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.util;
import com.android.ide.common.rendering.api.AttrResourceValue;
-import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.internal.util.XmlUtils;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
+import com.android.layoutlib.bridge.android.XmlPullParserResolver;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;
import org.xmlpull.v1.XmlPullParser;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import java.util.Map;
import java.util.function.Function;
@@ -36,33 +39,48 @@
/**
* A correct implementation of the {@link AttributeSet} interface on top of a XmlPullParser
*/
-public class BridgeXmlPullAttributes extends XmlPullAttributes {
+public class BridgeXmlPullAttributes extends XmlPullAttributes implements ResolvingAttributeSet {
- private final BridgeContext mContext;
- private final boolean mPlatformFile;
- private final Function<String, Map<String, Integer>> mFrameworkEnumValueSupplier;
- private final Function<String, Map<String, Integer>> mProjectEnumValueSupplier;
-
- // VisibleForTesting
- BridgeXmlPullAttributes(@NonNull XmlPullParser parser, @NonNull BridgeContext context,
- boolean platformFile,
- @NonNull Function<String, Map<String, Integer>> frameworkEnumValueSupplier,
- @NonNull Function<String, Map<String, Integer>> projectEnumValueSupplier) {
- super(parser);
- mContext = context;
- mPlatformFile = platformFile;
- mFrameworkEnumValueSupplier = frameworkEnumValueSupplier;
- mProjectEnumValueSupplier = projectEnumValueSupplier;
+ interface EnumValueSupplier {
+ @Nullable
+ Map<String, Integer> getEnumValues(
+ @NonNull ResourceNamespace namespace, @NonNull String attrName);
}
- public BridgeXmlPullAttributes(@NonNull XmlPullParser parser, @NonNull BridgeContext context,
- boolean platformFile) {
- this(parser, context, platformFile, Bridge::getEnumValues, attrName -> {
- // get the styleable matching the resolved name
- RenderResources res = context.getRenderResources();
- ResourceValue attr = res.getProjectResource(ResourceType.ATTR, attrName);
- return attr instanceof AttrResourceValue ?
- ((AttrResourceValue) attr).getAttributeValues() : null;
+ private final BridgeContext mContext;
+ private final ResourceNamespace mXmlFileResourceNamespace;
+ private final ResourceNamespace.Resolver mResourceNamespaceResolver;
+ private final Function<String, Map<String, Integer>> mFrameworkEnumValueSupplier;
+ private final EnumValueSupplier mProjectEnumValueSupplier;
+
+ // VisibleForTesting
+ BridgeXmlPullAttributes(
+ @NonNull XmlPullParser parser,
+ @NonNull BridgeContext context,
+ @NonNull ResourceNamespace xmlFileResourceNamespace,
+ @NonNull Function<String, Map<String, Integer>> frameworkEnumValueSupplier,
+ @NonNull EnumValueSupplier projectEnumValueSupplier) {
+ super(parser);
+ mContext = context;
+ mFrameworkEnumValueSupplier = frameworkEnumValueSupplier;
+ mProjectEnumValueSupplier = projectEnumValueSupplier;
+ mXmlFileResourceNamespace = xmlFileResourceNamespace;
+ mResourceNamespaceResolver =
+ new XmlPullParserResolver(
+ mParser, context.getLayoutlibCallback().getImplicitNamespaces());
+ }
+
+ public BridgeXmlPullAttributes(
+ @NonNull XmlPullParser parser,
+ @NonNull BridgeContext context,
+ @NonNull ResourceNamespace xmlFileResourceNamespace) {
+ this(parser, context, xmlFileResourceNamespace, Bridge::getEnumValues, (ns, attrName) -> {
+ ResourceValue attr =
+ context.getRenderResources().getUnresolvedResource(
+ ResourceReference.attr(ns, attrName));
+ return attr instanceof AttrResourceValue
+ ? ((AttrResourceValue) attr).getAttributeValues()
+ : null;
});
}
@@ -84,15 +102,16 @@
if (BridgeConstants.NS_RESOURCES.equals(ns)) {
return Bridge.getResourceId(ResourceType.ATTR, name);
-
}
// this is not an attribute in the android namespace, we query the customviewloader, if
// the namespaces match.
if (mContext.getLayoutlibCallback().getNamespace().equals(ns)) {
- Integer v = mContext.getLayoutlibCallback().getResourceId(ResourceType.ATTR, name);
- if (v != null) {
- return v;
+ // TODO(namespaces): cache the namespace objects.
+ ResourceNamespace namespace = ResourceNamespace.fromNamespaceUri(ns);
+ if (namespace != null) {
+ return mContext.getLayoutlibCallback().getOrGenerateResourceId(
+ ResourceReference.attr(namespace, name));
}
}
@@ -161,9 +180,15 @@
return XmlUtils.convertValueToInt(value, defaultValue);
} catch (NumberFormatException e) {
// This is probably an enum
- Map<String, Integer> enumValues = BridgeConstants.NS_RESOURCES.equals(namespace) ?
- mFrameworkEnumValueSupplier.apply(attribute) :
- mProjectEnumValueSupplier.apply(attribute);
+ Map<String, Integer> enumValues = null;
+ if (BridgeConstants.NS_RESOURCES.equals(namespace)) {
+ enumValues = mFrameworkEnumValueSupplier.apply(attribute);
+ } else {
+ ResourceNamespace attrNamespace = ResourceNamespace.fromNamespaceUri(namespace);
+ if (attrNamespace != null) {
+ enumValues = mProjectEnumValueSupplier.getEnumValues(attrNamespace, attribute);
+ }
+ }
Integer enumValue = enumValues != null ? enumValues.get(value) : null;
if (enumValue != null) {
@@ -241,9 +266,8 @@
@Override
public int getAttributeIntValue(int index, int defaultValue) {
- return getAttributeIntValue(mParser.getAttributeNamespace(index),
- getAttributeName(index)
- , defaultValue);
+ return getAttributeIntValue(
+ mParser.getAttributeNamespace(index), getAttributeName(index), defaultValue);
}
@Override
@@ -278,6 +302,14 @@
return defaultValue;
}
+ @Override
+ @Nullable
+ public ResourceValue getResolvedAttributeValue(@Nullable String namespace,
+ @NonNull String name) {
+ String s = getAttributeValue(namespace, name);
+ return s == null ? null : getResourceValue(s);
+ }
+
// -- private helper methods
/**
@@ -285,8 +317,9 @@
*/
private ResourceValue getResourceValue(String value) {
// now look for this particular value
- RenderResources resources = mContext.getRenderResources();
- return resources.resolveResValue(resources.findResValue(value, mPlatformFile));
+ return mContext.getRenderResources().resolveResValue(
+ new UnresolvedResourceValue(
+ value, mXmlFileResourceNamespace, mResourceNamespaceResolver));
}
/**
@@ -295,17 +328,9 @@
private int resolveResourceValue(String value, int defaultValue) {
ResourceValue resource = getResourceValue(value);
if (resource != null) {
- Integer id;
- if (mPlatformFile || resource.isFramework()) {
- id = Bridge.getResourceId(resource.getResourceType(), resource.getName());
- } else {
- id = mContext.getLayoutlibCallback().getResourceId(
- resource.getResourceType(), resource.getName());
- }
-
- if (id != null) {
- return id;
- }
+ return resource.isFramework() ?
+ Bridge.getResourceId(resource.getResourceType(), resource.getName()) :
+ mContext.getLayoutlibCallback().getOrGenerateResourceId(resource.asReference());
}
return defaultValue;
diff --git a/bridge/src/android/util/ResolvingAttributeSet.java b/bridge/src/android/util/ResolvingAttributeSet.java
new file mode 100644
index 0000000..6efd812
--- /dev/null
+++ b/bridge/src/android/util/ResolvingAttributeSet.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 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.util;
+
+import com.android.ide.common.rendering.api.ResourceValue;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+public interface ResolvingAttributeSet extends AttributeSet {
+ /**
+ * Returns the resolved value of the attribute with the given name and namespace
+ *
+ * @param namespace the namespace of the attribute
+ * @param name the name of the attribute
+ * @return the resolved value of the attribute, or null if the attribute does not exist
+ */
+ @Nullable
+ ResourceValue getResolvedAttributeValue(@Nullable String namespace, @NonNull String name);
+}
diff --git a/bridge/src/android/util/Xml_Delegate.java b/bridge/src/android/util/Xml_Delegate.java
index 213e848..e309dc6 100644
--- a/bridge/src/android/util/Xml_Delegate.java
+++ b/bridge/src/android/util/Xml_Delegate.java
@@ -33,11 +33,10 @@
* around to map int to instance of the delegate.
*/
public class Xml_Delegate {
-
@LayoutlibDelegate
/*package*/ static XmlPullParser newPullParser() {
try {
- return ParserFactory.instantiateParser(null);
+ return ParserFactory.create();
} catch (XmlPullParserException e) {
throw new AssertionError();
}
diff --git a/bridge/src/android/util/imagepool/Bucket.java b/bridge/src/android/util/imagepool/Bucket.java
new file mode 100644
index 0000000..c562243
--- /dev/null
+++ b/bridge/src/android/util/imagepool/Bucket.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import com.android.tools.layoutlib.annotations.Nullable;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+
+import android.util.imagepool.ImagePool.Image.Orientation;
+
+import java.awt.image.BufferedImage;
+import java.lang.ref.SoftReference;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Data model for image pool. Bucket contains the list of same sized buffered image in soft ref.
+ */
+/* private package */ class Bucket {
+
+ @VisibleForTesting final Queue<SoftReference<BufferedImage>> mBufferedImageRef = new LinkedList<>();
+
+ public boolean isEmpty() {
+ return mBufferedImageRef.isEmpty();
+ }
+
+ @Nullable
+ public BufferedImage remove() {
+ if (mBufferedImageRef.isEmpty()) {
+ return null;
+ }
+
+ SoftReference<BufferedImage> reference = mBufferedImageRef.remove();
+ return reference == null ? null : reference.get();
+ }
+
+ public void offer(BufferedImage img) {
+ mBufferedImageRef.offer(new SoftReference<>(img));
+ }
+
+ public void clear() {
+ mBufferedImageRef.clear();
+ }
+
+ static class BucketCreationMetaData {
+ public final int mWidth;
+ public final int mHeight;
+ public final int mType;
+ public final int mNumberOfCopies;
+ public final Orientation mOrientation;
+ public final long mMaxCacheSize;
+
+ BucketCreationMetaData(int width, int height, int type, int numberOfCopies,
+ Orientation orientation, long maxCacheSize) {
+ mWidth = width;
+ mHeight = height;
+ mType = type;
+ mNumberOfCopies = numberOfCopies;
+ mOrientation = orientation;
+ mMaxCacheSize = maxCacheSize;
+ }
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/android/util/imagepool/ImageImpl.java b/bridge/src/android/util/imagepool/ImageImpl.java
new file mode 100644
index 0000000..42a6e73
--- /dev/null
+++ b/bridge/src/android/util/imagepool/ImageImpl.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Representation of buffered image. When used with ImagePool, it provides 2 features
+ * (last one not yet impl'd):
+ *
+ * <ul>
+ * <li>Automatic recycle of BufferedImage through use of reference counter</li>
+ * <li>Able to re-use the image for similar size of buffered image</li>
+ * <li>TODO: Able to re-use the iamge for different orientation (not yet impl'd)</li>
+ * </ul>
+ */
+/* private package */ class ImageImpl implements ImagePool.Image {
+
+ private final ReadWriteLock mLock = new ReentrantReadWriteLock();
+
+ private final int mWidth;
+ private final int mHeight;
+ private final Orientation mOrientation;
+
+ @VisibleForTesting final BufferedImage mImg;
+
+ ImageImpl(
+ int width,
+ int height,
+ @NotNull BufferedImage img,
+ Orientation orientation) {
+ mImg = img;
+ mWidth = width;
+ mHeight = height;
+ mOrientation = orientation;
+ }
+
+ @Override
+ public int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public void setRGB(int x, int y, int width, int height, int[] colors, int offset, int stride) {
+ mLock.readLock().lock();
+ try {
+ // TODO: Apply orientation.
+ mImg.setRGB(x, y, width, height, colors, offset, stride);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+
+ @Override
+ public void drawImage(Graphics2D graphics, int x, int y, @Nullable ImageObserver o) {
+ mLock.readLock().lock();
+ try {
+ // TODO: Apply orientation.
+ graphics.drawImage(mImg, x, y, mWidth, mHeight, o);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/android/util/imagepool/ImagePool.java b/bridge/src/android/util/imagepool/ImagePool.java
new file mode 100644
index 0000000..baec65d
--- /dev/null
+++ b/bridge/src/android/util/imagepool/ImagePool.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
+import java.util.function.Consumer;
+
+/**
+ * Simplified version of image pool that exists in Studio.
+ *
+ * Lacks:
+ * - PhantomReference and FinalizableReference to recognize the death of references automatically.
+ * (Meaning devs need to be more deligent in dispose.)
+ * Has:
+ * + Debugger that allows us to better trace where image is being leaked in stack.
+ */
+public interface ImagePool {
+
+ /**
+ * Returns a new image of width w and height h.
+ */
+ @NotNull
+ Image acquire(final int w, final int h, final int type);
+
+ /**
+ * Disposes the image pool, releasing all the references to the buffered images.
+ */
+ void dispose();
+
+ /**
+ * Interface that represents a buffered image. Using this wrapper allows us ot better track
+ * memory usages around BufferedImage. When all of it's references are removed, it will
+ * automatically be pooled back into the image pool for re-use.
+ */
+ interface Image {
+
+ /**
+ * Same as {@link BufferedImage#setRGB(int, int, int, int, int[], int, int)}
+ */
+ void setRGB(int x, int y, int width, int height, int[] colors, int offset, int stride);
+
+ /**
+ * Same as {@link Graphics2D#drawImage(java.awt.Image, int, int, ImageObserver)}
+ */
+ void drawImage(Graphics2D graphics, int x, int y, ImageObserver o);
+
+ /**
+ * Image orientation. It's not used at the moment. To be used later.
+ */
+ enum Orientation {
+ NONE,
+ CW_90
+ }
+
+ int getWidth();
+ int getHeight();
+ }
+
+ /**
+ * Policy for how to set up the memory pool.
+ */
+ class ImagePoolPolicy {
+
+ public final int[] mBucketSizes;
+ public final int[] mNumberOfCopies;
+ public final long mBucketMaxCacheSize;
+
+ /**
+ * @param bucketPixelSizes - list of pixel sizes to bucket (categorize) images. The list
+ * must be sorted (low to high).
+ * @param numberOfCopies - Allows users to create multiple copies of the bucket. It is
+ * recommended to create more copies for smaller images to avoid fragmentation in memory.
+ * It must match the size of bucketPixelSizes.
+ * @param bucketMaxCacheByteSize - Maximum cache byte sizes image pool is allowed to hold onto
+ * in memory.
+ */
+ public ImagePoolPolicy(
+ int[] bucketPixelSizes, int[] numberOfCopies, long bucketMaxCacheByteSize) {
+ assert bucketPixelSizes.length == numberOfCopies.length;
+ mBucketSizes = bucketPixelSizes;
+ mNumberOfCopies = numberOfCopies;
+ mBucketMaxCacheSize = bucketMaxCacheByteSize;
+ }
+ }
+}
diff --git a/bridge/src/android/util/imagepool/ImagePoolHelper.java b/bridge/src/android/util/imagepool/ImagePoolHelper.java
new file mode 100644
index 0000000..292fc59
--- /dev/null
+++ b/bridge/src/android/util/imagepool/ImagePoolHelper.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import android.util.imagepool.Bucket.BucketCreationMetaData;
+import android.util.imagepool.ImagePool.Image.Orientation;
+import android.util.imagepool.ImagePool.ImagePoolPolicy;
+
+import java.awt.image.BufferedImage;
+import java.util.Map;
+
+/* private package */ class ImagePoolHelper {
+
+ @Nullable
+ public static BucketCreationMetaData getBucketCreationMetaData(int w, int h, int type, ImagePoolPolicy poolPolicy, ImagePoolStats stats) {
+ // Find the bucket sizes for both dimensions
+ int widthBucket = -1;
+ int heightBucket = -1;
+ int index = 0;
+
+ for (int bucketMinSize : poolPolicy.mBucketSizes) {
+ if (widthBucket == -1 && w <= bucketMinSize) {
+ widthBucket = bucketMinSize;
+
+ if (heightBucket != -1) {
+ break;
+ }
+ }
+ if (heightBucket == -1 && h <= bucketMinSize) {
+ heightBucket = bucketMinSize;
+
+ if (widthBucket != -1) {
+ break;
+ }
+ }
+ ++index;
+ }
+
+ stats.recordBucketRequest(w, h);
+
+ if (index >= poolPolicy.mNumberOfCopies.length) {
+ return null;
+ }
+
+ // TODO: Apply orientation
+// if (widthBucket < heightBucket) {
+// return new BucketCreationMetaData(heightBucket, widthBucket, type, poolPolicy.mNumberOfCopies[index],
+// Orientation.CW_90, poolPolicy.mBucketMaxCacheSize);
+// }
+ return new BucketCreationMetaData(widthBucket, heightBucket, type, poolPolicy.mNumberOfCopies[index],
+ Orientation.NONE, poolPolicy.mBucketMaxCacheSize);
+ }
+
+ @Nullable
+ public static BufferedImage getBufferedImage(
+ Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats) {
+
+ // strongref is just for gc.
+ BufferedImage strongRef = populateBucket(bucket, metaData, stats);
+
+ // pool is too small to create the requested buffer.
+ if (bucket.isEmpty()) {
+ assert strongRef == null;
+ return null;
+ }
+
+ // Go through the bucket of soft references to find the first buffer that's not null.
+ // Even if gc is triggered here, strongref should survive.
+ BufferedImage img = bucket.remove();
+ while (img == null && !bucket.isEmpty()) {
+ img = bucket.remove();
+ }
+
+ if (img == null && bucket.isEmpty()) {
+ // Whole buffer was null. Recurse.
+ return getBufferedImage(bucket, metaData, stats);
+ }
+ return img;
+ }
+
+ /**
+ * Populate the bucket in greedy manner to avoid fragmentation.
+ * Behaviour is controlled by {@link ImagePoolPolicy}.
+ * Returns one strong referenced buffer to avoid getting results gc'd. Null if pool is not large
+ * enough.
+ */
+ @Nullable
+ private static BufferedImage populateBucket(
+ Bucket bucket, BucketCreationMetaData metaData, ImagePoolStats stats) {
+ if (!bucket.isEmpty()) {
+ // If not empty no need to populate.
+ return null;
+ }
+
+ BufferedImage strongRef = null;
+ for (int i = 0; i < metaData.mNumberOfCopies; i++) {
+ if (!stats.fitsMaxCacheSize(
+ metaData.mWidth, metaData.mHeight, metaData.mMaxCacheSize)) {
+ break;
+ }
+ strongRef =new BufferedImage(metaData.mWidth, metaData.mHeight,
+ metaData.mType);
+ bucket.offer(strongRef);
+ stats.recordBucketCreation(metaData.mWidth, metaData.mHeight);
+ }
+ return strongRef;
+ }
+
+ private static String toKey(int w, int h, int type) {
+ return new StringBuilder()
+ .append(w)
+ .append('x')
+ .append(h)
+ .append(':')
+ .append(type)
+ .toString();
+ }
+
+ public static Bucket getBucket(Map<String, Bucket> map, BucketCreationMetaData metaData, ImagePoolPolicy mPolicy) {
+ String key = toKey(metaData.mWidth, metaData.mHeight, metaData.mType);
+ Bucket bucket = map.get(key);
+ if (bucket == null) {
+ bucket = new Bucket();
+ map.put(key, bucket);
+ }
+ return bucket;
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/android/util/imagepool/ImagePoolImpl.java b/bridge/src/android/util/imagepool/ImagePoolImpl.java
new file mode 100644
index 0000000..3da706e
--- /dev/null
+++ b/bridge/src/android/util/imagepool/ImagePoolImpl.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import com.android.tools.layoutlib.annotations.Nullable;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+
+import android.util.imagepool.Bucket.BucketCreationMetaData;
+import android.util.imagepool.ImagePool.Image.Orientation;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.lang.ref.Reference;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Consumer;
+
+import com.google.common.base.FinalizablePhantomReference;
+import com.google.common.base.FinalizableReferenceQueue;
+
+class ImagePoolImpl implements ImagePool {
+
+ private final ReentrantReadWriteLock mReentrantLock = new ReentrantReadWriteLock();
+ private final ImagePoolPolicy mPolicy;
+ @VisibleForTesting final Map<String, Bucket> mPool = new HashMap<>();
+ @VisibleForTesting final ImagePoolStats mImagePoolStats = new ImagePoolStatsProdImpl();
+ private final FinalizableReferenceQueue mFinalizableReferenceQueue = new FinalizableReferenceQueue();
+ private final Set<Reference<?>> mReferences = new HashSet<>();
+
+ public ImagePoolImpl(ImagePoolPolicy policy) {
+ mPolicy = policy;
+ mImagePoolStats.start();
+ }
+
+ @Override
+ public Image acquire(int w, int h, int type) {
+ return acquire(w, h, type, null);
+ }
+
+ /* package private */ Image acquire(int w, int h, int type,
+ @Nullable Consumer<BufferedImage> freedCallback) {
+ mReentrantLock.writeLock().lock();
+ try {
+ BucketCreationMetaData metaData =
+ ImagePoolHelper.getBucketCreationMetaData(w, h, type, mPolicy, mImagePoolStats);
+ if (metaData == null) {
+ return defaultImageImpl(w, h, type, freedCallback);
+ }
+
+ final Bucket existingBucket = ImagePoolHelper.getBucket(mPool, metaData, mPolicy);
+ final BufferedImage img =
+ ImagePoolHelper.getBufferedImage(existingBucket, metaData, mImagePoolStats);
+ if (img == null) {
+ return defaultImageImpl(w, h, type, freedCallback);
+ }
+
+ // Clear the image. - is this necessary?
+ if (img.getRaster().getDataBuffer().getDataType() == java.awt.image.DataBuffer.TYPE_INT) {
+ Arrays.fill(((DataBufferInt)img.getRaster().getDataBuffer()).getData(), 0);
+ }
+
+ return prepareImage(
+ new ImageImpl(w, h, img, metaData.mOrientation),
+ true,
+ img,
+ existingBucket,
+ freedCallback);
+ } finally {
+ mReentrantLock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * Add statistics as well as dispose behaviour before returning image.
+ */
+ private Image prepareImage(
+ Image image,
+ boolean offerBackToBucket,
+ @Nullable BufferedImage img,
+ @Nullable Bucket existingBucket,
+ @Nullable Consumer<BufferedImage> freedCallback) {
+ final Integer imageHash = image.hashCode();
+ mImagePoolStats.acquiredImage(imageHash);
+ FinalizablePhantomReference<Image> reference =
+ new FinalizablePhantomReference<ImagePool.Image>(image, mFinalizableReferenceQueue) {
+ @Override
+ public void finalizeReferent() {
+ // This method might be called twice if the user has manually called the free() method. The second call will have no effect.
+ if (mReferences.remove(this)) {
+ mImagePoolStats.disposeImage(imageHash);
+ if (offerBackToBucket) {
+ if (!mImagePoolStats.fitsMaxCacheSize(img.getWidth(), img.getHeight(),
+ mPolicy.mBucketMaxCacheSize)) {
+ mImagePoolStats.tooBigForCache();
+ // Adding this back would go over the max cache size we set for ourselves. Release it.
+ return;
+ }
+
+ // else stat does not change.
+ existingBucket.offer(img);
+ }
+ if (freedCallback != null) {
+ freedCallback.accept(img);
+ }
+ }
+ }
+ };
+ mReferences.add(reference);
+ return image;
+ }
+
+ /**
+ * Default Image Impl to be used when the pool is not big enough.
+ */
+ private Image defaultImageImpl(int w, int h, int type,
+ @Nullable Consumer<BufferedImage> freedCallback) {
+ BufferedImage bufferedImage = new BufferedImage(w, h, type);
+ mImagePoolStats.tooBigForCache();
+ mImagePoolStats.recordAllocOutsidePool(w, h);
+ return prepareImage(new ImageImpl(w, h, bufferedImage, Orientation.NONE),
+ false, null, null, freedCallback);
+ }
+
+ @Override
+ public void dispose() {
+ mReentrantLock.writeLock().lock();
+ try {
+ for (Bucket bucket : mPool.values()) {
+ bucket.clear();
+ }
+ mImagePoolStats.clear();
+ } finally {
+ mReentrantLock.writeLock().unlock();
+ }
+ }
+
+ /* package private */ void printStat() {
+ System.out.println(mImagePoolStats.getStatistic());
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/android/util/imagepool/ImagePoolProvider.java b/bridge/src/android/util/imagepool/ImagePoolProvider.java
new file mode 100644
index 0000000..dbdd849
--- /dev/null
+++ b/bridge/src/android/util/imagepool/ImagePoolProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import android.util.imagepool.ImagePool.ImagePoolPolicy;
+
+public class ImagePoolProvider {
+
+ private static ImagePool sInstance;
+
+ @NotNull
+ public static synchronized ImagePool get() {
+ if (sInstance == null) {
+ // Idea is to create more
+ ImagePoolPolicy policy = new ImagePoolPolicy(
+ new int[]{100, 200, 400, 600, 800, 1000, 1600, 3200},
+ new int[]{ 3, 3, 2, 2, 2, 1, 1, 1},
+ 10_000_000L); // 10 MB
+
+ sInstance = new ImagePoolImpl(policy);
+ }
+ return sInstance;
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/android/util/imagepool/ImagePoolStats.java b/bridge/src/android/util/imagepool/ImagePoolStats.java
new file mode 100644
index 0000000..17dab14
--- /dev/null
+++ b/bridge/src/android/util/imagepool/ImagePoolStats.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+/**
+ * Keeps track of statistics. Some are purely for debugging purposes in which case it's wrapped
+ * around DEBUG check.
+ */
+interface ImagePoolStats {
+
+ void recordBucketCreation(int widthBucket, int heightBucket);
+
+ boolean fitsMaxCacheSize(int width, int height, long maxCacheSize);
+
+ void clear();
+
+ void recordBucketRequest(int w, int h);
+
+ void recordAllocOutsidePool(int width, int height);
+
+ void tooBigForCache();
+
+ void acquiredImage(Integer imageHash);
+
+ void disposeImage(Integer imageHash);
+
+ void start();
+
+ String getStatistic();
+}
diff --git a/bridge/src/android/util/imagepool/ImagePoolStatsDebugImpl.java b/bridge/src/android/util/imagepool/ImagePoolStatsDebugImpl.java
new file mode 100644
index 0000000..de0f757
--- /dev/null
+++ b/bridge/src/android/util/imagepool/ImagePoolStatsDebugImpl.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+
+/**
+ * Useful impl for debugging reproable error.
+ */
+public class ImagePoolStatsDebugImpl extends ImagePoolStatsProdImpl {
+
+ private static String PACKAGE_NAME = ImagePoolStats.class.getPackage().getName();
+
+ // Used for deugging purposes only.
+ private final Map<Integer, String> mCallStack = new HashMap<>();
+ private long mRequestedTotalBytes = 0;
+ private long mAllocatedOutsidePoolBytes = 0;
+
+ // Used for gc-related stats.
+ private long mPreviousGcCollection = 0;
+ private long mPreviousGcTime = 0;
+
+ /** Used for policy */
+ @Override
+ public void recordBucketCreation(int widthBucket, int heightBucket) {
+ super.recordBucketCreation(widthBucket, heightBucket);
+ }
+
+ @Override
+ public boolean fitsMaxCacheSize(int width, int height, long maxCacheSize) {
+ return super.fitsMaxCacheSize(width, height, maxCacheSize);
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+
+ mRequestedTotalBytes = 0;
+ mAllocatedOutsidePoolBytes = 0;
+ mTooBigForPoolCount = 0;
+ mCallStack.clear();
+ }
+
+ @Override
+ public void tooBigForCache() {
+ super.tooBigForCache();
+ }
+
+ /** Used for Debugging only */
+ @Override
+ public void recordBucketRequest(int w, int h) {
+ mRequestedTotalBytes += (w * h * ESTIMATED_PIXEL_BYTES);
+ }
+
+ @Override
+ public void recordAllocOutsidePool(int width, int height) {
+ mAllocatedOutsidePoolBytes += (width * height * ESTIMATED_PIXEL_BYTES);
+ }
+
+ @Override
+ public void acquiredImage(Integer imageHash) {
+ for (int i = 1; i < Thread.currentThread().getStackTrace().length; i++) {
+ StackTraceElement element = Thread.currentThread().getStackTrace()[i];
+ String str = element.toString();
+
+ if (!str.contains(PACKAGE_NAME)) {
+ mCallStack.put(imageHash, str);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void disposeImage(Integer imageHash) {
+ mCallStack.remove(imageHash);
+ }
+
+ @Override
+ public void start() {
+ long totalGarbageCollections = 0;
+ long garbageCollectionTime = 0;
+ for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
+ long count = gc.getCollectionCount();
+ if (count >= 0) {
+ totalGarbageCollections += count;
+ }
+ long time = gc.getCollectionTime();
+ if (time >= 0) {
+ garbageCollectionTime += time;
+ }
+ }
+ mPreviousGcCollection = totalGarbageCollections;
+ mPreviousGcTime = garbageCollectionTime;
+ }
+
+ private String calculateGcStatAndReturn() {
+ long totalGarbageCollections = 0;
+ long garbageCollectionTime = 0;
+ for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
+ long count = gc.getCollectionCount();
+ if (count > 0) {
+ totalGarbageCollections += count;
+ }
+ long time = gc.getCollectionTime();
+ if(time > 0) {
+ garbageCollectionTime += time;
+ }
+ }
+ totalGarbageCollections -= mPreviousGcCollection;
+ garbageCollectionTime -= mPreviousGcTime;
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("Total Garbage Collections: ");
+ builder.append(totalGarbageCollections);
+ builder.append("\n");
+
+ builder.append("Total Garbage Collection Time (ms): ");
+ builder.append(garbageCollectionTime);
+ builder.append("\n");
+
+ return builder.toString();
+ }
+
+ @Override
+ public String getStatistic() {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append(calculateGcStatAndReturn());
+ builder.append("Memory\n");
+ builder.append(" requested total : ");
+ builder.append(mRequestedTotalBytes / 1_000_000);
+ builder.append(" MB\n");
+ builder.append(" allocated (in pool) : ");
+ builder.append(mAllocateTotalBytes / 1_000_000);
+ builder.append(" MB\n");
+ builder.append(" allocated (out of pool) : ");
+ builder.append(mAllocatedOutsidePoolBytes / 1_000_000);
+ builder.append(" MB\n");
+
+ double percent = (1.0 - (double) mRequestedTotalBytes / (mAllocateTotalBytes +
+ mAllocatedOutsidePoolBytes));
+ if (percent < 0.0) {
+ builder.append(" saved : ");
+ builder.append(-1.0 * percent);
+ builder.append("%\n");
+ } else {
+ builder.append(" wasting : ");
+ builder.append(percent);
+ builder.append("%\n");
+ }
+
+ builder.append("Undispose images\n");
+ Multiset<String> countSet = HashMultiset.create();
+ for (String callsite : mCallStack.values()) {
+ countSet.add(callsite);
+ }
+
+ for (Multiset.Entry<String> entry : countSet.entrySet()) {
+ builder.append(" - ");
+ builder.append(entry.getElement());
+ builder.append(" - missed dispose : ");
+ builder.append(entry.getCount());
+ builder.append(" times\n");
+ }
+
+ builder.append("Number of times requested image didn't fit the pool : ");
+ builder.append(mTooBigForPoolCount);
+ builder.append("\n");
+
+ return builder.toString();
+ }
+}
diff --git a/bridge/src/android/util/imagepool/ImagePoolStatsProdImpl.java b/bridge/src/android/util/imagepool/ImagePoolStatsProdImpl.java
new file mode 100644
index 0000000..e6c6abe
--- /dev/null
+++ b/bridge/src/android/util/imagepool/ImagePoolStatsProdImpl.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+
+class ImagePoolStatsProdImpl implements ImagePoolStats {
+
+ static int ESTIMATED_PIXEL_BYTES = 4;
+
+ // Used for determining how many buckets can be created.
+ @VisibleForTesting long mAllocateTotalBytes = 0;
+ @VisibleForTesting int mTooBigForPoolCount = 0;
+
+ /** Used for policy */
+ @Override
+ public void recordBucketCreation(int widthBucket, int heightBucket) {
+ mAllocateTotalBytes += (widthBucket * heightBucket * ESTIMATED_PIXEL_BYTES);
+ }
+
+ @Override
+ public boolean fitsMaxCacheSize(int width, int height, long maxCacheSize) {
+ long newTotal = mAllocateTotalBytes + (width * height * ESTIMATED_PIXEL_BYTES);
+ return newTotal <= maxCacheSize;
+ }
+
+ @Override
+ public void tooBigForCache() {
+ mTooBigForPoolCount++;
+ }
+
+ @Override
+ public void clear() {
+ mAllocateTotalBytes = 0;
+ }
+
+ @Override
+ public void recordBucketRequest(int w, int h) { }
+
+ @Override
+ public void recordAllocOutsidePool(int width, int height) { }
+
+ @Override
+ public void acquiredImage(@NotNull Integer imageHash) { }
+
+ @Override
+ public void disposeImage(@NotNull Integer imageHash) { }
+
+ @Override
+ public void start() { }
+
+ @Override
+ public String getStatistic() { return ""; }
+}
diff --git a/bridge/src/android/view/BridgeInflater.java b/bridge/src/android/view/BridgeInflater.java
index 84fd0ed..0421f58 100644
--- a/bridge/src/android/view/BridgeInflater.java
+++ b/bridge/src/android/view/BridgeInflater.java
@@ -16,9 +16,11 @@
package android.view;
+import com.android.SdkConstants;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.MergeCookie;
+import com.android.ide.common.rendering.api.ResourceNamespace;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.Bridge;
@@ -26,6 +28,7 @@
import com.android.layoutlib.bridge.MockView;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil;
import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
import com.android.layoutlib.bridge.impl.ParserFactory;
@@ -33,7 +36,6 @@
import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.NotNull;
import com.android.tools.layoutlib.annotations.Nullable;
-import com.android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
@@ -43,10 +45,11 @@
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.util.ResolvingAttributeSet;
+import android.view.View.OnAttachStateChangeListener;
import android.widget.ImageView;
import android.widget.NumberPicker;
-import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -60,7 +63,17 @@
* Custom implementation of {@link LayoutInflater} to handle custom views.
*/
public final class BridgeInflater extends LayoutInflater {
-
+ private static final String INFLATER_CLASS_ATTR_NAME = "viewInflaterClass";
+ private static final ResourceReference RES_AUTO_INFLATER_CLASS_ATTR =
+ ResourceReference.attr(ResourceNamespace.RES_AUTO, INFLATER_CLASS_ATTR_NAME);
+ private static final ResourceReference LEGACY_APPCOMPAT_INFLATER_CLASS_ATTR =
+ ResourceReference.attr(ResourceNamespace.APPCOMPAT_LEGACY, INFLATER_CLASS_ATTR_NAME);
+ private static final ResourceReference ANDROIDX_APPCOMPAT_INFLATER_CLASS_ATTR =
+ ResourceReference.attr(ResourceNamespace.APPCOMPAT, INFLATER_CLASS_ATTR_NAME);
+ private static final String LEGACY_DEFAULT_APPCOMPAT_INFLATER_NAME =
+ "android.support.v7.app.AppCompatViewInflater";
+ private static final String ANDROIDX_DEFAULT_APPCOMPAT_INFLATER_NAME =
+ "androidx.appcompat.app.AppCompatViewInflater";
private final LayoutlibCallback mLayoutlibCallback;
private boolean mIsInMerge = false;
@@ -186,7 +199,19 @@
@Nullable
private static Class<?> findCustomInflater(@NotNull BridgeContext bc,
@NotNull LayoutlibCallback layoutlibCallback) {
- ResourceValue value = bc.getRenderResources().findItemInTheme("viewInflaterClass", false);
+ ResourceReference attrRef;
+ if (layoutlibCallback.isResourceNamespacingRequired()) {
+ if (layoutlibCallback.hasLegacyAppCompat()) {
+ attrRef = LEGACY_APPCOMPAT_INFLATER_CLASS_ATTR;
+ } else if (layoutlibCallback.hasAndroidXAppCompat()) {
+ attrRef = ANDROIDX_APPCOMPAT_INFLATER_CLASS_ATTR;
+ } else {
+ return null;
+ }
+ } else {
+ attrRef = RES_AUTO_INFLATER_CLASS_ATTR;
+ }
+ ResourceValue value = bc.getRenderResources().findItemInTheme(attrRef);
String inflaterName = value != null ? value.getValue() : null;
if (inflaterName != null) {
@@ -195,12 +220,16 @@
} catch (ClassNotFoundException ignore) {
}
- // viewInflaterClass was defined but we couldn't find the class
+ // viewInflaterClass was defined but we couldn't find the class.
} else if (bc.isAppCompatTheme()) {
// Older versions of AppCompat do not define the viewInflaterClass so try to get it
- // manually
+ // manually.
try {
- return layoutlibCallback.findClass("android.support.v7.app.AppCompatViewInflater");
+ if (layoutlibCallback.hasLegacyAppCompat()) {
+ return layoutlibCallback.findClass(LEGACY_DEFAULT_APPCOMPAT_INFLATER_NAME);
+ } else if (layoutlibCallback.hasAndroidXAppCompat()) {
+ return layoutlibCallback.findClass(ANDROIDX_DEFAULT_APPCOMPAT_INFLATER_NAME);
+ }
} catch (ClassNotFoundException ignore) {
}
}
@@ -332,36 +361,32 @@
ResourceValue value = null;
- @SuppressWarnings("deprecation")
- Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
- if (layoutInfo != null) {
- value = bridgeContext.getRenderResources().getFrameworkResource(
- ResourceType.LAYOUT, layoutInfo.getSecond());
- } else {
+ ResourceReference layoutInfo = Bridge.resolveResourceId(resource);
+ if (layoutInfo == null) {
layoutInfo = mLayoutlibCallback.resolveResourceId(resource);
- if (layoutInfo != null) {
- value = bridgeContext.getRenderResources().getProjectResource(
- ResourceType.LAYOUT, layoutInfo.getSecond());
- }
+ }
+ if (layoutInfo != null) {
+ value = bridgeContext.getRenderResources().getResolvedResource(layoutInfo);
}
if (value != null) {
- File f = new File(value.getValue());
- if (f.isFile()) {
- try {
- XmlPullParser parser = ParserFactory.create(f, true);
-
- BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
- parser, bridgeContext, value.isFramework());
-
- return inflate(bridgeParser, root);
- } catch (Exception e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed to parse file " + f.getAbsolutePath(), e, null);
-
+ String path = value.getValue();
+ try {
+ XmlPullParser parser = ParserFactory.create(path, true);
+ if (parser == null) {
return null;
}
+
+ BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
+ parser, bridgeContext, value.getNamespace());
+
+ return inflate(bridgeParser, root);
+ } catch (Exception e) {
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed to parse file " + path, e, null);
+
+ return null;
}
}
}
@@ -407,72 +432,90 @@
private void setupViewInContext(View view, AttributeSet attrs) {
Context context = getContext();
context = getBaseContext(context);
- if (context instanceof BridgeContext) {
- BridgeContext bc = (BridgeContext) context;
- // get the view key
- Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
- if (viewKey != null) {
- bc.addViewKey(view, viewKey);
- }
- String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX");
- if (scrollPosX != null && scrollPosX.endsWith("px")) {
- int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2));
- bc.setScrollXPos(view, value);
- }
- String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
- if (scrollPosY != null && scrollPosY.endsWith("px")) {
- int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2));
- bc.setScrollYPos(view, value);
- }
- if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
- Integer resourceId = null;
- String attrListItemValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
- BridgeConstants.ATTR_LIST_ITEM);
- int attrItemCountValue = attrs.getAttributeIntValue(BridgeConstants.NS_TOOLS_URI,
- BridgeConstants.ATTR_ITEM_COUNT, -1);
- if (attrListItemValue != null && !attrListItemValue.isEmpty()) {
- ResourceValue resValue = bc.getRenderResources().findResValue(attrListItemValue, false);
- if (resValue.isFramework()) {
- resourceId = Bridge.getResourceId(resValue.getResourceType(),
- resValue.getName());
- } else {
- resourceId = mLayoutlibCallback.getResourceId(resValue.getResourceType(),
- resValue.getName());
- }
- }
- if (resourceId == null) {
- resourceId = 0;
- }
- RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId, attrItemCountValue);
- } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) {
- String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
- BridgeConstants.ATTR_OPEN_DRAWER);
- if (attrVal != null) {
- getDrawerLayoutMap().put(view, attrVal);
- }
- }
- else if (view instanceof NumberPicker) {
- NumberPicker numberPicker = (NumberPicker) view;
- String minValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "minValue");
- if (minValue != null) {
- numberPicker.setMinValue(Integer.parseInt(minValue));
- }
- String maxValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "maxValue");
- if (maxValue != null) {
- numberPicker.setMaxValue(Integer.parseInt(maxValue));
- }
- }
- else if (view instanceof ImageView) {
- ImageView img = (ImageView) view;
- Drawable drawable = img.getDrawable();
- if (drawable instanceof Animatable) {
- if (!((Animatable) drawable).isRunning()) {
- ((Animatable) drawable).start();
- }
- }
- }
-
+ if (!(context instanceof BridgeContext)) {
+ return;
}
+
+ BridgeContext bc = (BridgeContext) context;
+ // get the view key
+ Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge);
+ if (viewKey != null) {
+ bc.addViewKey(view, viewKey);
+ }
+ String scrollPosX = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollX");
+ if (scrollPosX != null && scrollPosX.endsWith("px")) {
+ int value = Integer.parseInt(scrollPosX.substring(0, scrollPosX.length() - 2));
+ bc.setScrollXPos(view, value);
+ }
+ String scrollPosY = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
+ if (scrollPosY != null && scrollPosY.endsWith("px")) {
+ int value = Integer.parseInt(scrollPosY.substring(0, scrollPosY.length() - 2));
+ bc.setScrollYPos(view, value);
+ }
+ if (ReflectionUtils.isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) {
+ int resourceId = 0;
+ int attrItemCountValue = attrs.getAttributeIntValue(BridgeConstants.NS_TOOLS_URI,
+ BridgeConstants.ATTR_ITEM_COUNT, -1);
+ if (attrs instanceof ResolvingAttributeSet) {
+ ResourceValue attrListItemValue =
+ ((ResolvingAttributeSet) attrs).getResolvedAttributeValue(
+ BridgeConstants.NS_TOOLS_URI, BridgeConstants.ATTR_LIST_ITEM);
+ if (attrListItemValue != null) {
+ resourceId = bc.getResourceId(attrListItemValue.asReference(), 0);
+ }
+ }
+ RecyclerViewUtil.setAdapter(view, bc, mLayoutlibCallback, resourceId, attrItemCountValue);
+ } else if (ReflectionUtils.isInstanceOf(view, DrawerLayoutUtil.CN_DRAWER_LAYOUT)) {
+ String attrVal = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
+ BridgeConstants.ATTR_OPEN_DRAWER);
+ if (attrVal != null) {
+ getDrawerLayoutMap().put(view, attrVal);
+ }
+ }
+ else if (view instanceof NumberPicker) {
+ NumberPicker numberPicker = (NumberPicker) view;
+ String minValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "minValue");
+ if (minValue != null) {
+ numberPicker.setMinValue(Integer.parseInt(minValue));
+ }
+ String maxValue = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI, "maxValue");
+ if (maxValue != null) {
+ numberPicker.setMaxValue(Integer.parseInt(maxValue));
+ }
+ }
+ else if (view instanceof ImageView) {
+ ImageView img = (ImageView) view;
+ Drawable drawable = img.getDrawable();
+ if (drawable instanceof Animatable) {
+ if (!((Animatable) drawable).isRunning()) {
+ ((Animatable) drawable).start();
+ }
+ }
+ }
+ else if (view instanceof ViewStub) {
+ // By default, ViewStub will be set to GONE and won't be inflate. If the XML has the
+ // tools:visibility attribute we'll workaround that behavior.
+ String visibility = attrs.getAttributeValue(BridgeConstants.NS_TOOLS_URI,
+ SdkConstants.ATTR_VISIBILITY);
+
+ boolean isVisible = "visible".equals(visibility);
+ if (isVisible || "invisible".equals(visibility)) {
+ // We can not inflate the view until is attached to its parent so we need to delay
+ // the setVisible call until after that happens.
+ final int visibilityValue = isVisible ? View.VISIBLE : View.INVISIBLE;
+ view.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ v.removeOnAttachStateChangeListener(this);
+ view.setVisibility(visibilityValue);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {}
+ });
+ }
+ }
+
}
public void setIsInMerge(boolean isInMerge) {
@@ -541,7 +584,7 @@
@NonNull
private Map<View, String> getDrawerLayoutMap() {
if (mOpenDrawerLayouts == null) {
- mOpenDrawerLayouts = new HashMap<View, String>(4);
+ mOpenDrawerLayouts = new HashMap<>(4);
}
return mOpenDrawerLayouts;
}
diff --git a/bridge/src/android/view/IWindowManagerImpl.java b/bridge/src/android/view/IWindowManagerImpl.java
index b1d9151..efa8a9a 100644
--- a/bridge/src/android/view/IWindowManagerImpl.java
+++ b/bridge/src/android/view/IWindowManagerImpl.java
@@ -16,32 +16,15 @@
package android.view;
-import android.app.IAssistDataReceiver;
import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
-import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.DisplayMetrics;
-import android.view.RemoteAnimationAdapter;
-
-import com.android.internal.os.IResultReceiver;
-import com.android.internal.policy.IKeyguardDismissCallback;
-import com.android.internal.policy.IShortcutService;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethodClient;
/**
* Basic implementation of {@link IWindowManager} so that {@link Display} (and
* {@link Display_Delegate}) can return a valid instance.
*/
-public class IWindowManagerImpl implements IWindowManager {
+public class IWindowManagerImpl extends IWindowManager.Default {
private final Configuration mConfig;
private final DisplayMetrics mMetrics;
@@ -70,500 +53,8 @@
}
@Override
- public boolean hasNavigationBar() {
+ public boolean hasNavigationBar(int displayId) {
+ // TODO(multi-display): Change it once we need it per display.
return mHasNavigationBar;
}
-
- // ---- unused implementation of IWindowManager ----
-
- @Override
- public int getNavBarPosition() throws RemoteException {
- return 0;
- }
-
- @Override
- public void addWindowToken(IBinder arg0, int arg1, int arg2) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void clearForcedDisplaySize(int displayId) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void clearForcedDisplayDensityForUser(int displayId, int userId) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setOverscan(int displayId, int left, int top, int right, int bottom)
- throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void closeSystemDialogs(String arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void startFreezingScreen(int exitAnim, int enterAnim) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void stopFreezingScreen() {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void disableKeyguard(IBinder arg0, String arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void executeAppTransition() throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void exitKeyguardSecurely(IOnKeyguardExitResult arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void freezeRotation(int arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public float getAnimationScale(int arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public float[] getAnimationScales() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int getPendingAppTransition() throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public boolean inputMethodClientHasFocus(IInputMethodClient arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isKeyguardLocked() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isKeyguardSecure() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean isViewServerRunning() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public IWindowSession openSession(IWindowSessionCallback argn1, IInputMethodClient arg0,
- IInputContext arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void overridePendingAppTransition(String arg0, int arg1, int arg2,
- IRemoteCallback startedCallback) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
- int startHeight) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionClipReveal(int startX, int startY,
- int startWidth, int startHeight) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionThumb(GraphicBuffer srcThumb, int startX, int startY,
- IRemoteCallback startedCallback, boolean scaleUp) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionAspectScaledThumb(GraphicBuffer srcThumb, int startX,
- int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback,
- boolean scaleUp) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionInPlace(String packageName, int anim) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionMultiThumbFuture(
- IAppTransitionAnimationSpecsFuture specsFuture, IRemoteCallback startedCallback,
- boolean scaleUp) throws RemoteException {
-
- }
-
- @Override
- public void overridePendingAppTransitionMultiThumb(AppTransitionAnimationSpec[] specs,
- IRemoteCallback callback0, IRemoteCallback callback1, boolean scaleUp) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void overridePendingAppTransitionRemote(RemoteAnimationAdapter adapter) {
- }
-
- @Override
- public void prepareAppTransition(int arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void reenableKeyguard(IBinder arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void removeWindowToken(IBinder arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean requestAssistScreenshot(IAssistDataReceiver receiver)
- throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void setAnimationScale(int arg0, float arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setAnimationScales(float[] arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public float getCurrentAnimatorScale() throws RemoteException {
- return 0;
- }
-
- @Override
- public void setEventDispatching(boolean arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setFocusedApp(IBinder arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void getInitialDisplaySize(int displayId, Point size) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void getBaseDisplaySize(int displayId, Point size) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setForcedDisplaySize(int displayId, int arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public int getInitialDisplayDensity(int displayId) {
- return -1;
- }
-
- @Override
- public int getBaseDisplayDensity(int displayId) {
- return -1;
- }
-
- @Override
- public void setForcedDisplayDensityForUser(int displayId, int density, int userId)
- throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setForcedDisplayScalingMode(int displayId, int mode) {
- }
-
- @Override
- public void setInTouchMode(boolean arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public int[] setNewDisplayOverrideConfiguration(Configuration arg0, int displayId)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void refreshScreenCaptureDisabled(int userId) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void updateRotation(boolean arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setStrictModeVisualIndicatorPreference(String arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void showStrictModeViolation(boolean arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public boolean startViewServer(int arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void statusBarVisibilityChanged(int arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setRecentsVisibility(boolean visible) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setPipVisibility(boolean visible) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setShelfHeight(boolean visible, int shelfHeight) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
- }
-
- @Override
- public boolean stopViewServer() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void thawRotation() throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public Configuration updateOrientationFromAppTokens(Configuration arg0, IBinder arg1, int arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int watchRotation(IRotationWatcher arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public void removeRotationWatcher(IRotationWatcher arg0) throws RemoteException {
- }
-
- @Override
- public IBinder asBinder() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int getPreferredOptionsPanelGravity() throws RemoteException {
- return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- }
-
- @Override
- public void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message)
- throws RemoteException {
- }
-
- @Override
- public void setSwitchingUser(boolean switching) throws RemoteException {
- }
-
- @Override
- public void lockNow(Bundle options) {
- // TODO Auto-generated method stub
- }
-
- @Override
- public boolean isSafeModeEnabled() {
- return false;
- }
-
- @Override
- public boolean isRotationFrozen() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void enableScreenIfNeeded() throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public boolean clearWindowContentFrameStats(IBinder token) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public WindowContentFrameStats getWindowContentFrameStats(IBinder token)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public int getDockedStackSide() throws RemoteException {
- return 0;
- }
-
- @Override
- public void endProlongedAnimations() {
- }
-
- @Override
- public void registerDockedStackListener(IDockedStackListener listener) throws RemoteException {
- }
-
- @Override
- public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) throws RemoteException {
- }
-
- @Override
- public void setResizeDimLayer(boolean visible, int targetStackId, float alpha)
- throws RemoteException {
- }
-
- @Override
- public void setDockedStackDividerTouchRegion(Rect touchableRegion) throws RemoteException {
- }
-
- @Override
- public void requestAppKeyboardShortcuts(
- IResultReceiver receiver, int deviceId) throws RemoteException {
- }
-
- @Override
- public void getStableInsets(int displayId, Rect outInsets) throws RemoteException {
- }
-
- @Override
- public void registerShortcutKey(long shortcutCode, IShortcutService service)
- throws RemoteException {}
-
- @Override
- public void createInputConsumer(IBinder token, String name, InputChannel inputChannel)
- throws RemoteException {}
-
- @Override
- public boolean destroyInputConsumer(String name) throws RemoteException {
- return false;
- }
-
- @Override
- public Bitmap screenshotWallpaper() throws RemoteException {
- return null;
- }
-
- @Override
- public Region getCurrentImeTouchRegion() throws RemoteException {
- return null;
- }
-
- @Override
- public boolean registerWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
- int displayId) throws RemoteException {
- return false;
- }
-
- @Override
- public void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
- int displayId) throws RemoteException {
- }
-
- @Override
- public void startWindowTrace() throws RemoteException {
- }
-
- @Override
- public void stopWindowTrace() throws RemoteException {
- }
-
- @Override
- public boolean isWindowTraceEnabled() throws RemoteException {
- return false;
- }
-
- @Override
- public void requestUserActivityNotification() throws RemoteException {
- }
-
- @Override
- public void dontOverrideDisplayInfo(int displayId) throws RemoteException {
- }
}
diff --git a/bridge/src/android/view/LayoutInflater_Delegate.java b/bridge/src/android/view/LayoutInflater_Delegate.java
index cec6bb3..3f364f9 100644
--- a/bridge/src/android/view/LayoutInflater_Delegate.java
+++ b/bridge/src/android/view/LayoutInflater_Delegate.java
@@ -233,4 +233,15 @@
LayoutInflater.consumeChildElements(parser);
}
+
+ @LayoutlibDelegate
+ /* package */ static void initPrecompiledViews(LayoutInflater thisInflater) {
+ initPrecompiledViews(thisInflater, false);
+ }
+
+ @LayoutlibDelegate
+ /* package */ static void initPrecompiledViews(LayoutInflater thisInflater,
+ boolean enablePrecompiledViews) {
+ thisInflater.initPrecompiledViews_Original(enablePrecompiledViews);
+ }
}
diff --git a/bridge/src/android/view/PointerIcon_Delegate.java b/bridge/src/android/view/PointerIcon_Delegate.java
index 4a5ea9b..84c75f7 100644
--- a/bridge/src/android/view/PointerIcon_Delegate.java
+++ b/bridge/src/android/view/PointerIcon_Delegate.java
@@ -30,4 +30,9 @@
// PointerIcon would not be displayed by layoutlib anyway, so we always return the null
// icon.
}
+
+ @LayoutlibDelegate
+ /*package*/ static void registerDisplayListener(Context context) {
+ // Ignore this as we do not have a DisplayManager
+ }
}
diff --git a/bridge/src/android/view/SurfaceControl_Delegate.java b/bridge/src/android/view/SurfaceControl_Delegate.java
new file mode 100644
index 0000000..24838b2
--- /dev/null
+++ b/bridge/src/android/view/SurfaceControl_Delegate.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 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.view;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+public class SurfaceControl_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<SurfaceControl_Delegate> sManager =
+ new DelegateManager<>(SurfaceControl_Delegate.class);
+ private static long sFinalizer = -1;
+
+ @LayoutlibDelegate
+ /*package*/ static long nativeCreateTransaction() {
+ return sManager.addNewDelegate(new SurfaceControl_Delegate());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nativeGetNativeTransactionFinalizer() {
+ synchronized (SurfaceControl_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
+ }
+}
diff --git a/bridge/src/android/view/ViewGroup_Delegate.java b/bridge/src/android/view/ViewGroup_Delegate.java
index 10a4f31..a34ef22 100644
--- a/bridge/src/android/view/ViewGroup_Delegate.java
+++ b/bridge/src/android/view/ViewGroup_Delegate.java
@@ -16,9 +16,12 @@
package android.view;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.resources.Density;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
import android.graphics.Canvas;
@@ -27,6 +30,7 @@
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.view.animation.Transformation;
+import android.view.shadow.HighQualityShadowPainter;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
@@ -65,11 +69,32 @@
private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
Outline outline) {
+ boolean highQualityShadow = false;
+ boolean enableShadow = true;
float elevation = getElevation(child, parent);
- if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
- RectShadowPainter.paintShadow(outline, elevation, canvas, child.getAlpha());
+ Context bridgeContext = parent.getContext();
+ if (bridgeContext instanceof BridgeContext) {
+ highQualityShadow = ((BridgeContext) bridgeContext).getLayoutlibCallback()
+ .getFlag(RenderParamsFlags.FLAG_RENDER_HIGH_QUALITY_SHADOW);
+ enableShadow = ((BridgeContext) bridgeContext).getLayoutlibCallback()
+ .getFlag(RenderParamsFlags.FLAG_ENABLE_SHADOW);
+ }
+
+ if (!enableShadow) {
return;
}
+
+ if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
+ if (highQualityShadow) {
+ float densityDpi = bridgeContext.getResources().getDisplayMetrics().densityDpi;
+ HighQualityShadowPainter.paintRectShadow(
+ parent, outline, elevation, canvas, child.getAlpha(), densityDpi);
+ } else {
+ RectShadowPainter.paintShadow(outline, elevation, canvas, child.getAlpha());
+ }
+ return;
+ }
+
BufferedImage shadow = null;
if (outline.mPath != null) {
shadow = getPathShadow(outline, canvas, elevation, child.getAlpha());
@@ -83,7 +108,7 @@
Rect clipBounds = canvas.getClipBounds();
Rect newBounds = new Rect(clipBounds);
newBounds.inset((int)-elevation, (int)-elevation);
- canvas.clipRect(newBounds, Op.REPLACE);
+ canvas.clipRectUnion(newBounds);
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.restore();
}
diff --git a/bridge/src/android/view/accessibility/AccessibilityManager.java b/bridge/src/android/view/accessibility/AccessibilityManager.java
index 84b4064..d5e60a9 100644
--- a/bridge/src/android/view/accessibility/AccessibilityManager.java
+++ b/bridge/src/android/view/accessibility/AccessibilityManager.java
@@ -151,7 +151,7 @@
public void setState(int state) {
}
- public void notifyServicesStateChanged() {
+ public void notifyServicesStateChanged(long updatedUiTimeout) {
}
public void setRelevantEventTypes(int eventTypes) {
diff --git a/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java b/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
index dc4f9c8..85dc5d3 100644
--- a/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
+++ b/bridge/src/android/view/inputmethod/InputMethodManager_Accessor.java
@@ -21,7 +21,7 @@
*/
public class InputMethodManager_Accessor {
- public static void resetInstance() {
- InputMethodManager.sInstance = null;
+ public static void tearDownEditMode() {
+ InputMethodManager.tearDownEditMode();
}
}
diff --git a/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java b/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
index b2a183b..5dd25b4 100644
--- a/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
+++ b/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
@@ -16,12 +16,8 @@
package android.view.inputmethod;
-import com.android.internal.view.IInputMethodManager;
-import com.android.layoutlib.bridge.util.ReflectionUtils;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import android.os.Looper;
-
/**
* Delegate used to provide new implementation of a select few methods of {@link InputMethodManager}
@@ -35,15 +31,7 @@
// ---- Overridden methods ----
@LayoutlibDelegate
- /*package*/ static InputMethodManager getInstance() {
- synchronized (InputMethodManager.class) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm == null) {
- imm = new InputMethodManager(ReflectionUtils.createProxy(IInputMethodManager.class),
- Looper.getMainLooper());
- InputMethodManager.sInstance = imm;
- }
- return imm;
- }
+ /*package*/ static boolean isInEditMode() {
+ return true;
}
}
diff --git a/bridge/src/android/view/math/Math3DHelper.java b/bridge/src/android/view/math/Math3DHelper.java
new file mode 100644
index 0000000..7359c4c
--- /dev/null
+++ b/bridge/src/android/view/math/Math3DHelper.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2018 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.view.math;
+
+public class Math3DHelper {
+
+ private static final float EPSILON = 0.0000001f;
+
+ private Math3DHelper() { }
+
+ /**
+ * Calculates [p1x+t*(p2x-p1x)=dx*t2+px,p1y+t*(p2y-p1y)=dy*t2+py],[t,t2];
+ *
+ * @param d - dimension in which the poly is represented (supports 2 or 3D)
+ * @return float[]{t2, t, p1} or float[]{Float.NaN}
+ */
+ public static float[] rayIntersectPoly(float[] poly, int polyLength, float px, float py,
+ float dx, float dy, int d) {
+ int p1 = polyLength - 1;
+ for (int p2 = 0; p2 < polyLength; p2++) {
+ float p1x = poly[p1 * d + 0];
+ float p1y = poly[p1 * d + 1];
+ float p2x = poly[p2 * d + 0];
+ float p2y = poly[p2 * d + 1];
+ float div = (dx * (p1y - p2y) + dy * (p2x - p1x));
+ if (div != 0) {
+ float t = (dx * (p1y - py) + dy * (px - p1x)) / div;
+ if (t >= 0 && t <= 1) {
+ float t2 = (p1x * (py - p2y)
+ + p2x * (p1y - py)
+ + px * (p2y - p1y))
+ / div;
+ if (t2 > 0) {
+ return new float[]{t2, t, p1};
+ }
+ }
+ }
+ p1 = p2;
+ }
+ return new float[]{Float.NaN};
+ }
+
+ public static void centroid2d(float[] poly, int len, float[] ret) {
+ float sumx = 0;
+ float sumy = 0;
+ int p1 = len - 1;
+ float area = 0;
+ for (int p2 = 0; p2 < len; p2++) {
+ float x1 = poly[p1 * 2 + 0];
+ float y1 = poly[p1 * 2 + 1];
+ float x2 = poly[p2 * 2 + 0];
+ float y2 = poly[p2 * 2 + 1];
+ float a = (x1 * y2 - x2 * y1);
+ sumx += (x1 + x2) * a;
+ sumy += (y1 + y2) * a;
+ area += a;
+ p1 = p2;
+ }
+ float centroidx = sumx / (3 * area);
+ float centroidy = sumy / (3 * area);
+ ret[0] = centroidx;
+ ret[1] = centroidy;
+ }
+
+ public static void centroid3d(float[] poly, int len, float[] ret) {
+ int n = len - 1;
+ double area = 0;
+ double cx = 0;
+ double cy = 0;
+ double cz = 0;
+ for (int i = 1; i < n; i++) {
+ int k = i + 1;
+ float a0 = poly[i * 3 + 0] - poly[0 * 3 + 0];
+ float a1 = poly[i * 3 + 1] - poly[0 * 3 + 1];
+ float a2 = poly[i * 3 + 2] - poly[0 * 3 + 2];
+ float b0 = poly[k * 3 + 0] - poly[0 * 3 + 0];
+ float b1 = poly[k * 3 + 1] - poly[0 * 3 + 1];
+ float b2 = poly[k * 3 + 2] - poly[0 * 3 + 2];
+ float c0 = a1 * b2 - b1 * a2;
+ float c1 = a2 * b0 - b2 * a0;
+ float c2 = a0 * b1 - b0 * a1;
+ double areaOfTriangle = Math.sqrt(c0 * c0 + c1 * c1 + c2 * c2);
+ area += areaOfTriangle;
+ cx += areaOfTriangle * (poly[i * 3 + 0] + poly[k * 3 + 0] + poly[0 * 3 + 0]);
+ cy += areaOfTriangle * (poly[i * 3 + 1] + poly[k * 3 + 1] + poly[0 * 3 + 1]);
+ cz += areaOfTriangle * (poly[i * 3 + 2] + poly[k * 3 + 2] + poly[0 * 3 + 2]);
+ }
+ ret[0] = (float) (cx / (3 * area));
+ ret[1] = (float) (cy / (3 * area));
+ ret[2] = (float) (cz / (3 * area));
+ }
+
+ public final static int min(int x1, int x2, int x3) {
+ return (x1 > x2) ? ((x2 > x3) ? x3 : x2) : ((x1 > x3) ? x3 : x1);
+ }
+
+ public final static int max(int x1, int x2, int x3) {
+ return (x1 < x2) ? ((x2 < x3) ? x3 : x2) : ((x1 < x3) ? x3 : x1);
+ }
+
+ private static void xsort(float[] points, int pointsLength) {
+ quicksortX(points, 0, pointsLength - 1);
+ }
+
+ public static int hull(float[] points, int pointsLength, float[] retPoly) {
+ xsort(points, pointsLength);
+ int n = pointsLength;
+ float[] lUpper = new float[n * 2];
+ lUpper[0] = points[0];
+ lUpper[1] = points[1];
+ lUpper[2] = points[2];
+ lUpper[3] = points[3];
+
+ int lUpperSize = 2;
+
+ for (int i = 2; i < n; i++) {
+ lUpper[lUpperSize * 2 + 0] = points[i * 2 + 0];
+ lUpper[lUpperSize * 2 + 1] = points[i * 2 + 1];
+ lUpperSize++;
+
+ while (lUpperSize > 2 && !rightTurn(
+ lUpper[(lUpperSize - 3) * 2], lUpper[(lUpperSize - 3) * 2 + 1],
+ lUpper[(lUpperSize - 2) * 2], lUpper[(lUpperSize - 2) * 2 + 1],
+ lUpper[(lUpperSize - 1) * 2], lUpper[(lUpperSize - 1) * 2 + 1])) {
+ // Remove the middle point of the three last
+ lUpper[(lUpperSize - 2) * 2 + 0] = lUpper[(lUpperSize - 1) * 2 + 0];
+ lUpper[(lUpperSize - 2) * 2 + 1] = lUpper[(lUpperSize - 1) * 2 + 1];
+ lUpperSize--;
+ }
+ }
+
+ float[] lLower = new float[n * 2];
+ lLower[0] = points[(n - 1) * 2 + 0];
+ lLower[1] = points[(n - 1) * 2 + 1];
+ lLower[2] = points[(n - 2) * 2 + 0];
+ lLower[3] = points[(n - 2) * 2 + 1];
+
+ int lLowerSize = 2;
+
+ for (int i = n - 3; i >= 0; i--) {
+ lLower[lLowerSize * 2 + 0] = points[i * 2 + 0];
+ lLower[lLowerSize * 2 + 1] = points[i * 2 + 1];
+ lLowerSize++;
+
+ while (lLowerSize > 2 && !rightTurn(
+ lLower[(lLowerSize - 3) * 2], lLower[(lLowerSize - 3) * 2 + 1],
+ lLower[(lLowerSize - 2) * 2], lLower[(lLowerSize - 2) * 2 + 1],
+ lLower[(lLowerSize - 1) * 2], lLower[(lLowerSize - 1) * 2 + 1])) {
+ // Remove the middle point of the three last
+ lLower[(lLowerSize - 2) * 2 + 0] = lLower[(lLowerSize - 1) * 2 + 0];
+ lLower[(lLowerSize - 2) * 2 + 1] = lLower[(lLowerSize - 1) * 2 + 1];
+ lLowerSize--;
+ }
+ }
+ int count = 0;
+
+ for (int i = 0; i < lUpperSize; i++) {
+ retPoly[count * 2 + 0] = lUpper[i * 2 + 0];
+ retPoly[count * 2 + 1] = lUpper[i * 2 + 1];
+ count++;
+ }
+
+ for (int i = 1; i < lLowerSize - 1; i++) {
+ retPoly[count * 2 + 0] = lLower[i * 2 + 0];
+ retPoly[count * 2 + 1] = lLower[i * 2 + 1];
+ count++;
+ }
+
+ return count;
+ }
+
+ private static boolean rightTurn(float ax, float ay, float bx, float by, float cx, float cy) {
+ return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax) > 0.00001;
+ }
+
+ /**
+ * Calculates the intersection of poly1 with poly2 and puts in poly2
+ * @return number of point in poly2
+ */
+ public static int intersection(
+ float[] poly1, int poly1length, float[] poly2, int poly2length) {
+ makeClockwise(poly1, poly1length);
+ makeClockwise(poly2, poly2length);
+ float[] poly = new float[(poly1length * poly2length + 2) * 2];
+ int count = 0;
+ int pcount = 0;
+ for (int i = 0; i < poly1length; i++) {
+ if (pointInsidePolygon(poly1[i * 2], poly1[i * 2 + 1], poly2, poly2length)) {
+ poly[count * 2] = poly1[i * 2];
+ poly[count * 2 + 1] = poly1[i * 2 + 1];
+ count++;
+ pcount++;
+ }
+ }
+ int fromP1 = pcount;
+ for (int i = 0; i < poly2length; i++) {
+ if (pointInsidePolygon(poly2[i * 2], poly2[i * 2 + 1], poly1, poly1length)) {
+ poly[count * 2] = poly2[i * 2];
+ poly[count * 2 + 1] = poly2[i * 2 + 1];
+ count++;
+ }
+ }
+ int fromP2 = count - fromP1;
+ if (fromP1 == poly1length) { // use p1
+ for (int i = 0; i < poly1length; i++) {
+ poly2[i * 2] = poly1[i * 2];
+ poly2[i * 2 + 1] = poly1[i * 2 + 1];
+ }
+ return poly1length;
+ }
+ if (fromP2 == poly2length) { // use p2
+ return poly2length;
+ }
+ float[] intersection = new float[2];
+ for (int i = 0; i < poly2length; i++) {
+ for (int j = 0; j < poly1length; j++) {
+ int i1_by_2 = i * 2;
+ int i2_by_2 = ((i + 1) % poly2length) * 2;
+ int j1_by_2 = j * 2;
+ int j2_by_2 = ((j + 1) % poly1length) * 2;
+ boolean found = lineIntersection(
+ poly2[i1_by_2], poly2[i1_by_2 + 1],
+ poly2[i2_by_2], poly2[i2_by_2 + 1],
+ poly1[j1_by_2], poly1[j1_by_2 + 1],
+ poly1[j2_by_2], poly1[j2_by_2 + 1], intersection);
+ if (found) {
+ poly[count * 2] = intersection[0];
+ poly[count * 2 + 1] = intersection[1];
+ count++;
+ } else {
+ float dx = poly2[i * 2] - poly1[j * 2];
+ float dy = poly2[i * 2 + 1] - poly1[j * 2 + 1];
+
+ if (dx * dx + dy * dy < 0.01) {
+ poly[count * 2] = poly2[i * 2];
+ poly[count * 2 + 1] = poly2[i * 2 + 1];
+ count++;
+ }
+ }
+ }
+ }
+ if (count == 0) {
+ return 0;
+ }
+ float avgx = 0;
+ float avgy = 0;
+ for (int i = 0; i < count; i++) {
+ avgx += poly[i * 2];
+ avgy += poly[i * 2 + 1];
+ }
+ avgx /= count;
+ avgy /= count;
+
+ float[] ctr = new float[] { avgx, avgy };
+ sort(poly, count, ctr);
+ int size = count;
+
+ poly2[0] = poly[0];
+ poly2[1] = poly[1];
+
+ count = 1;
+ for (int i = 1; i < size; i++) {
+ float dx = poly[i * 2] - poly[(i - 1) * 2];
+ float dy = poly[i * 2 + 1] - poly[(i - 1) * 2 + 1];
+ if (dx * dx + dy * dy >= 0.01) {
+ poly2[count * 2] = poly[i * 2];
+ poly2[count * 2 + 1] = poly[i * 2 + 1];
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public static void sort(float[] poly, int polyLength, float[] ctr) {
+ quicksortCirc(poly, 0, polyLength - 1, ctr);
+ }
+
+ public static float angle(float x1, float y1, float[] ctr) {
+ return -(float) Math.atan2(x1 - ctr[0], y1 - ctr[1]);
+ }
+
+ private static void swap(float[] points, int i, int j) {
+ float x = points[i * 2];
+ float y = points[i * 2 + 1];
+ points[i * 2] = points[j * 2];
+ points[i * 2 + 1] = points[j * 2 + 1];
+ points[j * 2] = x;
+ points[j * 2 + 1] = y;
+ }
+
+ private static void quicksortCirc(float[] points, int low, int high, float[] ctr) {
+ int i = low, j = high;
+ int p = low + (high - low) / 2;
+ float pivot = angle(points[p * 2], points[p * 2 + 1], ctr);
+ while (i <= j) {
+ while (angle(points[i * 2], points[i * 2 + 1], ctr) < pivot) {
+ i++;
+ }
+ while (angle(points[j * 2], points[j * 2 + 1], ctr) > pivot) {
+ j--;
+ }
+
+ if (i <= j) {
+ swap(points, i, j);
+ i++;
+ j--;
+ }
+ }
+ if (low < j) {
+ quicksortCirc(points, low, j, ctr);
+ }
+ if (i < high) {
+ quicksortCirc(points, i, high, ctr);
+ }
+ }
+
+ private static void quicksortX(float[] points, int low, int high) {
+ int i = low, j = high;
+ int p = low + (high - low) / 2;
+ float pivot = points[p * 2];
+ while (i <= j) {
+ while (points[i * 2] < pivot) {
+ i++;
+ }
+ while (points[j * 2] > pivot) {
+ j--;
+ }
+
+ if (i <= j) {
+ swap(points, i, j);
+ i++;
+ j--;
+ }
+ }
+ if (low < j) {
+ quicksortX(points, low, j);
+ }
+ if (i < high) {
+ quicksortX(points, i, high);
+ }
+ }
+
+ private static boolean pointInsidePolygon(float x, float y, float[] poly, int len) {
+ boolean c = false;
+ float testx = x;
+ float testy = y;
+ for (int i = 0, j = len - 1; i < len; j = i++) {
+ if (((poly[i * 2 + 1] > testy) != (poly[j * 2 + 1] > testy)) &&
+ (testx < (poly[j * 2] - poly[i * 2]) * (testy - poly[i * 2 + 1])
+ / (poly[j * 2 + 1] - poly[i * 2 + 1]) + poly[i * 2])) {
+ c = !c;
+ }
+ }
+ return c;
+ }
+
+ private static void makeClockwise(float[] polygon, int len) {
+ if (polygon == null || len == 0) {
+ return;
+ }
+ if (!isClockwise(polygon, len)) {
+ reverse(polygon, len);
+ }
+ }
+
+ private static boolean isClockwise(float[] polygon, int len) {
+ float sum = 0;
+ float p1x = polygon[(len - 1) * 2];
+ float p1y = polygon[(len - 1) * 2 + 1];
+ for (int i = 0; i < len; i++) {
+
+ float p2x = polygon[i * 2];
+ float p2y = polygon[i * 2 + 1];
+ sum += p1x * p2y - p2x * p1y;
+ p1x = p2x;
+ p1y = p2y;
+ }
+ return sum < 0;
+ }
+
+ private static void reverse(float[] polygon, int len) {
+ int n = len / 2;
+ for (int i = 0; i < n; i++) {
+ float tmp0 = polygon[i * 2];
+ float tmp1 = polygon[i * 2 + 1];
+ int k = len - 1 - i;
+ polygon[i * 2] = polygon[k * 2];
+ polygon[i * 2 + 1] = polygon[k * 2 + 1];
+ polygon[k * 2] = tmp0;
+ polygon[k * 2 + 1] = tmp1;
+ }
+ }
+
+ /**
+ * Intersects two lines in parametric form.
+ */
+ private static final boolean lineIntersection(
+ float x1, float y1,
+ float x2, float y2,
+ float x3, float y3,
+ float x4, float y4,
+ float[] ret) {
+
+ float d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+ if (d == 0.000f) {
+ return false;
+ }
+
+ float dx = (x1 * y2 - y1 * x2);
+ float dy = (x3 * y4 - y3 * x4);
+ float x = (dx * (x3 - x4) - (x1 - x2) * dy) / d;
+ float y = (dx * (y3 - y4) - (y1 - y2) * dy) / d;
+
+ if (((x - x1) * (x - x2) > EPSILON)
+ || ((x - x3) * (x - x4) > EPSILON)
+ || ((y - y1) * (y - y2) > EPSILON)
+ || ((y - y3) * (y - y4) > EPSILON)) {
+
+ return false;
+ }
+ ret[0] = x;
+ ret[1] = y;
+ return true;
+ }
+
+ /**
+ * Imagine a donut shaped image and trying to create triangles from its centroid (like
+ * cutting a pie). This function performs such action (and also edge-case triangle strips
+ * generation) then returns the resulting triangle strips.
+ *
+ * @param retstrips - the resulting triangle strips
+ */
+ public static void donutPie2(float[] penumbra, int penumbraLength,
+ float[] umbra, int umbraLength, int rays, int layers, float strength,
+ float[] retstrips) {
+ int rings = layers + 1;
+
+ double step = Math.PI * 2 / rays;
+ float[] retxy = new float[2];
+ centroid2d(umbra, umbraLength, retxy);
+ float cx = retxy[0];
+ float cy = retxy[1];
+
+ float[] t1 = new float[rays];
+ float[] t2 = new float[rays];
+
+ for (int i = 0; i < rays; i++) {
+ float dx = (float) Math.sin(Math.PI / 4 + step * i);
+ float dy = (float) Math.cos(Math.PI / 4 + step * i);
+ t2[i] = rayIntersectPoly(umbra, umbraLength, cx, cy, dx, dy, 2)[0];
+ t1[i] = rayIntersectPoly(penumbra, penumbraLength, cx, cy, dx, dy, 2)[0];
+ }
+
+ int p = 0;
+ // Calc the vertex
+ for (int r = 0; r < layers; r++) {
+ int startp = p;
+ for (int i = 0; i < rays; i++) {
+ float dx = (float) Math.sin(Math.PI / 4 + step * i);
+ float dy = (float) Math.cos(Math.PI / 4 + step * i);
+
+ for (int j = r; j < (r + 2); j++) {
+ float jf = j / (float) (rings - 1);
+ float t = t1[i] + jf * (t2[i] - t1[i]);
+ float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
+
+ retstrips[p * 3] = dx * t + cx;
+ retstrips[p * 3 + 1] = dy * t + cy;
+ retstrips[p * 3 + 2] = jf * op * strength;
+
+ p++;
+ }
+ }
+ retstrips[p * 3] = retstrips[startp * 3];
+ retstrips[p * 3 + 1] = retstrips[startp * 3 + 1];
+ retstrips[p * 3 + 2] = retstrips[startp * 3 + 2];
+ p++;
+ startp++;
+ retstrips[p * 3] = retstrips[startp * 3];
+ retstrips[p * 3 + 1] = retstrips[startp * 3 + 1];
+ retstrips[p * 3 + 2] = retstrips[startp * 3 + 2];
+ p++;
+ }
+ int oldp = p - 1;
+ retstrips[p * 3] = retstrips[oldp * 3];
+ retstrips[p * 3 + 1] = retstrips[oldp * 3 + 1];
+ retstrips[p * 3 + 2] = retstrips[oldp * 3 + 2];
+ p+=2;
+
+ oldp = p;
+ for (int k = 0; k < rays; k++) {
+ int i = k / 2;
+ if ((k & 1) == 1) { // traverse the inside in a zig zag pattern
+ // for strips
+ i = rays - i - 1;
+ }
+ float dx = (float) Math.sin(Math.PI / 4 + step * i);
+ float dy = (float) Math.cos(Math.PI / 4 + step * i);
+
+ float jf = 1;
+
+ float t = t1[i] + jf * (t2[i] - t1[i]);
+ float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
+
+ retstrips[p * 3] = dx * t + cx;
+ retstrips[p * 3 + 1] = dy * t + cy;
+ retstrips[p * 3 + 2] = jf * op * strength;
+ p++;
+ }
+ p = oldp - 1;
+ retstrips[p * 3] = retstrips[oldp * 3];
+ retstrips[p * 3 + 1] = retstrips[oldp * 3 + 1];
+ retstrips[p * 3 + 2] = retstrips[oldp * 3 + 2];
+ }
+
+ /**
+ * @return Rect bound of flattened (ignoring z). LTRB
+ * @param dimension - 2D or 3D
+ */
+ public static float[] flatBound(float[] poly, int dimension) {
+ int polySize = poly.length/dimension;
+ float left = poly[0];
+ float right = poly[0];
+ float top = poly[1];
+ float bottom = poly[1];
+
+ for (int i = 0; i < polySize; i++) {
+ float x = poly[i * dimension + 0];
+ float y = poly[i * dimension + 1];
+
+ if (left > x) {
+ left = x;
+ } else if (right < x) {
+ right = x;
+ }
+
+ if (top > y) {
+ top = y;
+ } else if (bottom < y) {
+ bottom = y;
+ }
+ }
+ return new float[]{left, top, right, bottom};
+ }
+
+ /**
+ * Translate the polygon to x and y
+ * @param dimension in what dimension is polygon represented (supports 2 or 3D).
+ */
+ public static void translate(float[] poly, float translateX, float translateY, int dimension) {
+ int polySize = poly.length/dimension;
+
+ for (int i = 0; i < polySize; i++) {
+ poly[i * dimension + 0] += translateX;
+ poly[i * dimension + 1] += translateY;
+ }
+ }
+
+}
+
diff --git a/bridge/src/android/view/shadow/AmbientShadowBitmapGenerator.java b/bridge/src/android/view/shadow/AmbientShadowBitmapGenerator.java
new file mode 100644
index 0000000..57fa2d6
--- /dev/null
+++ b/bridge/src/android/view/shadow/AmbientShadowBitmapGenerator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 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.view.shadow;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+import android.graphics.Bitmap;
+import android.view.math.Math3DHelper;
+
+/**
+ * Generates ambient shadow bitmap
+ */
+class AmbientShadowBitmapGenerator {
+
+ private final AmbientShadowConfig mShadowConfig;
+ private final TriangleBuffer mTriangleBuffer;
+ private final AmbientShadowVertexCalculator mCalculator;
+
+ private float mTranslateX;
+ private float mTranslateY;
+
+ private boolean mValid;
+
+ public AmbientShadowBitmapGenerator(AmbientShadowConfig shadowConfig) {
+ mShadowConfig = shadowConfig;
+
+ mTriangleBuffer = new TriangleBuffer();
+ mTriangleBuffer.setSize(mShadowConfig.getWidth(), mShadowConfig.getHeight(), 0);
+
+ mCalculator = new AmbientShadowVertexCalculator(mShadowConfig);
+ }
+
+ /**
+ * Populate vertices and fill the triangle buffers. To be called before {@link #getBitmap()}
+ */
+ public void populateShadow() {
+ try {
+ mValid = mCalculator.generateVertex(mShadowConfig.getPolygon());
+ if (!mValid) {
+ Bridge.getLog().warning(LayoutLog.TAG_INFO, "Arithmetic error while " +
+ "drawing ambient shadow", null, null);
+ return;
+ }
+
+ float[] shadowBounds = Math3DHelper.flatBound(mCalculator.getVertex(), 2);
+ if (shadowBounds[0] < 0) {
+ // translate to right by the offset amount.
+ mTranslateX = shadowBounds[0] * -1;
+ } else if (shadowBounds[2] > mShadowConfig.getWidth()) {
+ // translate to left by the offset amount.
+ mTranslateX = shadowBounds[2] - mShadowConfig.getWidth();
+ }
+
+ if (shadowBounds[1] < 0) {
+ mTranslateY = shadowBounds[1] * -1;
+ } else if (shadowBounds[3] > mShadowConfig.getHeight()) {
+ mTranslateY = shadowBounds[3] - mShadowConfig.getHeight();
+ }
+
+ Math3DHelper.translate(mCalculator.getVertex(), mTranslateX, mTranslateY, 2);
+
+ mTriangleBuffer.drawTriangles(mCalculator.getIndex(), mCalculator.getVertex(),
+ mCalculator.getColor(), mShadowConfig.getShadowStrength());
+ } catch (IndexOutOfBoundsException|ArithmeticException mathError) {
+ Bridge.getLog().warning(LayoutLog.TAG_INFO, "Arithmetic error while drawing " +
+ "ambient shadow",
+ mathError);
+ } catch (Exception ex) {
+ Bridge.getLog().warning(LayoutLog.TAG_INFO, "Error while drawing shadow",
+ ex);
+ }
+ }
+
+ public boolean isValid() {
+ return mValid;
+ }
+
+ public Bitmap getBitmap() {
+ return mTriangleBuffer.getImage();
+ }
+
+ public float getTranslateX() {
+ return mTranslateX;
+ }
+
+ public float getTranslateY() {
+ return mTranslateY;
+ }
+}
diff --git a/bridge/src/android/view/shadow/AmbientShadowConfig.java b/bridge/src/android/view/shadow/AmbientShadowConfig.java
new file mode 100644
index 0000000..b06bd0a
--- /dev/null
+++ b/bridge/src/android/view/shadow/AmbientShadowConfig.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 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.view.shadow;
+
+/**
+ * Model for ambient shadow rendering. Assumes light sources from centroid of the object.
+ */
+class AmbientShadowConfig {
+
+ private final int mWidth;
+ private final int mHeight;
+
+ private final float mEdgeScale;
+ private final float mShadowBoundRatio;
+ private final float mShadowStrength;
+
+ private final float[] mPolygon;
+
+ private final int mRays;
+ private final int mLayers;
+
+ private AmbientShadowConfig(Builder builder) {
+ mEdgeScale = builder.mEdgeScale;
+ mShadowBoundRatio = builder.mShadowBoundRatio;
+ mShadowStrength = builder.mShadowStrength;
+ mRays = builder.mRays;
+ mLayers = builder.mLayers;
+ mWidth = builder.mWidth;
+ mHeight = builder.mHeight;
+ mPolygon = builder.mPolygon;
+ }
+
+ public int getWidth() {
+ return mWidth;
+ }
+
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Returns scales intensity of the edge of the shadow (opacity) [0-100]
+ */
+ public float getEdgeScale() {
+ return mEdgeScale;
+ }
+
+ /**
+ * Returns scales the area (in xy) of the shadow [0-1]
+ */
+ public float getShadowBoundRatio() {
+ return mShadowBoundRatio;
+ }
+
+ /**
+ * Returns scales the intensity of the entire shadow (opacity) [0-1]
+ */
+ public float getShadowStrength() {
+ return mShadowStrength;
+ }
+
+ /**
+ * Returns opaque polygon to cast shadow
+ */
+ public float[] getPolygon() {
+ return mPolygon;
+ }
+
+ /**
+ * Returns # of rays to use in ray tracing. It determines the accuracy of outline (bounds) of
+ * the shadow.
+ */
+ public int getRays() {
+ return mRays;
+ }
+
+ /**
+ * Returns # of layers. It determines the intensity of the pen-umbra.
+ */
+ public int getLayers() {
+ return mLayers;
+ }
+
+ public static class Builder {
+
+ private float mEdgeScale;
+ private float mShadowBoundRatio;
+ private float mShadowStrength;
+ private int mRays;
+ private int mLayers;
+
+ private float[] mPolygon;
+
+ private int mWidth;
+ private int mHeight;
+
+ public Builder setEdgeScale(float edgeScale) {
+ mEdgeScale = edgeScale;
+ return this;
+ }
+
+ public Builder setShadowBoundRatio(float shadowBoundRatio) {
+ mShadowBoundRatio = shadowBoundRatio;
+ return this;
+ }
+
+ public Builder setShadowStrength(float shadowStrength) {
+ mShadowStrength = shadowStrength;
+ return this;
+ }
+
+ public Builder setRays(int rays) {
+ mRays = rays;
+ return this;
+ }
+
+ public Builder setLayers(int layers) {
+ mLayers = layers;
+ return this;
+ }
+
+ public Builder setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ return this;
+ }
+
+ public Builder setPolygon(float[] polygon) {
+ mPolygon = polygon;
+ return this;
+ }
+
+ public AmbientShadowConfig build() {
+ return new AmbientShadowConfig(this);
+ }
+ }
+}
diff --git a/bridge/src/android/view/shadow/AmbientShadowVertexCalculator.java b/bridge/src/android/view/shadow/AmbientShadowVertexCalculator.java
new file mode 100644
index 0000000..3bf747c
--- /dev/null
+++ b/bridge/src/android/view/shadow/AmbientShadowVertexCalculator.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 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.view.shadow;
+
+import android.view.math.Math3DHelper;
+
+/**
+ * Generates vertices, colours, and indices required for ambient shadow. Ambient shadows are
+ * assumed to be raycasted from the centroid of the polygon, and reaches upto a ratio based on
+ * the polygon's z-height.
+ */
+class AmbientShadowVertexCalculator {
+
+ private final float[] mVertex;
+ private final float[] mColor;
+ private final int[] mIndex;
+ private final AmbientShadowConfig mConfig;
+
+ public AmbientShadowVertexCalculator(AmbientShadowConfig config) {
+ mConfig = config;
+
+ int rings = mConfig.getLayers() + 1;
+ int size = mConfig.getRays() * rings;
+
+ mVertex = new float[size * 2];
+ mColor = new float[size * 4];
+ mIndex = new int[(size * 2 + (mConfig.getRays() - 2)) * 3];
+ }
+
+ /**
+ * Generates vertex using the polygon info
+ * @param polygon 3d polygon info in format : {x1, y1, z1, x2, y2, z2 ...}
+ * @return true if vertices are generated with right colour/index. False otherwise.
+ */
+ public boolean generateVertex(float[] polygon) {
+ // Despite us not using z coord, we want calculations in 3d space as our polygon is using
+ // 3d coord system.
+ float[] centroidxy = new float[3];
+ int polygonLength = polygon.length/3;
+
+ Math3DHelper.centroid3d(polygon, polygonLength, centroidxy);
+
+ float cx = centroidxy[0];
+ float cy = centroidxy[1];
+
+ Rays rays = new Rays(mConfig.getRays());
+ int raysLength = rays.dx.length;
+ float rayDist[] = new float[mConfig.getRays()];
+
+ float[] rayHeights = new float[mConfig.getRays()];
+
+ for (int i = 0; i < raysLength; i++) {
+ float dx = rays.dx[i];
+ float dy = rays.dy[i];
+
+ float[] intersection = Math3DHelper.rayIntersectPoly(polygon, polygonLength, cx, cy,
+ dx, dy, 3);
+ if (intersection.length == 1) {
+ return false;
+ }
+ rayDist[i] = intersection[0];
+ int index = (int) (intersection[2] * 3);
+ int index2 = (int) (((intersection[2] + 1) % polygonLength) * 3);
+ float h1 = polygon[index + 2] * mConfig.getShadowBoundRatio();
+ float h2 = polygon[index2 + 2] * mConfig.getShadowBoundRatio();
+ rayHeights[i] = h1 + intersection[1] * (h2 - h1);
+ }
+
+ int rings = mConfig.getLayers() + 1;
+ for (int i = 0; i < raysLength; i++) {
+ float dx = rays.dx[i];
+ float dy = rays.dy[i];
+ float cast = rayDist[i] * rayHeights[i];
+
+ float opacity = .8f * (0.5f / (mConfig.getEdgeScale() / 10f));
+ for (int j = 0; j < rings; j++) {
+ int p = i * rings + j;
+ float jf = j / (float) (rings - 1);
+ float t = rayDist[i] + jf * (cast - rayDist[i]);
+
+ mVertex[p * 2 + 0] = dx * t + cx;
+ mVertex[p * 2 + 1] = dy * t + cy;
+ // TODO: we might be able to optimize this in the future.
+ mColor[p * 4 + 0] = 0;
+ mColor[p * 4 + 1] = 0;
+ mColor[p * 4 + 2] = 0;
+ mColor[p * 4 + 3] = (1 - jf) * opacity;
+ }
+ }
+
+ int k = 0;
+ for (int i = 0; i < mConfig.getRays(); i++) {
+ for (int j = 0; j < mConfig.getLayers(); j++) {
+ int r1 = j + rings * i;
+ int r2 = j + rings * ((i + 1) % mConfig.getRays());
+
+ mIndex[k * 3 + 0] = r1;
+ mIndex[k * 3 + 1] = r1 + 1;
+ mIndex[k * 3 + 2] = r2;
+ k++;
+ mIndex[k * 3 + 0] = r2;
+ mIndex[k * 3 + 1] = r1 + 1;
+ mIndex[k * 3 + 2] = r2 + 1;
+ k++;
+ }
+ }
+ int ringOffset = 0;
+ for (int i = 1; i < mConfig.getRays() - 1; i++, k++) {
+ mIndex[k * 3 + 0] = ringOffset;
+ mIndex[k * 3 + 1] = ringOffset + rings * i;
+ mIndex[k * 3 + 2] = ringOffset + rings * (1 + i);
+ }
+ return true;
+ }
+
+ public int[] getIndex() {
+ return mIndex;
+ }
+
+ /**
+ * @return list of vertices in 2d in format : {x1, y1, x2, y2 ...}
+ */
+ public float[] getVertex() {
+ return mVertex;
+ }
+
+ public float[] getColor() {
+ return mColor;
+ }
+
+ private static class Rays {
+ public final float[] dx;
+ public final float[] dy;
+ public final double deltaAngle;
+
+ public Rays(int rays) {
+ dx = new float[rays];
+ dy = new float[rays];
+ deltaAngle = 2 * Math.PI / rays;
+
+ for (int i = 0; i < rays; i++) {
+ dx[i] = (float) Math.sin(deltaAngle * i);
+ dy[i] = (float) Math.cos(deltaAngle * i);
+ }
+ }
+ }
+
+}
diff --git a/bridge/src/android/view/shadow/HighQualityShadowPainter.java b/bridge/src/android/view/shadow/HighQualityShadowPainter.java
new file mode 100644
index 0000000..dd555c5
--- /dev/null
+++ b/bridge/src/android/view/shadow/HighQualityShadowPainter.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2018 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.view.shadow;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.ViewGroup;
+
+import static android.view.shadow.ShadowConstants.MIN_ALPHA;
+import static android.view.shadow.ShadowConstants.SCALE_DOWN;
+
+public class HighQualityShadowPainter {
+
+ private HighQualityShadowPainter() { }
+
+ /**
+ * Draws simple Rect shadow
+ */
+ public static void paintRectShadow(ViewGroup parent, Outline outline, float elevation,
+ Canvas canvas, float alpha, float densityDpi) {
+
+ if (!validate(elevation, densityDpi)) {
+ return;
+ }
+
+ int width = parent.getWidth() / SCALE_DOWN;
+ int height = parent.getHeight() / SCALE_DOWN;
+
+ Rect rectOriginal = new Rect();
+ Rect rectScaled = new Rect();
+ if (!outline.getRect(rectScaled) || alpha < MIN_ALPHA) {
+ // If alpha below MIN_ALPHA it's invisible (based on manual test). Save some perf.
+ return;
+ }
+
+ outline.getRect(rectOriginal);
+
+ rectScaled.left /= SCALE_DOWN;
+ rectScaled.right /= SCALE_DOWN;
+ rectScaled.top /= SCALE_DOWN;
+ rectScaled.bottom /= SCALE_DOWN;
+ float radius = outline.getRadius() / SCALE_DOWN;
+
+ if (radius > rectScaled.width() || radius > rectScaled.height()) {
+ // Rounded edge generation fails if radius is bigger than drawing box.
+ return;
+ }
+
+ // ensure alpha doesn't go over 1
+ alpha = (alpha > 1.0f) ? 1.0f : alpha;
+ float[] poly = getPoly(rectScaled, elevation / SCALE_DOWN, radius);
+
+ paintAmbientShadow(poly, canvas, width, height, alpha, rectOriginal, radius);
+ paintSpotShadow(poly, rectScaled, elevation / SCALE_DOWN,
+ canvas, densityDpi, width, height, alpha, rectOriginal, radius);
+ }
+
+ /**
+ * High quality shadow does not work well with object that is too high in elevation. Check if
+ * the object elevation is reasonable and returns true if shadow will work well. False other
+ * wise.
+ */
+ private static boolean validate(float elevation, float densityDpi) {
+ float scaledElevationPx = elevation / SCALE_DOWN;
+ float scaledSpotLightHeightPx = ShadowConstants.SPOT_SHADOW_LIGHT_Z_HEIGHT_DP *
+ (densityDpi / DisplayMetrics.DENSITY_DEFAULT);
+ if (scaledElevationPx > scaledSpotLightHeightPx) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param polygon - polygon of the shadow caster
+ * @param canvas - canvas to draw
+ * @param width - scaled canvas (parent) width
+ * @param height - scaled canvas (parent) height
+ * @param alpha - 0-1 scale
+ * @param shadowCasterOutline - unscaled original shadow caster outline.
+ * @param radius
+ */
+ private static void paintAmbientShadow(float[] polygon, Canvas canvas, int width, int height,
+ float alpha, Rect shadowCasterOutline, float radius) {
+ // TODO: Consider re-using the triangle buffer here since the world stays consistent.
+ // TODO: Reduce the buffer size based on shadow bounds.
+
+ AmbientShadowConfig config = new AmbientShadowConfig.Builder()
+ .setSize(width, height)
+ .setPolygon(polygon)
+ .setEdgeScale(ShadowConstants.AMBIENT_SHADOW_EDGE_SCALE)
+ .setShadowBoundRatio(ShadowConstants.AMBIENT_SHADOW_SHADOW_BOUND)
+ .setShadowStrength(ShadowConstants.AMBIENT_SHADOW_STRENGTH * alpha)
+ .setRays(ShadowConstants.AMBIENT_SHADOW_RAYS)
+ .setLayers(ShadowConstants.AMBIENT_SHADOW_LAYERS)
+ .build();
+
+ AmbientShadowBitmapGenerator generator = new AmbientShadowBitmapGenerator(config);
+ generator.populateShadow();
+
+ if (!generator.isValid()) {
+ return;
+ }
+
+ drawScaled(
+ canvas, generator.getBitmap(), (int) generator.getTranslateX(),
+ (int) generator.getTranslateY(), width, height,
+ shadowCasterOutline, radius);
+ }
+
+ /**
+ * @param poly - polygon of the shadow caster
+ * @param rectBound - scaled bounds of shadow caster.
+ * @param canvas - canvas to draw
+ * @param width - scaled canvas (parent) width
+ * @param height - scaled canvas (parent) height
+ * @param alpha - 0-1 scale
+ * @param shadowCasterOutline - unscaled original shadow caster outline.
+ * @param radius
+ */
+ private static void paintSpotShadow(float[] poly, Rect rectBound, float elevation, Canvas canvas,
+ float densityDpi, int width, int height, float alpha, Rect shadowCasterOutline,
+ float radius) {
+
+ // TODO: Use alpha later
+ float lightZHeightPx = ShadowConstants.SPOT_SHADOW_LIGHT_Z_HEIGHT_DP * (densityDpi / DisplayMetrics.DENSITY_DEFAULT);
+ if (lightZHeightPx - elevation < ShadowConstants.SPOT_SHADOW_LIGHT_Z_EPSILON) {
+ // If the view is above or too close to the light source then return.
+ // This is done to somewhat simulate android behaviour.
+ return;
+ }
+
+ float lightX = (rectBound.left + rectBound.right) / 2;
+ float lightY = rectBound.top;
+ // Light shouldn't be bigger than the object by too much.
+ int dynamicLightRadius = Math.min(rectBound.width(), rectBound.height());
+
+ SpotShadowConfig config = new SpotShadowConfig.Builder()
+ .setSize(width, height)
+ .setLayers(ShadowConstants.SPOT_SHADOW_LAYERS)
+ .setRays(ShadowConstants.SPOT_SHADOW_RAYS)
+ .setLightCoord(lightX, lightY, lightZHeightPx)
+ .setLightRadius(dynamicLightRadius)
+ .setLightSourcePoints(ShadowConstants.SPOT_SHADOW_LIGHT_SOURCE_POINTS)
+ .setShadowStrength(ShadowConstants.SPOT_SHADOW_STRENGTH * alpha)
+ .setPolygon(poly, poly.length / ShadowConstants.COORDINATE_SIZE)
+ .build();
+
+ SpotShadowBitmapGenerator generator = new SpotShadowBitmapGenerator(config);
+ generator.populateShadow();
+
+ if (!generator.validate()) {
+ return;
+ }
+
+ drawScaled(canvas, generator.getBitmap(), (int) generator.getTranslateX(),
+ (int) generator.getTranslateY(), width, height, shadowCasterOutline, radius);
+ }
+
+ /**
+ * Draw the bitmap scaled up.
+ * @param translateX - offset in x axis by which the bitmap is shifted.
+ * @param translateY - offset in y axis by which the bitmap is shifted.
+ * @param width - scaled width of canvas (parent)
+ * @param height - scaled height of canvas (parent)
+ * @param shadowCaster - unscaled outline of shadow caster
+ * @param radius
+ */
+ private static void drawScaled(Canvas canvas, Bitmap bitmap, int translateX, int translateY,
+ int width, int height, Rect shadowCaster, float radius) {
+ int unscaledTranslateX = translateX * SCALE_DOWN;
+ int unscaledTranslateY = translateY * SCALE_DOWN;
+
+ // To the canvas
+ Rect dest = new Rect(
+ -unscaledTranslateX,
+ -unscaledTranslateY,
+ (width * SCALE_DOWN) - unscaledTranslateX,
+ (height * SCALE_DOWN) - unscaledTranslateY);
+ Rect destSrc = new Rect(0, 0, width, height);
+
+ if (radius > 0) {
+ // Rounded edge.
+ int save = canvas.save();
+ canvas.drawBitmap(bitmap, destSrc, dest, null);
+ canvas.restoreToCount(save);
+ return;
+ }
+
+ /**
+ * ----------------------------------
+ * | |
+ * | top |
+ * | |
+ * ----------------------------------
+ * | | | |
+ * | left | shadow caster | right |
+ * | | | |
+ * ----------------------------------
+ * | |
+ * | bottom |
+ * | |
+ * ----------------------------------
+ *
+ * dest == top + left + shadow caster + right + bottom
+ * Visually, canvas.drawBitmap(bitmap, destSrc, dest, paint) would achieve the same result.
+ */
+ Rect left = new Rect(dest.left, shadowCaster.top, shadowCaster.left, shadowCaster.bottom);
+ int leftScaled = left.width() / SCALE_DOWN + destSrc.left;
+
+ Rect top = new Rect(dest.left, dest.top, dest.right, shadowCaster.top);
+ int topScaled = top.height() / SCALE_DOWN + destSrc.top;
+
+ Rect right = new Rect(shadowCaster.right, shadowCaster.top, dest.right,
+ shadowCaster.bottom);
+ int rightScaled = (shadowCaster.right + unscaledTranslateX) / SCALE_DOWN + destSrc.left;
+
+ Rect bottom = new Rect(dest.left, shadowCaster.bottom, dest.right, dest.bottom);
+ int bottomScaled = (bottom.bottom - bottom.height()) / SCALE_DOWN + destSrc.top;
+
+ // calculate parts of the middle ground that can be ignored.
+ Rect leftSrc = new Rect(destSrc.left, topScaled, leftScaled, bottomScaled);
+ Rect topSrc = new Rect(destSrc.left, destSrc.top, destSrc.right, topScaled);
+ Rect rightSrc = new Rect(rightScaled, topScaled, destSrc.right, bottomScaled);
+ Rect bottomSrc = new Rect(destSrc.left, bottomScaled, destSrc.right, destSrc.bottom);
+
+ int save = canvas.save();
+ Paint paint = new Paint();
+ canvas.drawBitmap(bitmap, leftSrc, left, paint);
+ canvas.drawBitmap(bitmap, topSrc, top, paint);
+ canvas.drawBitmap(bitmap, rightSrc, right, paint);
+ canvas.drawBitmap(bitmap, bottomSrc, bottom, paint);
+ canvas.restoreToCount(save);
+ }
+
+ private static float[] getPoly(Rect rect, float elevation, float radius) {
+ if (radius <= 0) {
+ float[] poly = new float[ShadowConstants.RECT_VERTICES_SIZE * ShadowConstants.COORDINATE_SIZE];
+
+ poly[0] = poly[9] = rect.left;
+ poly[1] = poly[4] = rect.top;
+ poly[3] = poly[6] = rect.right;
+ poly[7] = poly[10] = rect.bottom;
+ poly[2] = poly[5] = poly[8] = poly[11] = elevation;
+
+ return poly;
+ }
+
+ return buildRoundedEdges(rect, elevation, radius);
+ }
+
+ private static float[] buildRoundedEdges(
+ Rect rect, float elevation, float radius) {
+
+ float[] roundedEdgeVertices = new float[(ShadowConstants.SPLICE_ROUNDED_EDGE + 1) * 4 * 3];
+ int index = 0;
+ // 1.0 LT. From theta 0 to pi/2 in K division.
+ for (int i = 0; i <= ShadowConstants.SPLICE_ROUNDED_EDGE; i++) {
+ double theta = (Math.PI / 2.0d) * ((double) i / ShadowConstants.SPLICE_ROUNDED_EDGE);
+ float x = (float) (rect.left + (radius - radius * Math.cos(theta)));
+ float y = (float) (rect.top + (radius - radius * Math.sin(theta)));
+ roundedEdgeVertices[index++] = x;
+ roundedEdgeVertices[index++] = y;
+ roundedEdgeVertices[index++] = elevation;
+ }
+
+ // 2.0 RT
+ for (int i = ShadowConstants.SPLICE_ROUNDED_EDGE; i >= 0; i--) {
+ double theta = (Math.PI / 2.0d) * ((double) i / ShadowConstants.SPLICE_ROUNDED_EDGE);
+ float x = (float) (rect.right - (radius - radius * Math.cos(theta)));
+ float y = (float) (rect.top + (radius - radius * Math.sin(theta)));
+ roundedEdgeVertices[index++] = x;
+ roundedEdgeVertices[index++] = y;
+ roundedEdgeVertices[index++] = elevation;
+ }
+
+ // 3.0 RB
+ for (int i = 0; i <= ShadowConstants.SPLICE_ROUNDED_EDGE; i++) {
+ double theta = (Math.PI / 2.0d) * ((double) i / ShadowConstants.SPLICE_ROUNDED_EDGE);
+ float x = (float) (rect.right - (radius - radius * Math.cos(theta)));
+ float y = (float) (rect.bottom - (radius - radius * Math.sin(theta)));
+ roundedEdgeVertices[index++] = x;
+ roundedEdgeVertices[index++] = y;
+ roundedEdgeVertices[index++] = elevation;
+ }
+
+ // 4.0 LB
+ for (int i = ShadowConstants.SPLICE_ROUNDED_EDGE; i >= 0; i--) {
+ double theta = (Math.PI / 2.0d) * ((double) i / ShadowConstants.SPLICE_ROUNDED_EDGE);
+ float x = (float) (rect.left + (radius - radius * Math.cos(theta)));
+ float y = (float) (rect.bottom - (radius - radius * Math.sin(theta)));
+ roundedEdgeVertices[index++] = x;
+ roundedEdgeVertices[index++] = y;
+ roundedEdgeVertices[index++] = elevation;
+ }
+
+ return roundedEdgeVertices;
+ }
+}
diff --git a/bridge/src/android/view/shadow/ShadowConstants.java b/bridge/src/android/view/shadow/ShadowConstants.java
new file mode 100644
index 0000000..83617d8
--- /dev/null
+++ b/bridge/src/android/view/shadow/ShadowConstants.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 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.view.shadow;
+
+/**
+ * Constant values for shadow related configuration
+ */
+class ShadowConstants {
+
+ /**
+ * This is used as a factor by which to scale down the shadow bitmap. If we have world
+ * Width x Height, shadow bitmap will be Width/SCALE_DOWN x Height/SCALE_DOWN and during
+ * canvas draw the shadow will be scaled up, resulting faster perf (due to smaller bitmap) but
+ * blurrier (lower quality) shadow.
+ */
+ public static final int SCALE_DOWN = 5;
+
+ public static final float MIN_ALPHA = 0.2f;
+
+ public static final int SPOT_SHADOW_RAYS = 40;
+ public static final int SPOT_SHADOW_LAYERS = 1;
+ public static final int SPOT_SHADOW_LIGHT_SOURCE_POINTS = 4;
+ public static final int SPOT_SHADOW_LIGHT_Z_HEIGHT_DP = 50 / SCALE_DOWN;
+ public static final int SPOT_SHADOW_LIGHT_Z_EPSILON = 10 / SCALE_DOWN;
+ public static final float SPOT_SHADOW_STRENGTH = 0.3f;
+
+ public static final float AMBIENT_SHADOW_EDGE_SCALE = 60f;
+ public static final float AMBIENT_SHADOW_SHADOW_BOUND = 0.02f * SCALE_DOWN;
+ public static final int AMBIENT_SHADOW_RAYS = 120;
+ public static final int AMBIENT_SHADOW_LAYERS = 1;
+ public static final float AMBIENT_SHADOW_STRENGTH = 1.0f;
+
+ public static final int COORDINATE_SIZE = 3;
+ public static final int RECT_VERTICES_SIZE = 4;
+
+ public static final int SPLICE_ROUNDED_EDGE = 5;
+}
diff --git a/bridge/src/android/view/shadow/SpotShadowBitmapGenerator.java b/bridge/src/android/view/shadow/SpotShadowBitmapGenerator.java
new file mode 100644
index 0000000..1aa2258
--- /dev/null
+++ b/bridge/src/android/view/shadow/SpotShadowBitmapGenerator.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2018 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.view.shadow;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+
+import android.graphics.Bitmap;
+import android.view.math.Math3DHelper;
+
+/**
+ * Generate spot shadow bitmap.
+ */
+class SpotShadowBitmapGenerator {
+
+ private final SpotShadowConfig mShadowConfig;
+ private final TriangleBuffer mTriangle;
+ private float[] mStrips;
+ private float[] mLightSources;
+ private float mTranslateX;
+ private float mTranslateY;
+
+ public SpotShadowBitmapGenerator(SpotShadowConfig config) {
+ // TODO: Reduce the buffer size based on shadow bounds.
+ mTriangle = new TriangleBuffer();
+ mShadowConfig = config;
+ // For now assume no change to the world size
+ mTriangle.setSize(config.getWidth(), config.getHeight(), 0);
+ }
+
+ /**
+ * Populate the shadow bitmap.
+ */
+ public void populateShadow() {
+ try {
+ mLightSources = SpotShadowVertexCalculator.calculateLight(
+ mShadowConfig.getLightRadius(),
+ mShadowConfig.getLightSourcePoints(),
+ mShadowConfig.getLightCoord()[0],
+ mShadowConfig.getLightCoord()[1],
+ mShadowConfig.getLightCoord()[2]);
+
+ mStrips = new float[3 * SpotShadowVertexCalculator.getStripSize(
+ mShadowConfig.getRays(),
+ mShadowConfig.getLayers())];
+
+ if (SpotShadowVertexCalculator.calculateShadow(
+ mLightSources,
+ mShadowConfig.getLightSourcePoints(),
+ mShadowConfig.getPoly(),
+ mShadowConfig.getPolyLength(),
+ mShadowConfig.getRays(),
+ mShadowConfig.getLayers(),
+ mShadowConfig.getShadowStrength(),
+ mStrips) != 1) {
+ return;
+ }
+
+ // Bit of a hack to re-adjust spot shadow to fit correctly within parent canvas.
+ // Problem is that outline passed is not a final position, which throws off our
+ // whereas our shadow rendering algorithm, which requires pre-set range for
+ // optimization purposes.
+ float[] shadowBounds = Math3DHelper.flatBound(mStrips, 3);
+
+ if ((shadowBounds[2] - shadowBounds[0]) > mShadowConfig.getWidth() ||
+ (shadowBounds[3] - shadowBounds[1]) > mShadowConfig.getHeight()) {
+ // Spot shadow to be casted is larger than the parent canvas,
+ // We'll let ambient shadow do the trick and skip spot shadow here.
+ return;
+ }
+
+ mTranslateX = 0;
+ mTranslateY = 0;
+ if (shadowBounds[0] < 0) {
+ // translate to right by the offset amount.
+ mTranslateX = shadowBounds[0] * -1;
+ } else if (shadowBounds[2] > mShadowConfig.getWidth()) {
+ // translate to left by the offset amount.
+ mTranslateX = shadowBounds[2] - mShadowConfig.getWidth();
+ }
+
+ if (shadowBounds[1] < 0) {
+ mTranslateY = shadowBounds[1] * -1;
+ } else if (shadowBounds[3] > mShadowConfig.getHeight()) {
+ mTranslateY = shadowBounds[3] - mShadowConfig.getHeight();
+ }
+ Math3DHelper.translate(mStrips, mTranslateX, mTranslateY, 3);
+
+ mTriangle.drawTriangles(mStrips, mShadowConfig.getShadowStrength());
+ } catch (IndexOutOfBoundsException|ArithmeticException mathError) {
+ Bridge.getLog().warning(LayoutLog.TAG_INFO, "Arithmetic error while drawing " +
+ "spot shadow",
+ mathError);
+ } catch (Exception ex) {
+ Bridge.getLog().warning(LayoutLog.TAG_INFO, "Error while drawing shadow",
+ ex);
+ }
+ }
+
+ public float getTranslateX() {
+ return mTranslateX;
+ }
+
+ public float getTranslateY() {
+ return mTranslateY;
+ }
+
+ public void clear() {
+ mTriangle.clear();
+ }
+
+ /**
+ * @return true if generated shadow poly is valid. False otherwise.
+ */
+ public boolean validate() {
+ return mStrips != null && mStrips.length >= 9;
+ }
+
+ /**
+ * @return the bitmap of shadow after it's populated
+ */
+ public Bitmap getBitmap() {
+ return mTriangle.getImage();
+ }
+
+ @VisibleForTesting
+ public float[] getStrips() {
+ return mStrips;
+ }
+
+ @VisibleForTesting
+ public void updateLightSource(float x, float y) {
+ mShadowConfig.setLightCoord(x, y);
+ }
+}
diff --git a/bridge/src/android/view/shadow/SpotShadowConfig.java b/bridge/src/android/view/shadow/SpotShadowConfig.java
new file mode 100644
index 0000000..56a7524
--- /dev/null
+++ b/bridge/src/android/view/shadow/SpotShadowConfig.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2018 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.view.shadow;
+
+
+/**
+ * Model for spot shadow rendering. Assumes single light, single object.
+ */
+class SpotShadowConfig {
+ private final int mWidth;
+ private final int mHeight;
+
+ // No need to be final but making it immutable for now.
+ private final int mLightRadius;
+ private final int mLightSourcePoints;
+
+ // No need to be final but making it immutable for now.
+ private final int mRays;
+ private final int mLayers;
+
+ // No need to be final but making it immutable for now.
+ private final float[] mPoly;
+ private final int mPolyLength;
+
+ private float[] mLightCoord;
+
+ private final float mShadowStrength;
+
+ private SpotShadowConfig(SpotShadowConfig.Builder builder) {
+ mWidth = builder.mWidth;
+ mHeight = builder.mHeight;
+ mLightRadius = builder.mLightRadius;
+ mLightSourcePoints = builder.mLightSourcePoints;
+ mRays = builder.mRays;
+ mLayers = builder.mLayers;
+ mPoly = builder.mPoly;
+ mPolyLength = builder.mPolyLength;
+
+ mLightCoord = new float[3];
+ mLightCoord[0] = builder.mLightX;
+ mLightCoord[1] = builder.mLightY;
+ mLightCoord[2] = builder.mLightHeight;
+ mShadowStrength = builder.mShadowStrength;
+ }
+
+ /**
+ * World width / height
+ */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * @return number of light source points to ray trace
+ */
+ public int getLightSourcePoints() {
+ return mLightSourcePoints;
+ }
+
+ /**
+ * @return size of the light source radius (light source is always generated as a circular shape)
+ */
+ public int getLightRadius() {
+ return mLightRadius;
+ }
+
+ /**
+ * @return object that casts shadow. xyz coordinates.
+ */
+ public float[] getPoly() {
+ return mPoly;
+ }
+
+ /**
+ * @return # of vertices in the object {@link #getPoly()} that casts shadow.
+ */
+ public int getPolyLength() {
+ return mPolyLength;
+ }
+
+ /**
+ * @return number of rays to use in raytracing. It determines the accuracy of outline (bounds) of
+ * the shadow.
+ */
+ public int getRays() {
+ return mRays;
+ }
+
+ /**
+ * @return number of layers. It determines the intensity of pen-umbra
+ */
+ public int getLayers() {
+ return mLayers;
+ }
+
+ /**
+ * Update the light source coord.
+ * @param x - x in {@link #getWidth()} coordinate
+ * @param y - y in {@link #getHeight()} coordinate
+ */
+ public void setLightCoord(float x, float y) {
+ mLightCoord[0] = x;
+ mLightCoord[1] = y;
+ }
+
+ /**
+ * @return shadow intensity from 0 to 1
+ */
+ public float getShadowStrength() {
+ return mShadowStrength;
+ }
+
+ public float[] getLightCoord() {
+ return mLightCoord;
+ }
+
+ public static class Builder {
+
+ private int mWidth;
+ private int mHeight;
+
+ // No need to be final but making it immutable for now.
+ private int mLightRadius;
+ private int mLightSourcePoints;
+
+ // No need to be final but making it immutable for now.
+ private int mRays;
+ private int mLayers;
+
+ // No need to be final but making it immutable for now.
+ private float[] mPoly;
+ private int mPolyLength;
+
+ private float mLightX;
+ private float mLightY;
+ private float mLightHeight;
+
+ private float mShadowStrength;
+
+ /**
+ * @param shadowStrength from 0 to 1
+ */
+ public Builder setShadowStrength(float shadowStrength) {
+ this.mShadowStrength = shadowStrength;
+ return this;
+ }
+
+ public Builder setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ return this;
+ }
+
+ public Builder setLightRadius(int mLightRadius) {
+ this.mLightRadius = mLightRadius;
+ return this;
+ }
+
+ public Builder setLightSourcePoints(int mLightSourcePoints) {
+ this.mLightSourcePoints = mLightSourcePoints;
+ return this;
+ }
+
+ public Builder setRays(int mRays) {
+ this.mRays = mRays;
+ return this;
+ }
+
+ public Builder setLayers(int mLayers) {
+ this.mLayers = mLayers;
+ return this;
+ }
+
+ public Builder setPolygon(float[] poly, int polyLength) {
+ this.mPoly = poly;
+ this.mPolyLength = polyLength;
+ return this;
+ }
+
+ public Builder setLightCoord(float lightX, float lightY, float lightHeight) {
+ this.mLightX = lightX;
+ this.mLightY = lightY;
+ this.mLightHeight = lightHeight;
+ return this;
+ }
+
+ public SpotShadowConfig build() {
+ return new SpotShadowConfig(this);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/bridge/src/android/view/shadow/SpotShadowVertexCalculator.java b/bridge/src/android/view/shadow/SpotShadowVertexCalculator.java
new file mode 100644
index 0000000..2e4191b
--- /dev/null
+++ b/bridge/src/android/view/shadow/SpotShadowVertexCalculator.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2018 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.view.shadow;
+
+import android.view.math.Math3DHelper;
+
+/**
+ * Generates the vertices required for spot shadow and all other shadow-related rendering.
+ */
+class SpotShadowVertexCalculator {
+
+ private SpotShadowVertexCalculator() { }
+
+ /**
+ * Create evenly distributed circular light source points from x and y (on flat z plane).
+ * This is useful for ray tracing the shadow points later. Format : (x1,y1,z1,x2,y2,z2 ...)
+ *
+ * @param radius - radius of the light source
+ * @param points - how many light source points to generate
+ * @param x - center X of the light source
+ * @param y - center Y of the light source
+ * @param height - how high (z depth) the light should be
+ * @return float points (x,y,z) of light source points.
+ */
+ public static float[] calculateLight(float radius, int points, float x, float y, float height) {
+ float[] ret = new float[points * 3];
+ for (int i = 0; i < points; i++) {
+ double angle = 2 * i * Math.PI / points;
+ ret[i * 3] = (float) Math.sin(angle) * radius + x;
+ ret[i * 3 + 1] = (float) Math.cos(angle) * radius + y;
+ ret[i * 3 + 2] = (height);
+ }
+
+ return ret;
+ }
+
+ /**
+ * @param rays - Number of rays to use for tracing
+ * @param layers - Number of layers for shadow rendering.
+ * @return size required for shadow vertices mData array based on # of rays and layers
+ */
+ public static int getStripSize(int rays, int layers){
+ return (2 + rays + ((layers) * 2 * (rays + 1)));
+ }
+
+ /**
+ * Generate shadow vertices based on params. Format : (x1,y1,z1,x2,y2,z2 ...)
+ * Precondition : Light poly must be evenly distributed on a flat surface
+ * Precondition : Poly vertices must be a convex
+ * Precondition : Light height must be higher than any poly vertices
+ *
+ * @param lightPoly - Vertices of a light source.
+ * @param lightPolyLength - Size of the vertices (usually lightPoly.length/3 unless w is
+ * included)
+ * @param poly - Vertices of opaque object casting shadow
+ * @param polyLength - Size of the vertices
+ * @param rays - Number of rays to use for tracing. It determines accuracy of the outline
+ * (bounds) of the shadow
+ * @param layers - Number of layers for shadow. It determines intensity of pen-umbra
+ * @param strength - Strength of the shadow overall [0-1]
+ * @param retstrips - Array mData to be filled in format : {x1, y1, z1, x2, y2, z2}
+ * @return 1 if successful, error code otherwise.
+ */
+ public static int calculateShadow(
+ float[] lightPoly,
+ int lightPolyLength,
+ float[] poly,
+ int polyLength,
+ int rays,
+ int layers,
+ float strength,
+ float[] retstrips) {
+ float[] shadowRegion = new float[lightPolyLength * polyLength * 2];
+ float[] outline = new float[polyLength * 2];
+ float[] umbra = new float[polyLength * lightPolyLength * 2];
+ int umbraLength = 0;
+
+ int k = 0;
+ for (int j = 0; j < lightPolyLength; j++) {
+ int m = 0;
+ for (int i = 0; i < polyLength; i++) {
+ float t = lightPoly[j * 3 + 2] - poly[i * 3 + 2];
+ if (t == 0) {
+ return 0;
+ }
+ t = lightPoly[j * 3 + 2] / t;
+ float x = lightPoly[j * 3] - t * (lightPoly[j * 3] - poly[i * 3]);
+ float y = lightPoly[j * 3 + 1] - t * (lightPoly[j * 3 + 1] - poly[i * 3 + 1]);
+
+ shadowRegion[k * 2] = x;
+ shadowRegion[k * 2 + 1] = y;
+ outline[m * 2] = x;
+ outline[m * 2 + 1] = y;
+
+ k++;
+ m++;
+ }
+
+ if (umbraLength == 0) {
+ for (int i = 0; i < polyLength * 2; i++) {
+ umbra[i] = outline[i];
+ }
+ umbraLength = polyLength;
+ } else {
+ umbraLength = Math3DHelper.intersection(outline, polyLength, umbra, umbraLength);
+ if (umbraLength == 0) {
+ break;
+ }
+
+ }
+ }
+ int shadowRegionLength = k;
+
+ float[] penumbra = new float[k * 2];
+ int penumbraLength = Math3DHelper.hull(shadowRegion, shadowRegionLength, penumbra);
+ if (umbraLength < 3) {// no real umbra make a fake one
+ float[] p = new float[3];
+ Math3DHelper.centroid3d(lightPoly, lightPolyLength, p);
+ float[] centShadow = new float[polyLength * 2];
+ for (int i = 0; i < polyLength; i++) {
+ float t = p[2] - poly[i * 3 + 2];
+ if (t == 0) {
+ return 0;
+ }
+ t = p[2] / t;
+ float x = p[0] - t * (p[0] - poly[i * 3]);
+ float y = p[1] - t * (p[1] - poly[i * 3 + 1]);
+
+ centShadow[i * 2] = x;
+ centShadow[i * 2 + 1] = y;
+ }
+ float[] c = new float[2];
+ Math3DHelper.centroid2d(centShadow, polyLength, c);
+ for (int i = 0; i < polyLength; i++) {
+ centShadow[i * 2] = (c[0] * 9 + centShadow[i * 2]) / 10;
+ centShadow[i * 2 + 1] = (c[1] * 9 + centShadow[i * 2 + 1]) / 10;
+ }
+ umbra = centShadow; // fake umbra
+ umbraLength = polyLength; // same size as the original polygon
+ }
+
+ Math3DHelper.donutPie2(penumbra, penumbraLength, umbra, umbraLength, rays,
+ layers, strength, retstrips);
+ return 1;
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/android/view/shadow/TriangleBuffer.java b/bridge/src/android/view/shadow/TriangleBuffer.java
new file mode 100644
index 0000000..8db8e1c
--- /dev/null
+++ b/bridge/src/android/view/shadow/TriangleBuffer.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2018 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.view.shadow;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+
+import java.util.Arrays;
+
+import static android.view.math.Math3DHelper.max;
+import static android.view.math.Math3DHelper.min;
+
+/**
+ * 2D Triangle buffer element that colours using z value. (z scale set).
+ */
+class TriangleBuffer {
+ int mWidth;
+ int mHeight;
+ int mImgWidth;
+ int mImgHeight;
+ int mBorder;
+ Bitmap mBitmap;
+ int mData[];
+ private float mMinX;
+ private float mMaxX;
+ private float mMinY;
+ private float mMaxY;
+
+ public void setSize(int width, int height, int border) {
+ if (mWidth == width && mHeight == height) {
+ return;
+ }
+ mWidth = width-2*border;
+ mHeight = height-2*border;
+ mBorder = border;
+ mImgWidth = width;
+ mImgHeight = height;
+
+ setScale(0, width, 0, height);
+
+ mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+ mData = new int[width * height];
+ }
+
+ public void drawTriangles(int[] index, float[] vert, float[] color,float scale) {
+ int indexSize = index.length / 3;
+ for (int i = 0; i < indexSize; i++) {
+ int vIndex = index[i * 3 + 0];
+ float vx = vert[vIndex * 2 + 0];
+ float vy = vert[vIndex * 2 + 1];
+ float c = scale*color[vIndex * 4 + 3];
+ float fx3 = vx, fy3 = vy, fz3 = c;
+
+ vIndex = index[i * 3 + 1];
+ vx = vert[vIndex * 2 + 0];
+ vy = vert[vIndex * 2 + 1];
+ c = scale*color[vIndex * 4 + 3];
+ float fx2 = vx, fy2 = vy, fz2 = c;
+
+ vIndex = index[i * 3 + 2];
+ vx = vert[vIndex * 2 + 0];
+ vy = vert[vIndex * 2 + 1];
+ c = scale*color[vIndex * 4 + 3];
+ float fx1 = vx, fy1 = vy, fz1 = c;
+
+ triangleZBuffMin(mData, mImgWidth, mImgHeight, fx3, fy3, fz3, fx2, fy2,
+ fz2, fx1, fy1, fz1);
+ triangleZBuffMin(mData, mImgWidth, mImgHeight, fx1, fy1, fz1, fx2, fy2,
+ fz2, fx3, fy3, fz3);
+ }
+ mBitmap.setPixels(mData, 0, mWidth, 0, 0, mWidth, mHeight);
+ }
+
+ public void drawTriangles(float[] strip,float scale) {
+ for (int i = 0; i < strip.length-8; i+=3) {
+ float fx3 = strip[i], fy3 = strip[i+1], fz3 = scale* strip[i+2];
+ float fx2 = strip[i+3], fy2 = strip[i+4], fz2 = scale* strip[i+5];
+ float fx1 = strip[i+6], fy1 = strip[i+7], fz1 = scale* strip[i+8];
+
+ if (fx1*(fy2-fy3)+fx2*(fy3-fy1)+fx3*(fy1-fy2) ==0) {
+ continue;
+ }
+ triangleZBuffMin(mData, mImgWidth, mImgHeight, fx3, fy3, fz3, fx2, fy2,
+ fz2, fx1, fy1, fz1);
+ }
+ mBitmap.setPixels(mData, 0, mWidth, 0, 0, mWidth, mHeight);
+ }
+
+ public Bitmap getImage() {
+ return mBitmap;
+ }
+
+ private static void triangleZBuffMin(int[] buff, int w, int h, float fx3,
+ float fy3, float fz3, float fx2, float fy2, float fz2, float fx1,
+ float fy1, float fz1) {
+ if (((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2)) < 0) {
+ float tmpx = fx1;
+ float tmpy = fy1;
+ float tmpz = fz1;
+ fx1 = fx2;
+ fy1 = fy2;
+ fz1 = fz2;
+ fx2 = tmpx;
+ fy2 = tmpy;
+ fz2 = tmpz;
+ }
+ // using maxmima
+ // solve([x1*dx+y1*dy+zoff=z1,x2*dx+y2*dy+zoff=z2,x3*dx+y3*dy+zoff=z3],[dx,dy,zoff]);
+ double d = (fx1 * (fy3 - fy2) - fx2 * fy3 + fx3 * fy2 + (fx2 - fx3) * fy1);
+ if (d == 0) {
+ return;
+ }
+ float dx = (float) (-(fy1 * (fz3 - fz2) - fy2 * fz3 + fy3 * fz2 + (fy2 - fy3)
+ * fz1) / d);
+ float dy = (float) ((fx1 * (fz3 - fz2) - fx2 * fz3 + fx3 * fz2 + (fx2 - fx3)
+ * fz1) / d);
+ float zoff = (float) ((fx1 * (fy3 * fz2 - fy2 * fz3) + fy1
+ * (fx2 * fz3 - fx3 * fz2) + (fx3 * fy2 - fx2 * fy3) * fz1) / d);
+
+ // 28.4 fixed-point coordinates
+ int y1 = (int) (16.0f * fy1 + .5f);
+ int y2 = (int) (16.0f * fy2 + .5f);
+ int y3 = (int) (16.0f * fy3 + .5f);
+
+ int x1 = (int) (16.0f * fx1 + .5f);
+ int x2 = (int) (16.0f * fx2 + .5f);
+ int x3 = (int) (16.0f * fx3 + .5f);
+
+ int dx12 = x1 - x2;
+ int dx23 = x2 - x3;
+ int dx31 = x3 - x1;
+
+ int dy12 = y1 - y2;
+ int dy23 = y2 - y3;
+ int dy31 = y3 - y1;
+
+ int fdx12 = dx12 << 4;
+ int fdx23 = dx23 << 4;
+ int fdx31 = dx31 << 4;
+
+ int fdy12 = dy12 << 4;
+ int fdy23 = dy23 << 4;
+ int fdy31 = dy31 << 4;
+
+ int minx = (min(x1, x2, x3) + 0xF) >> 4;
+ int maxx = (max(x1, x2, x3) + 0xF) >> 4;
+ int miny = (min(y1, y2, y3) + 0xF) >> 4;
+ int maxy = (max(y1, y2, y3) + 0xF) >> 4;
+
+ if (miny < 0) {
+ miny = 0;
+ }
+ if (minx < 0) {
+ minx = 0;
+ }
+ if (maxx > w) {
+ maxx = w;
+ }
+ if (maxy > h) {
+ maxy = h;
+ }
+ int off = miny * w;
+
+ int c1 = dy12 * x1 - dx12 * y1;
+ int c2 = dy23 * x2 - dx23 * y2;
+ int c3 = dy31 * x3 - dx31 * y3;
+
+ if (dy12 < 0 || (dy12 == 0 && dx12 > 0)) {
+ c1++;
+ }
+ if (dy23 < 0 || (dy23 == 0 && dx23 > 0)) {
+ c2++;
+ }
+ if (dy31 < 0 || (dy31 == 0 && dx31 > 0)) {
+ c3++;
+ }
+ int cy1 = c1 + dx12 * (miny << 4) - dy12 * (minx << 4);
+ int cy2 = c2 + dx23 * (miny << 4) - dy23 * (minx << 4);
+ int cy3 = c3 + dx31 * (miny << 4) - dy31 * (minx << 4);
+
+ for (int y = miny; y < maxy; y++) {
+ int cx1 = cy1;
+ int cx2 = cy2;
+ int cx3 = cy3;
+ float p = zoff + dy * y;
+ for (int x = minx; x < maxx; x++) {
+ if (cx1 > 0 && cx2 > 0 && cx3 > 0) {
+ int point = x + off;
+ float zval = p + dx * x;
+ buff[point] = ((int) (zval * 255)) << 24;
+ }
+ cx1 -= fdy12;
+ cx2 -= fdy23;
+ cx3 -= fdy31;
+ }
+ cy1 += fdx12;
+ cy2 += fdx23;
+ cy3 += fdx31;
+ off += w;
+ }
+ }
+
+ private void setScale(float minx, float maxx, float miny, float maxy) {
+ mMinX = minx;
+ mMaxX = maxx;
+ mMinY = miny;
+ mMaxY = maxy;
+ }
+
+ public void clear() {
+ Arrays.fill(mData, 0);
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/com/android/layoutlib/bridge/Bridge.java b/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 5dca8e7..4072bf3 100644
--- a/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -21,6 +21,8 @@
import com.android.ide.common.rendering.api.Features;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.Result.Status;
import com.android.ide.common.rendering.api.SessionParams;
@@ -30,15 +32,16 @@
import com.android.layoutlib.bridge.util.DynamicIdMap;
import com.android.ninepatch.NinePatchChunk;
import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.Nullable;
import com.android.tools.layoutlib.create.MethodAdapter;
import com.android.tools.layoutlib.create.OverrideMethod;
import com.android.util.Pair;
-import android.annotation.NonNull;
import android.content.res.BridgeAssetManager;
import android.graphics.Bitmap;
import android.graphics.FontFamily_Delegate;
import android.graphics.Typeface;
+import android.graphics.Typeface_Builder_Delegate;
import android.graphics.Typeface_Delegate;
import android.icu.util.ULocale;
import android.os.Looper;
@@ -61,6 +64,8 @@
import libcore.io.MemoryMappedFile_Delegate;
+import static android.graphics.Typeface.DEFAULT_FAMILY;
+import static android.graphics.Typeface.RESOLVE_BY_FONT_TABLE;
import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
/**
@@ -141,6 +146,8 @@
*/
private static LayoutLog sCurrentLog = sDefaultLog;
+ public static boolean sIsTypefaceInitialized;
+
private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR;
@Override
@@ -165,6 +172,7 @@
@Override
public boolean init(Map<String,String> platformProperties,
File fontLocation,
+ String icuDataPath,
Map<String, Map<String, Integer>> enumValueMap,
LayoutLog log) {
sPlatformProperties = platformProperties;
@@ -353,8 +361,9 @@
BridgeAssetManager.clearSystem();
// dispose of the default typeface.
- Typeface_Delegate.resetDefaults();
- Typeface.sDynamicTypefaceCache.evictAll();
+ if (sIsTypefaceInitialized) {
+ Typeface.sDynamicTypefaceCache.evictAll();
+ }
sProject9PatchCache.clear();
sProjectBitmapCache.clear();
@@ -493,10 +502,15 @@
* will do the clean-up, and make the thread unable to do further scene actions.
*/
public synchronized static void prepareThread() {
- // we need to make sure the Looper has been initialized for this thread.
- // this is required for View that creates Handler objects.
+ // We need to make sure the Looper has been initialized for this thread.
+ // This is required for View that creates Handler objects.
if (Looper.myLooper() == null) {
- Looper.prepareMainLooper();
+ synchronized (Looper.class) {
+ // Check if the main looper has been prepared already.
+ if (Looper.getMainLooper() == null) {
+ Looper.prepareMainLooper();
+ }
+ }
}
}
@@ -530,17 +544,20 @@
/**
* Returns details of a framework resource from its integer value.
- * @param value the integer value
- * @return a Pair containing the resource type and name, or null if the id
- * does not match any resource.
+ *
+ * <p>TODO(namespaces): remove this and just do all id resolution through the callback.
*/
- @SuppressWarnings("deprecation")
- public static Pair<ResourceType, String> resolveResourceId(int value) {
+ @Nullable
+ public static ResourceReference resolveResourceId(int value) {
Pair<ResourceType, String> pair = sRMap.get(value);
if (pair == null) {
pair = sDynamicIds.resolveId(value);
}
- return pair;
+
+ if (pair != null) {
+ return new ResourceReference(ResourceNamespace.ANDROID, pair.getFirst(), pair.getSecond());
+ }
+ return null;
}
/**
@@ -550,24 +567,18 @@
*
* @param type the type of the resource
* @param name the name of the resource.
- *
- * @return an {@link Integer} containing the resource id.
+ * @return an int containing the resource id.
*/
- @NonNull
- public static Integer getResourceId(ResourceType type, String name) {
+ public static int getResourceId(ResourceType type, String name) {
Map<String, Integer> map = sRevRMap.get(type);
- Integer value = null;
- if (map != null) {
- value = map.get(name);
- }
-
+ Integer value = map == null ? null : map.get(name);
return value == null ? sDynamicIds.getId(type, name) : value;
-
}
/**
* Returns the list of possible enums for a given attribute name.
*/
+ @Nullable
public static Map<String, Integer> getEnumValues(String attributeName) {
if (sEnumValueMap != null) {
return sEnumValueMap.get(attributeName);
@@ -669,4 +680,14 @@
sFramework9PatchCache.put(value, new SoftReference<>(ninePatch));
}
}
+
+ @Override
+ public void clearFontCache(String path) {
+ if (sIsTypefaceInitialized) {
+ final String key =
+ Typeface_Builder_Delegate.createAssetUid(BridgeAssetManager.initSystem(), path,
+ 0, null, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, DEFAULT_FAMILY);
+ Typeface.sDynamicTypefaceCache.remove(key);
+ }
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index dd2668f..842d82a 100644
--- a/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -17,11 +17,12 @@
package com.android.layoutlib.bridge;
import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
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 com.android.util.PropertiesMap;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -72,8 +73,19 @@
}
@Override
- public Map<Object, PropertiesMap> getDefaultProperties() {
- return mSession != null ? mSession.getDefaultProperties() : Collections.emptyMap();
+ public Map<Object, Map<ResourceReference, ResourceValue>> getDefaultNamespacedProperties() {
+ return mSession != null ? mSession.getDefaultNamespacedProperties() :
+ Collections.emptyMap();
+ }
+
+ @Override
+ public Map<Object, String> getDefaultStyles() {
+ return mSession != null ? mSession.getDefaultStyles() : Collections.emptyMap();
+ }
+
+ @Override
+ public Map<Object, ResourceReference> getDefaultNamespacedStyles() {
+ return mSession != null ? mSession.getDefaultNamespacedStyles() : Collections.emptyMap();
}
@Override
diff --git a/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java b/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
index faaf105..2dfbd24 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java
@@ -18,13 +18,13 @@
import com.android.layoutlib.bridge.impl.RenderAction;
-import android.icu.util.ULocale;
+import android.os.LocaleList;
import java.util.Locale;
/**
- * This class provides an alternate implementation for {@code java.util.Locale#toLanguageTag}
- * which is only available after Java 6.
+ * This class provides an alternate implementation for {@code java.util.Locale#adjustLanguageCode}
+ * which is not available in openJDK. It also overrides the getDefault method.
*
* The create tool re-writes references to the above mentioned method to this one. Hence it's
* imperative that this class is not deleted unless the create tool is modified.
@@ -32,10 +32,6 @@
@SuppressWarnings("UnusedDeclaration")
public class AndroidLocale {
- public static String toLanguageTag(Locale locale) {
- return ULocale.forLocale(locale).toLanguageTag();
- }
-
public static String adjustLanguageCode(String languageCode) {
String adjusted = languageCode.toLowerCase(Locale.US);
// Map new language codes to the obsolete language
@@ -51,20 +47,12 @@
return adjusted;
}
- public static Locale forLanguageTag(String tag) {
- return ULocale.forLanguageTag(tag).toLocale();
- }
-
- public static String getScript(Locale locale) {
- return ULocale.forLocale(locale).getScript();
- }
-
public static Locale getDefault() {
BridgeContext context = RenderAction.getCurrentContext();
if (context != null) {
- Locale locale = context.getConfiguration().locale;
- if (locale != null) {
- return locale;
+ LocaleList localeList = context.getConfiguration().getLocales();
+ if (!localeList.isEmpty()) {
+ return localeList.get(0);
}
}
return Locale.getDefault();
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
index c827f17..d8bef78 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java
@@ -40,7 +40,7 @@
*/
public final class BridgeContentProvider implements IContentProvider {
@Override
- public ContentProviderResult[] applyBatch(String callingPackage,
+ public ContentProviderResult[] applyBatch(String callingPackage, String authority,
ArrayList<ContentProviderOperation> arg0)
throws RemoteException, OperationApplicationException {
// TODO Auto-generated method stub
@@ -55,8 +55,8 @@
}
@Override
- public Bundle call(String callingPackage, String arg0, String arg1, Bundle arg2)
- throws RemoteException {
+ public Bundle call(String callingPackage, String authority, String arg0, String arg1,
+ Bundle arg2) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index b33344c..8e80a53 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -26,6 +26,7 @@
import com.android.ide.common.rendering.api.ResourceNamespace.Resolver;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.ResourceValueImpl;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
@@ -34,8 +35,6 @@
import com.android.layoutlib.bridge.impl.Stack;
import com.android.resources.ResourceType;
import com.android.util.Pair;
-import com.android.util.PropertiesMap;
-import com.android.util.PropertiesMap.Property;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -101,11 +100,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
import static android.os._Original_Build.VERSION_CODES.JELLY_BEAN_MR1;
import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE;
@@ -120,26 +119,25 @@
private static final Map<String, ResourceValue> FRAMEWORK_PATCHED_VALUES = new HashMap<>(2);
private static final Map<String, ResourceValue> FRAMEWORK_REPLACE_VALUES = new HashMap<>(3);
- private static final Resolver LEGACY_NAMESPACE_RESOLVER =
- Collections.singletonMap(SdkConstants.TOOLS_PREFIX, SdkConstants.TOOLS_URI)::get;
-
static {
- FRAMEWORK_PATCHED_VALUES.put("animateFirstView", new ResourceValue(
- ResourceType.BOOL, "animateFirstView", "false", false));
+ FRAMEWORK_PATCHED_VALUES.put("animateFirstView",
+ new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.BOOL,
+ "animateFirstView", "false"));
FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges",
- new ResourceValue(ResourceType.BOOL, "animateLayoutChanges", "false", false));
+ new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.BOOL,
+ "animateLayoutChanges", "false"));
FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout",
- new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionItemLayout",
- "text_edit_suggestion_item", true));
+ new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.LAYOUT,
+ "textEditSuggestionItemLayout", "text_edit_suggestion_item"));
FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout",
- new ResourceValue(ResourceType.LAYOUT, "textEditSuggestionContainerLayout",
- "text_edit_suggestion_container", true));
+ new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.LAYOUT,
+ "textEditSuggestionContainerLayout", "text_edit_suggestion_container"));
FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle",
- new ResourceValue(ResourceType.STYLE, "textEditSuggestionHighlightStyle",
- "TextAppearance.Holo.SuggestionHighlight", true));
-
+ new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.STYLE,
+ "textEditSuggestionHighlightStyle",
+ "TextAppearance.Holo.SuggestionHighlight"));
}
/** The map adds cookies to each view so that IDE can link xml tags to views. */
@@ -165,13 +163,9 @@
private Resources.Theme mTheme;
- private final Map<Object, PropertiesMap> mDefaultPropMaps = new IdentityHashMap<>();
-
- // maps for dynamically generated id representing style objects (StyleResourceValue)
- @Nullable
- private Map<Integer, StyleResourceValue> mDynamicIdToStyleMap;
- private Map<StyleResourceValue, Integer> mStyleToDynamicIdMap;
- private int mDynamicIdGenerator = 0x02030000; // Base id for R.style in custom namespace
+ private final Map<Object, Map<ResourceReference, ResourceValue>> mDefaultPropMaps =
+ new IdentityHashMap<>();
+ private final Map<Object, ResourceReference> mDefaultStyleMap = new IdentityHashMap<>();
// cache for TypedArray generated from StyleResourceValue object
private TypedArrayCache mTypedArrayCache;
@@ -185,6 +179,8 @@
private IBinder mBinder;
private PackageManager mPackageManager;
private Boolean mIsThemeAppCompat;
+ private final ResourceNamespace mAppCompatNamespace;
+ private final Map<Key<?>, Object> mUserData = new HashMap<>();
/**
* Some applications that target both pre API 17 and post API 17, set the newer attrs to
@@ -245,6 +241,16 @@
mWindowManager = new WindowManagerImpl(mMetrics);
mDisplayManager = new DisplayManager(this);
+
+ if (mLayoutlibCallback.isResourceNamespacingRequired()) {
+ if (mLayoutlibCallback.hasAndroidXAppCompat()) {
+ mAppCompatNamespace = ResourceNamespace.APPCOMPAT;
+ } else {
+ mAppCompatNamespace = ResourceNamespace.APPCOMPAT_LEGACY;
+ }
+ } else {
+ mAppCompatNamespace = ResourceNamespace.RES_AUTO;
+ }
}
/**
@@ -266,10 +272,15 @@
}
/**
- * Disposes the {@link Resources} singleton.
+ * Disposes the {@link Resources} singleton and the AssetRepository inside BridgeAssetManager.
*/
public void disposeResources() {
Resources_Delegate.disposeSystem();
+
+ // The BridgeAssetManager pointed to by the mAssets field is a long-lived object, but
+ // the AssetRepository is not. To prevent it from leaking clear a reference to it from
+ // the BridgeAssetManager.
+ mAssets.releaseAssetRepository();
}
public void setBridgeInflater(BridgeInflater inflater) {
@@ -308,10 +319,14 @@
return mRenderResources;
}
- public Map<Object, PropertiesMap> getDefaultProperties() {
+ public Map<Object, Map<ResourceReference, ResourceValue>> getDefaultProperties() {
return mDefaultPropMaps;
}
+ public Map<Object, ResourceReference> getDefaultNamespacedStyles() {
+ return mDefaultStyleMap;
+ }
+
public Configuration getConfiguration() {
return mConfig;
}
@@ -357,19 +372,16 @@
}
public boolean resolveThemeAttribute(int resId, TypedValue outValue, boolean resolveRefs) {
- Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(resId);
- boolean isFrameworkRes = true;
+ ResourceReference resourceInfo = Bridge.resolveResourceId(resId);
if (resourceInfo == null) {
resourceInfo = mLayoutlibCallback.resolveResourceId(resId);
- isFrameworkRes = false;
}
- if (resourceInfo == null) {
+ if (resourceInfo == null || resourceInfo.getResourceType() != ResourceType.ATTR) {
return false;
}
- ResourceValue value = mRenderResources.findItemInTheme(resourceInfo.getSecond(),
- isFrameworkRes);
+ ResourceValue value = mRenderResources.findItemInTheme(resourceInfo);
if (resolveRefs) {
value = mRenderResources.resolveResValue(value);
}
@@ -395,20 +407,9 @@
else if (stringValue.charAt(0) == '@') {
outValue.type = TypedValue.TYPE_REFERENCE;
}
-
}
- int a;
- // if this is a framework value.
- if (value.isFramework()) {
- // look for idName in the android R classes.
- // use 0 a default res value as it's not a valid id value.
- a = getFrameworkResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/);
- } else {
- // look for idName in the project R class.
- // use 0 a default res value as it's not a valid id value.
- a = getProjectResourceValue(value.getResourceType(), value.getName(), 0 /*defValue*/);
- }
+ int a = getResourceId(value.asReference(), 0 /*defValue*/);
if (a != 0) {
outValue.resourceId = a;
@@ -423,10 +424,10 @@
public ResourceReference resolveId(int id) {
// first get the String related to this id in the framework
- Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id);
+ ResourceReference resourceInfo = Bridge.resolveResourceId(id);
if (resourceInfo != null) {
- return new ResourceReference(resourceInfo.getSecond(), true);
+ return resourceInfo;
}
// didn't find a match in the framework? look in the project.
@@ -434,30 +435,29 @@
resourceInfo = mLayoutlibCallback.resolveResourceId(id);
if (resourceInfo != null) {
- return new ResourceReference(resourceInfo.getSecond(), false);
+ return resourceInfo;
}
}
- // The base value for R.style is 0x01030000 and the custom style is 0x02030000.
- // So, if the second byte is 03, it's probably a style.
- if ((id >> 16 & 0xFF) == 0x03) {
- return getStyleByDynamicId(id);
- }
return null;
}
- public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent,
+ public Pair<View, Boolean> inflateView(ResourceReference layout, ViewGroup parent,
@SuppressWarnings("SameParameterValue") boolean attachToRoot,
boolean skipCallbackParser) {
- boolean isPlatformLayout = resource.isFramework();
+ boolean isPlatformLayout = layout.getNamespace().equals(ResourceNamespace.ANDROID);
if (!isPlatformLayout && !skipCallbackParser) {
// check if the project callback can provide us with a custom parser.
- ILayoutPullParser parser = getParser(resource);
+ ILayoutPullParser parser = null;
+ ResourceValue layoutValue = mRenderResources.getResolvedResource(layout);
+ if (layoutValue != null) {
+ parser = getLayoutlibCallback().getParser(layoutValue);
+ }
if (parser != null) {
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
- this, resource.isFramework());
+ BridgeXmlBlockParser blockParser =
+ new BridgeXmlBlockParser(parser, this, layout.getNamespace());
try {
pushParser(blockParser);
return Pair.of(
@@ -469,58 +469,42 @@
}
}
- ResourceValue resValue;
- if (resource instanceof ResourceValue) {
- resValue = (ResourceValue) resource;
- } else {
- if (isPlatformLayout) {
- resValue = mRenderResources.getFrameworkResource(ResourceType.LAYOUT,
- resource.getName());
- } else {
- resValue = mRenderResources.getProjectResource(ResourceType.LAYOUT,
- resource.getName());
- }
- }
+ ResourceValue resValue = mRenderResources.getResolvedResource(layout);
if (resValue != null) {
+ String path = resValue.getValue();
+ // We need to create a pull parser around the layout XML file, and then
+ // give that to our XmlBlockParser.
+ try {
+ XmlPullParser parser = ParserFactory.create(path, true);
+ if (parser != null) {
+ // Set the layout ref to have correct view cookies.
+ mBridgeInflater.setResourceReference(layout);
- File xml = new File(resValue.getValue());
- if (xml.isFile()) {
- // we need to create a pull parser around the layout XML file, and then
- // give that to our XmlBlockParser
- try {
- XmlPullParser parser = ParserFactory.create(xml, true);
-
- // set the resource ref to have correct view cookies
- mBridgeInflater.setResourceReference(resource);
-
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser,
- this, resource.isFramework());
+ BridgeXmlBlockParser blockParser =
+ new BridgeXmlBlockParser(parser, this, layout.getNamespace());
try {
pushParser(blockParser);
- return Pair.of(
- mBridgeInflater.inflate(blockParser, parent, attachToRoot),
+ return Pair.of(mBridgeInflater.inflate(blockParser, parent, attachToRoot),
Boolean.FALSE);
} finally {
popParser();
}
- } catch (XmlPullParserException e) {
+ } else {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + xml, e, null /*data*/);
- // we'll return null below.
- } catch (FileNotFoundException e) {
- // this shouldn't happen since we check above.
- } finally {
- mBridgeInflater.setResourceReference(null);
+ String.format("File %s is missing!", path), null);
}
- } else {
+ } catch (XmlPullParserException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- String.format("File %s is missing!", xml), null);
+ "Failed to parse file " + path, e, null /*data*/);
+ // we'll return null below.
+ } finally {
+ mBridgeInflater.setResourceReference(null);
}
} else {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
String.format("Layout %s%s does not exist.", isPlatformLayout ? "android:" : "",
- resource.getName()), null);
+ layout.getName()), null);
}
return Pair.of(null, Boolean.FALSE);
@@ -556,17 +540,6 @@
return isThemeAppCompat;
}
- @SuppressWarnings("deprecation")
- private ILayoutPullParser getParser(ResourceReference resource) {
- ILayoutPullParser parser;
- if (resource instanceof ResourceValue) {
- parser = mLayoutlibCallback.getParser((ResourceValue) resource);
- } else {
- parser = mLayoutlibCallback.getParser(resource.getName());
- }
- return parser;
- }
-
// ------------ Context methods
@Override
@@ -638,6 +611,7 @@
case AUTOFILL_MANAGER_SERVICE:
case AUDIO_SERVICE:
case TEXT_CLASSIFICATION_SERVICE:
+ case CONTENT_CAPTURE_MANAGER_SERVICE:
return null;
default:
assert false : "Unsupported Service: " + service;
@@ -651,7 +625,6 @@
return SystemServiceRegistry_Accessor.getSystemServiceName(serviceClass);
}
-
/**
* Same as Context#obtainStyledAttributes. We do not override the base method to give the
* original Context the chance to override the theme when needed.
@@ -668,7 +641,7 @@
// In some cases, style may not be a dynamic id, so we do a full search.
ResourceReference ref = resolveId(resId);
if (ref != null) {
- style = mRenderResources.getStyle(ref.getName(), ref.isFramework());
+ style = mRenderResources.getStyle(ref);
}
}
@@ -685,7 +658,7 @@
List<StyleResourceValue> currentThemes = mRenderResources.getAllThemes();
- Pair<BridgeTypedArray, PropertiesMap> typeArrayAndPropertiesPair =
+ Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>> typeArrayAndPropertiesPair =
mTypedArrayCache.get(attrs, currentThemes, resId);
if (typeArrayAndPropertiesPair == null) {
@@ -697,7 +670,7 @@
BridgeXmlBlockParser parser = getCurrentParser();
Object key = parser != null ? parser.getViewCookie() : null;
if (key != null) {
- PropertiesMap defaultPropMap = mDefaultPropMaps.get(key);
+ Map<ResourceReference, ResourceValue> defaultPropMap = mDefaultPropMaps.get(key);
if (defaultPropMap == null) {
defaultPropMap = typeArrayAndPropertiesPair.getSecond();
mDefaultPropMaps.put(key, defaultPropMap);
@@ -717,71 +690,65 @@
public BridgeTypedArray internalObtainStyledAttributes(@Nullable AttributeSet set, int[] attrs,
int defStyleAttr, int defStyleRes) {
- PropertiesMap defaultPropMap = null;
- boolean isPlatformFile = true;
+ Map<ResourceReference, ResourceValue> defaultPropMap = null;
+ Object key = null;
- // TODO(namespaces): We need to figure out how to keep track of the namespace of the current
- // layout file.
- ResourceNamespace currentFileNamespace = ResourceNamespace.TODO;
-
- // TODO(namespaces): get this through the callback, only in non-namespaced projects.
- ResourceNamespace.Resolver resolver = LEGACY_NAMESPACE_RESOLVER;
+ ResourceNamespace currentFileNamespace;
+ ResourceNamespace.Resolver resolver;
// Hint: for XmlPullParser, attach source //DEVICE_SRC/dalvik/libcore/xml/src/java
if (set instanceof BridgeXmlBlockParser) {
BridgeXmlBlockParser parser;
parser = (BridgeXmlBlockParser)set;
- isPlatformFile = parser.isPlatformFile();
-
- Object key = parser.getViewCookie();
+ key = parser.getViewCookie();
if (key != null) {
- defaultPropMap = mDefaultPropMaps.computeIfAbsent(key, k -> new PropertiesMap());
+ defaultPropMap = mDefaultPropMaps.computeIfAbsent(key, k -> new HashMap<>());
}
- resolver = parser::getNamespace;
- currentFileNamespace = ResourceNamespace.fromBoolean(parser.isPlatformFile());
+ currentFileNamespace = parser.getFileResourceNamespace();
+ resolver = new XmlPullParserResolver(parser, mLayoutlibCallback.getImplicitNamespaces());
} else if (set instanceof BridgeLayoutParamsMapAttributes) {
- // this is only for temp layout params generated dynamically, so this is never
- // platform content.
- isPlatformFile = false;
- } else if (set != null) { // null parser is ok
+ // This is for temp layout params generated dynamically in MockView. The set contains
+ // hardcoded values and we don't need to worry about resolving them.
+ currentFileNamespace = ResourceNamespace.RES_AUTO;
+ resolver = Resolver.EMPTY_RESOLVER;
+ } else if (set != null) {
// really this should not be happening since its instantiated in Bridge
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
"Parser is not a BridgeXmlBlockParser!", null);
return null;
+ } else {
+ // `set` is null, so there will be no values to resolve.
+ currentFileNamespace = ResourceNamespace.RES_AUTO;
+ resolver = Resolver.EMPTY_RESOLVER;
}
List<AttributeHolder> attributeList = searchAttrs(attrs);
BridgeTypedArray ta =
- Resources_Delegate.newTypeArray(mSystemResources, attrs.length, isPlatformFile);
+ Resources_Delegate.newTypeArray(mSystemResources, attrs.length);
- // look for a custom style.
- String customStyle = null;
- if (set != null) {
- customStyle = set.getAttributeValue(null, "style");
- }
-
+ // Look for a custom style.
StyleResourceValue customStyleValues = null;
- if (customStyle != null) {
- ResourceValue item = mRenderResources.findResValue(customStyle,
- isPlatformFile /*forceFrameworkOnly*/);
+ if (set != null) {
+ String customStyle = set.getAttributeValue(null, "style");
+ if (customStyle != null) {
+ ResourceValue resolved = mRenderResources.resolveResValue(
+ new UnresolvedResourceValue(customStyle, currentFileNamespace, resolver));
- // resolve it in case it links to something else
- item = mRenderResources.resolveResValue(item);
-
- if (item instanceof StyleResourceValue) {
- customStyleValues = (StyleResourceValue)item;
+ if (resolved instanceof StyleResourceValue) {
+ customStyleValues = (StyleResourceValue) resolved;
+ }
}
}
- // resolve the defStyleAttr value into a IStyleResourceValue
+ // resolve the defStyleAttr value into a StyleResourceValue
StyleResourceValue defStyleValues = null;
if (defStyleAttr != 0) {
// get the name from the int.
- Pair<String, Boolean> defStyleAttribute = searchAttr(defStyleAttr);
+ ResourceReference defStyleAttribute = searchAttr(defStyleAttr);
if (defStyleAttribute == null) {
// This should be rare. Happens trying to map R.style.foo to @style/foo fails.
@@ -790,25 +757,18 @@
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
"Failed to find the style corresponding to the id " + defStyleAttr, null);
} else {
- String defStyleName = defStyleAttribute.getFirst();
-
// look for the style in the current theme, and its parent:
- ResourceValue item = mRenderResources.findItemInTheme(defStyleName,
- defStyleAttribute.getSecond());
+ ResourceValue item = mRenderResources.findItemInTheme(defStyleAttribute);
if (item != null) {
+ if (key != null) {
+ mDefaultStyleMap.put(key, defStyleAttribute);
+ }
// item is a reference to a style entry. Search for it.
- item = mRenderResources.findResValue(item.getValue(), item.isFramework());
item = mRenderResources.resolveResValue(item);
if (item instanceof StyleResourceValue) {
defStyleValues = (StyleResourceValue) item;
}
- if (defaultPropMap != null) {
- if (defStyleAttribute.getSecond()) {
- defStyleName = "android:" + defStyleName;
- }
- defaultPropMap.put("style", new Property(defStyleName, item.getValue()));
- }
}
}
}
@@ -818,21 +778,18 @@
if (item != null) {
defStyleValues = item;
} else {
- boolean isFrameworkRes = true;
- Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes);
+ ResourceReference value = Bridge.resolveResourceId(defStyleRes);
if (value == null) {
value = mLayoutlibCallback.resolveResourceId(defStyleRes);
- isFrameworkRes = false;
}
if (value != null) {
- if ((value.getFirst() == ResourceType.STYLE)) {
+ if ((value.getResourceType() == ResourceType.STYLE)) {
// look for the style in all resources:
- item = mRenderResources.getStyle(value.getSecond(), isFrameworkRes);
+ item = mRenderResources.getStyle(value);
if (item != null) {
- if (defaultPropMap != null) {
- String name = item.getName();
- defaultPropMap.put("style", new Property(name, name));
+ if (key != null) {
+ mDefaultStyleMap.put(key, item.asReference());
}
defStyleValues = item;
@@ -840,14 +797,14 @@
Bridge.getLog().error(null,
String.format(
"Style with id 0x%x (resolved to '%s') does not exist.",
- defStyleRes, value.getSecond()),
+ defStyleRes, value.getName()),
null);
}
} else {
Bridge.getLog().error(null,
String.format(
"Resource id 0x%x is not of type STYLE (instead %s)",
- defStyleRes, value.getFirst().toString()),
+ defStyleRes, value.getResourceType().name()),
null);
}
} else {
@@ -860,8 +817,6 @@
}
}
- String appNamespace = mLayoutlibCallback.getNamespace();
-
if (attributeList != null) {
for (int index = 0 ; index < attributeList.size() ; index++) {
AttributeHolder attributeHolder = attributeList.get(index);
@@ -870,17 +825,15 @@
continue;
}
- String attrName = attributeHolder.name;
- boolean frameworkAttr = attributeHolder.isFramework;
+ String attrName = attributeHolder.getName();
String value = null;
if (set != null) {
value = set.getAttributeValue(
- frameworkAttr ? BridgeConstants.NS_RESOURCES : appNamespace,
- attrName);
+ attributeHolder.getNamespace().getXmlNamespaceUri(), attrName);
// if this is an app attribute, and the first get fails, try with the
// new res-auto namespace as well
- if (!frameworkAttr && value == null) {
+ if (attributeHolder.getNamespace() != ResourceNamespace.ANDROID && value == null) {
value = set.getAttributeValue(BridgeConstants.NS_APP_RES_AUTO, attrName);
}
}
@@ -893,42 +846,41 @@
ResourceValue defaultValue = null;
if (defaultPropMap != null || value == null) {
// look for the value in the custom style first (and its parent if needed)
+ ResourceReference attrRef = attributeHolder.asReference();
if (customStyleValues != null) {
- defaultValue = mRenderResources.findItemInStyle(customStyleValues, attrName,
- frameworkAttr);
+ defaultValue =
+ mRenderResources.findItemInStyle(customStyleValues, attrRef);
}
// then look for the value in the default Style (and its parent if needed)
if (defaultValue == null && defStyleValues != null) {
- defaultValue = mRenderResources.findItemInStyle(defStyleValues, attrName,
- frameworkAttr);
+ defaultValue =
+ mRenderResources.findItemInStyle(defStyleValues, attrRef);
}
// if the item is not present in the defStyle, we look in the main theme (and
// its parent themes)
if (defaultValue == null) {
- defaultValue = mRenderResources.findItemInTheme(attrName, frameworkAttr);
+ defaultValue =
+ mRenderResources.findItemInTheme(attrRef);
}
// if we found a value, we make sure this doesn't reference another value.
// So we resolve it.
if (defaultValue != null) {
- String preResolve = defaultValue.getValue();
- defaultValue = mRenderResources.resolveResValue(defaultValue);
-
if (defaultPropMap != null) {
- defaultPropMap.put(
- frameworkAttr ? SdkConstants.PREFIX_ANDROID + attrName :
- attrName, new Property(preResolve, defaultValue.getValue()));
+ defaultPropMap.put(attrRef, defaultValue);
}
+
+ defaultValue = mRenderResources.resolveResValue(defaultValue);
}
}
- // Done calculating the defaultValue
+ // Done calculating the defaultValue.
- // if there's no direct value for this attribute in the XML, we look for default
+ // If there's no direct value for this attribute in the XML, we look for default
// values in the widget defStyle, and then in the theme.
if (value == null) {
- if (frameworkAttr) {
+ if (attributeHolder.getNamespace() == ResourceNamespace.ANDROID) {
// For some framework values, layoutlib patches the actual value in the
// theme when it helps to improve the final preview. In most cases
// we just disable animations.
@@ -938,7 +890,7 @@
}
}
- // if we found a value, we make sure this doesn't reference another value.
+ // If we found a value, we make sure this doesn't reference another value.
// So we resolve it.
if (defaultValue != null) {
// If the value is a reference to another theme attribute that doesn't
@@ -949,13 +901,17 @@
// fail to resolve when using old themes (they haven't been backported).
// Since this is an artifact caused by us using always the latest
// code, we check for some of those values and replace them here.
+ ResourceReference reference = defaultValue.getReference();
defaultValue = FRAMEWORK_REPLACE_VALUES.get(attrName);
+ // Only log a warning if the referenced value isn't one of the RTL
+ // attributes, or the app targets old API.
if (defaultValue == null &&
(getApplicationInfo().targetSdkVersion < JELLY_BEAN_MR1 ||
!attrName.equals(RTL_ATTRS.get(val)))) {
- // Only log a warning if the referenced value isn't one of the RTL
- // attributes, or the app targets old API.
+ if (reference != null) {
+ val = reference.getResourceUrl().toString();
+ }
Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
String.format("Failed to find '%s' in current theme.", val),
val);
@@ -963,22 +919,21 @@
}
}
- ta.bridgeSetValue(index, attrName, frameworkAttr, attributeHolder.resourceId,
+ ta.bridgeSetValue(
+ index,
+ attrName, attributeHolder.getNamespace(),
+ attributeHolder.getResourceId(),
defaultValue);
} else {
// There is a value in the XML, but we need to resolve it in case it's
// referencing another resource or a theme value.
- ResourceValue dummy =
- new ResourceValue(
- currentFileNamespace,
- null,
- attrName,
- value);
- dummy.setNamespaceLookup(resolver);
-
ta.bridgeSetValue(
- index, attrName, frameworkAttr, attributeHolder.resourceId,
- mRenderResources.resolveResValue(dummy));
+ index,
+ attrName, attributeHolder.getNamespace(),
+ attributeHolder.getResourceId(),
+ mRenderResources.resolveResValue(
+ new UnresolvedResourceValue(
+ value, currentFileNamespace, resolver)));
}
}
}
@@ -1019,14 +974,14 @@
*
* @see #obtainStyledAttributes(int, int[])
*/
- private Pair<BridgeTypedArray, PropertiesMap> createStyleBasedTypedArray(
+ private Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>> createStyleBasedTypedArray(
@Nullable StyleResourceValue style, int[] attrs) throws Resources.NotFoundException {
List<AttributeHolder> attributes = searchAttrs(attrs);
BridgeTypedArray ta =
- Resources_Delegate.newTypeArray(mSystemResources, attrs.length, false);
+ Resources_Delegate.newTypeArray(mSystemResources, attrs.length);
- PropertiesMap defaultPropMap = new PropertiesMap();
+ Map<ResourceReference, ResourceValue> defaultPropMap = new HashMap<>();
// for each attribute, get its name so that we can search it in the style
for (int i = 0; i < attrs.length; i++) {
AttributeHolder attrHolder = attributes.get(i);
@@ -1034,24 +989,20 @@
if (attrHolder != null) {
// look for the value in the given style
ResourceValue resValue;
- String attrName = attrHolder.name;
- boolean frameworkAttr = attrHolder.isFramework;
if (style != null) {
- resValue = mRenderResources.findItemInStyle(style, attrName, frameworkAttr);
+ resValue = mRenderResources.findItemInStyle(style, attrHolder.asReference());
} else {
- resValue = mRenderResources.findItemInTheme(attrName, frameworkAttr);
+ resValue = mRenderResources.findItemInTheme(attrHolder.asReference());
}
if (resValue != null) {
- // Add it to defaultPropMap before resolving
- String preResolve = resValue.getValue();
+ defaultPropMap.put(attrHolder.asReference(), resValue);
// resolve it to make sure there are no references left.
resValue = mRenderResources.resolveResValue(resValue);
- ta.bridgeSetValue(i, attrName, frameworkAttr, attrHolder.resourceId,
+ ta.bridgeSetValue(
+ i, attrHolder.getName(), attrHolder.getNamespace(),
+ attrHolder.getResourceId(),
resValue);
- defaultPropMap.put(
- frameworkAttr ? SdkConstants.ANDROID_PREFIX + attrName : attrName,
- new Property(preResolve, resValue.getValue()));
}
}
}
@@ -1074,16 +1025,13 @@
// for each attribute, get its name so that we can search it in the style
for (int id : attributeIds) {
- Pair<ResourceType, String> resolvedResource = Bridge.resolveResourceId(id);
- boolean isFramework = false;
- if (resolvedResource != null) {
- isFramework = true;
- } else {
- resolvedResource = mLayoutlibCallback.resolveResourceId(id);
+ ResourceReference refForId = Bridge.resolveResourceId(id);
+ if (refForId == null) {
+ refForId = mLayoutlibCallback.resolveResourceId(id);
}
- if (resolvedResource != null) {
- results.add(new AttributeHolder(id, resolvedResource.getSecond(), isFramework));
+ if (refForId != null) {
+ results.add(new AttributeHolder(id, refForId));
} else {
results.add(null);
}
@@ -1094,74 +1042,61 @@
/**
* Searches for the attribute referenced by its internal id.
- *
- * @param attr An attribute reference given to obtainStyledAttributes such as defStyle.
- * @return A (name, isFramework) pair describing the attribute if found. Returns null
- * if nothing is found.
*/
- private Pair<String, Boolean> searchAttr(int attr) {
- Pair<ResourceType, String> info = Bridge.resolveResourceId(attr);
- if (info != null) {
- return Pair.of(info.getSecond(), Boolean.TRUE);
+ private ResourceReference searchAttr(int attrId) {
+ ResourceReference attr = Bridge.resolveResourceId(attrId);
+ if (attr == null) {
+ attr = mLayoutlibCallback.resolveResourceId(attrId);
}
- info = mLayoutlibCallback.resolveResourceId(attr);
- if (info != null) {
- return Pair.of(info.getSecond(), Boolean.FALSE);
- }
-
- return null;
+ return attr;
}
+ /**
+ * Maps a given style to a numeric id.
+ *
+ * <p>For now Bridge handles numeric ids (both fixed and dynamic) for framework and the callback
+ * for non-framework. TODO(namespaces): teach the IDE about fixed framework ids and handle this
+ * all in the callback.
+ */
public int getDynamicIdByStyle(StyleResourceValue resValue) {
- if (mDynamicIdToStyleMap == null) {
- // create the maps.
- mDynamicIdToStyleMap = new HashMap<>();
- mStyleToDynamicIdMap = new HashMap<>();
+ if (resValue.isFramework()) {
+ return Bridge.getResourceId(resValue.getResourceType(), resValue.getName());
+ } else {
+ return mLayoutlibCallback.getOrGenerateResourceId(resValue.asReference());
}
-
- // look for an existing id
- Integer id = mStyleToDynamicIdMap.get(resValue);
-
- if (id == null) {
- // generate a new id
- id = ++mDynamicIdGenerator;
-
- // and add it to the maps.
- mDynamicIdToStyleMap.put(id, resValue);
- mStyleToDynamicIdMap.put(resValue, id);
- }
-
- return id;
}
- private StyleResourceValue getStyleByDynamicId(int i) {
- if (mDynamicIdToStyleMap != null) {
- return mDynamicIdToStyleMap.get(i);
+ /**
+ * Maps a numeric id back to {@link StyleResourceValue}.
+ *
+ * <p>For now framework numeric ids are handled by Bridge, so try there first and fall back to
+ * the callback, which manages ids for non-framework resources. TODO(namespaces): manage all
+ * ids in the IDE.
+ *
+ * <p>Once we the resource for the given id, we ask the IDE to get the
+ * {@link StyleResourceValue} for it.
+ */
+ @Nullable
+ private StyleResourceValue getStyleByDynamicId(int id) {
+ ResourceReference reference = Bridge.resolveResourceId(id);
+ if (reference == null) {
+ reference = mLayoutlibCallback.resolveResourceId(id);
}
- return null;
- }
-
- public int getFrameworkResourceValue(ResourceType resType, String resName, int defValue) {
- if (getRenderResources().getFrameworkResource(resType, resName) != null) {
- // Bridge.getResourceId creates a new resource id if an existing one isn't found. So,
- // we check for the existence of the resource before calling it.
- return Bridge.getResourceId(resType, resName);
+ if (reference == null) {
+ return null;
}
- return defValue;
+ return mRenderResources.getStyle(reference);
}
- public int getProjectResourceValue(ResourceType resType, String resName, int defValue) {
- // getResourceId creates a new resource id if an existing resource id isn't found. So, we
- // check for the existence of the resource before calling it.
- if (getRenderResources().getProjectResource(resType, resName) != null) {
- if (mLayoutlibCallback != null) {
- Integer value = mLayoutlibCallback.getResourceId(resType, resName);
- if (value != null) {
- return value;
- }
+ public int getResourceId(@NonNull ResourceReference resource, int defValue) {
+ if (getRenderResources().getUnresolvedResource(resource) != null) {
+ if (resource.getNamespace().equals(ResourceNamespace.ANDROID)) {
+ return Bridge.getResourceId(resource.getResourceType(), resource.getName());
+ } else if (mLayoutlibCallback != null) {
+ return mLayoutlibCallback.getOrGenerateResourceId(resource);
}
}
@@ -1175,6 +1110,40 @@
return context;
}
+ /**
+ * Returns the Framework attr resource reference with the given name.
+ */
+ @NonNull
+ public static ResourceReference createFrameworkAttrReference(@NonNull String name) {
+ return createFrameworkResourceReference(ResourceType.ATTR, name);
+ }
+
+ /**
+ * Returns the Framework resource reference with the given type and name.
+ */
+ @NonNull
+ public static ResourceReference createFrameworkResourceReference(@NonNull ResourceType type,
+ @NonNull String name) {
+ return new ResourceReference(ResourceNamespace.ANDROID, type, name);
+ }
+
+ /**
+ * Returns the AppCompat attr resource reference with the given name.
+ */
+ @NonNull
+ public ResourceReference createAppCompatAttrReference(@NonNull String name) {
+ return createAppCompatResourceReference(ResourceType.ATTR, name);
+ }
+
+ /**
+ * Returns the AppCompat resource reference with the given type and name.
+ */
+ @NonNull
+ public ResourceReference createAppCompatResourceReference(@NonNull ResourceType type,
+ @NonNull String name) {
+ return new ResourceReference(mAppCompatNamespace, type, name);
+ }
+
public IBinder getBinder() {
if (mBinder == null) {
// create a dummy binder. We only need it be not null.
@@ -1244,6 +1213,17 @@
}
@Override
+ public boolean bindService(Intent arg0, int arg1, Executor arg2, ServiceConnection arg3) {
+ return false;
+ }
+
+ @Override
+ public boolean bindIsolatedService(Intent arg0,
+ int arg1, String arg2, Executor arg3, ServiceConnection arg4) {
+ return false;
+ }
+
+ @Override
public int checkCallingOrSelfPermission(String arg0) {
// pass
return 0;
@@ -1904,6 +1884,12 @@
}
@Override
+ public void updateServiceGroup(@NonNull ServiceConnection conn, int group,
+ int importance) {
+ // pass
+ }
+
+ @Override
public void unbindService(ServiceConnection arg0) {
// pass
@@ -1956,6 +1942,12 @@
}
@Override
+ public int getDisplayId() {
+ // pass
+ return 0;
+ }
+
+ @Override
public void updateDisplay(int displayId) {
// pass
}
@@ -2034,15 +2026,66 @@
return true;
}
- private class AttributeHolder {
- private int resourceId;
- private String name;
- private boolean isFramework;
+ public <T> void putUserData(@NonNull Key<T> key, @Nullable T data) {
+ mUserData.put(key, data);
+ }
- private AttributeHolder(int resourceId, String name, boolean isFramework) {
- this.resourceId = resourceId;
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T> T getUserData(@NonNull Key<T> key) {
+ return (T) mUserData.get(key);
+ }
+
+ /**
+ * No two Key instances are considered equal.
+ *
+ * @param <T> the type of values associated with the key
+ */
+ public static final class Key<T> {
+ private final String name;
+
+ @NonNull
+ public static <T> Key<T> create(@NonNull String name) {
+ return new Key<T>(name);
+ }
+
+ private Key(@NonNull String name) {
this.name = name;
- this.isFramework = isFramework;
+ }
+
+ /** For debugging only. */
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ private class AttributeHolder {
+ private final int resourceId;
+ @NonNull private final ResourceReference reference;
+
+ private AttributeHolder(int resourceId, @NonNull ResourceReference reference) {
+ this.resourceId = resourceId;
+ this.reference = reference;
+ }
+
+ @NonNull
+ private ResourceReference asReference() {
+ return reference;
+ }
+
+ private int getResourceId() {
+ return resourceId;
+ }
+
+ @NonNull
+ private String getName() {
+ return reference.getName();
+ }
+
+ @NonNull
+ private ResourceNamespace getNamespace() {
+ return reference.getNamespace();
}
}
@@ -2064,18 +2107,20 @@
private Map<int[],
Map<List<StyleResourceValue>,
- Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>> mCache;
+ Map<Integer, Pair<BridgeTypedArray,
+ Map<ResourceReference, ResourceValue>>>>> mCache;
private TypedArrayCache() {
mCache = new IdentityHashMap<>();
}
- public Pair<BridgeTypedArray, PropertiesMap> get(int[] attrs,
+ public Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>> get(int[] attrs,
List<StyleResourceValue> themes, int resId) {
- Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>
+ Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, Map<ResourceReference,
+ ResourceValue>>>>
cacheFromThemes = mCache.get(attrs);
if (cacheFromThemes != null) {
- Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId =
+ Map<Integer, Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>>> cacheFromResId =
cacheFromThemes.get(themes);
if (cacheFromResId != null) {
return cacheFromResId.get(resId);
@@ -2085,10 +2130,11 @@
}
public void put(int[] attrs, List<StyleResourceValue> themes, int resId,
- Pair<BridgeTypedArray, PropertiesMap> value) {
- Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, PropertiesMap>>>
+ Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>> value) {
+ Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, Map<ResourceReference,
+ ResourceValue>>>>
cacheFromThemes = mCache.computeIfAbsent(attrs, k -> new HashMap<>());
- Map<Integer, Pair<BridgeTypedArray, PropertiesMap>> cacheFromResId =
+ Map<Integer, Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>>> cacheFromResId =
cacheFromThemes.computeIfAbsent(themes, k -> new HashMap<>());
cacheFromResId.put(resId, value);
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 73a9a62..b0b09b2 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -152,7 +152,12 @@
}
@Override
- public boolean isPermissionReviewModeEnabled() {
+ public boolean arePermissionsIndividuallyControlled() {
+ return false;
+ }
+
+ @Override
+ public boolean isWirelessConsentModeEnabled() {
return false;
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
index b87ca3b..0b5f14c 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -16,16 +16,17 @@
package com.android.layoutlib.bridge.android;
+import android.os.BatterySaverPolicyConfig;
import android.os.IBinder;
import android.os.IPowerManager;
import android.os.PowerManager;
+import android.os.PowerManager.WakeReason;
import android.os.PowerSaveState;
import android.os.RemoteException;
import android.os.WorkSource;
/**
* Fake implementation of IPowerManager.
- *
*/
public class BridgePowerManager implements IPowerManager {
@@ -40,10 +41,32 @@
}
@Override
- public boolean setPowerSaveMode(boolean mode) throws RemoteException {
+ public boolean setPowerSaveModeEnabled(boolean mode) throws RemoteException {
return false;
}
+ @Override
+ public boolean setDynamicPowerSaveHint(boolean powerSaveHint, int disableThreshold)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean setAdaptivePowerSavePolicy(BatterySaverPolicyConfig config)
+ throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public boolean setAdaptivePowerSaveEnabled(boolean enabled) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int getPowerSaveModeTrigger() {
+ return 0;
+ }
+
public PowerSaveState getPowerSaveState(int serviceType) {
return null;
}
@@ -138,7 +161,8 @@
}
@Override
- public void wakeUp(long time, String reason, String opPackageName) throws RemoteException {
+ public void wakeUp(long time, @WakeReason int reason, String details , String opPackageName)
+ throws RemoteException {
// pass for now.
}
@@ -168,7 +192,17 @@
}
@Override
+ public int getLastSleepReason() {
+ return PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
+ }
+
+ @Override
public void setDozeAfterScreenOff(boolean mode) throws RemoteException {
// pass for now.
}
+
+ @Override
+ public boolean forceSuspend() {
+ return false;
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
index d50117e..0269352 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.layoutlib.bridge.android;
-
import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.impl.ParserFactory;
import org.xmlpull.v1.XmlPullParser;
@@ -28,6 +28,7 @@
import android.content.res.XmlResourceParser;
import android.util.AttributeSet;
import android.util.BridgeXmlPullAttributes;
+import android.util.ResolvingAttributeSet;
import java.io.IOException;
import java.io.InputStream;
@@ -38,12 +39,11 @@
* It delegates to both an instance of {@link XmlPullParser} and an instance of
* XmlPullAttributes (for the {@link AttributeSet} part).
*/
-public class BridgeXmlBlockParser implements XmlResourceParser {
-
- private final XmlPullParser mParser;
- private final AttributeSet mAttrib;
- private final BridgeContext mContext;
- private final boolean mPlatformFile;
+public class BridgeXmlBlockParser implements XmlResourceParser, ResolvingAttributeSet {
+ @NonNull private final XmlPullParser mParser;
+ @NonNull private final ResolvingAttributeSet mAttrib;
+ @Nullable private final BridgeContext mContext;
+ @NonNull private final ResourceNamespace mFileResourceNamespace;
private boolean mStarted = false;
private int mEventType = START_DOCUMENT;
@@ -52,22 +52,24 @@
/**
* Builds a {@link BridgeXmlBlockParser}.
- * @param parser The XmlPullParser to get the content from.
+ * @param parser XmlPullParser to get the content from.
* @param context the Context.
- * @param platformFile Indicates whether the the file is a platform file or not.
+ * @param fileNamespace namespace of the file being parsed.
*/
- public BridgeXmlBlockParser(@NonNull XmlPullParser parser, @Nullable BridgeContext context,
- boolean platformFile) {
+ public BridgeXmlBlockParser(
+ @NonNull XmlPullParser parser,
+ @Nullable BridgeContext context,
+ @NonNull ResourceNamespace fileNamespace) {
if (ParserFactory.LOG_PARSER) {
System.out.println("CRTE " + parser.toString());
}
mParser = parser;
mContext = context;
- mPlatformFile = platformFile;
+ mFileResourceNamespace = fileNamespace;
if (mContext != null) {
- mAttrib = new BridgeXmlPullAttributes(parser, context, mPlatformFile);
+ mAttrib = new BridgeXmlPullAttributes(parser, context, mFileResourceNamespace);
mContext.pushParser(this);
mPopped = false;
}
@@ -80,8 +82,9 @@
return mParser;
}
- public boolean isPlatformFile() {
- return mPlatformFile;
+ @NonNull
+ public ResourceNamespace getFileResourceNamespace() {
+ return mFileResourceNamespace;
}
public Object getViewCookie() {
@@ -309,10 +312,6 @@
if (ev == END_TAG && mParser.getDepth() == 1) {
// done with parser remove it from the context stack.
ensurePopped();
-
- if (ParserFactory.LOG_PARSER) {
- System.out.println("");
- }
}
mEventType = ev;
@@ -493,4 +492,10 @@
return mAttrib.getStyleAttribute();
}
+ @Override
+ @Nullable
+ public ResourceValue getResolvedAttributeValue(@Nullable String namespace,
+ @NonNull String name) {
+ return mAttrib.getResolvedAttributeValue(namespace, name);
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/NopAttributeSet.java b/bridge/src/com/android/layoutlib/bridge/android/NopAttributeSet.java
index cd971ee..86ec798 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/NopAttributeSet.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/NopAttributeSet.java
@@ -13,15 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.layoutlib.bridge.android;
-import android.util.AttributeSet;
+import com.android.ide.common.rendering.api.ResourceValue;
+
+import android.util.ResolvingAttributeSet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
/**
* Empty {@link AttributeSet}
*/
-class NopAttributeSet implements AttributeSet {
+class NopAttributeSet implements ResolvingAttributeSet {
@Override
public int getAttributeCount() {
return 0;
@@ -140,4 +144,11 @@
public int getStyleAttribute() {
return 0;
}
+
+ @Override
+ @Nullable
+ public ResourceValue getResolvedAttributeValue(@Nullable String namespace,
+ @NonNull String name) {
+ return null;
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java b/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
index a20652f..a2f5976 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/RenderParamsFlags.java
@@ -16,6 +16,7 @@
package com.android.layoutlib.bridge.android;
+import com.android.ide.common.rendering.api.IImageFactory;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderParams;
import com.android.ide.common.rendering.api.SessionParams.Key;
@@ -65,6 +66,27 @@
public static final Key<String> FLAG_KEY_ADAPTIVE_ICON_MASK_PATH =
new Key<>("adaptiveIconMaskPath", String.class);
+ /**
+ * When enabled, Layoutlib will resize the output image to whatever size
+ * is returned by {@link IImageFactory#getImage(int, int)}. The default
+ * behaviour when this is false is to crop the image to the size of the image
+ * returned by {@link IImageFactory#getImage(int, int)}.
+ */
+ public static final Key<Boolean> FLAG_KEY_RESULT_IMAGE_AUTO_SCALE =
+ new Key<Boolean>("enableResultImageAutoScale", Boolean.class);
+
+ /**
+ * Enables Ray Traced shadows in layoutlib.
+ */
+ public static final Key<Boolean> FLAG_RENDER_HIGH_QUALITY_SHADOW =
+ new Key<>("renderHighQualityShadow", Boolean.class);
+
+ /**
+ * Flags to enable shadows in layoutlib.
+ */
+ public static final Key<Boolean> FLAG_ENABLE_SHADOW =
+ new Key<>("enableShadow", Boolean.class);
+
// Disallow instances.
private RenderParamsFlags() {}
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/UnresolvedResourceValue.java b/bridge/src/com/android/layoutlib/bridge/android/UnresolvedResourceValue.java
new file mode 100644
index 0000000..b562db7
--- /dev/null
+++ b/bridge/src/com/android/layoutlib/bridge/android/UnresolvedResourceValue.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 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.layoutlib.bridge.android;
+
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceValueImpl;
+import com.android.resources.ResourceType;
+
+import android.annotation.NonNull;
+
+/**
+ * Special subclass that layoutlib uses to start the resolution process and recognize if the
+ * resolution failed.
+ */
+public class UnresolvedResourceValue extends ResourceValueImpl {
+ public UnresolvedResourceValue(
+ @NonNull String value,
+ @NonNull ResourceNamespace namespace,
+ @NonNull ResourceNamespace.Resolver namespaceResolver) {
+ super(namespace, ResourceType.STRING, "layoutlib", value);
+ setNamespaceResolver(namespaceResolver);
+ }
+}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/XmlPullParserResolver.java b/bridge/src/com/android/layoutlib/bridge/android/XmlPullParserResolver.java
new file mode 100644
index 0000000..7ae5bce
--- /dev/null
+++ b/bridge/src/com/android/layoutlib/bridge/android/XmlPullParserResolver.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 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.layoutlib.bridge.android;
+
+import com.android.ide.common.rendering.api.ResourceNamespace;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * A {@link ResourceNamespace.Resolver} that delegates to the given {@link XmlPullParser} and falls
+ * back to the "implicit" resolver which is assumed to contain namespaces predefined for the file
+ * being parsed.
+ *
+ * <p>Note that the parser will start giving different results as the underlying parser moves in the
+ * input, so it should either be discarded or reused with care.
+ */
+public class XmlPullParserResolver implements ResourceNamespace.Resolver {
+ private final XmlPullParser mParser;
+ private final ResourceNamespace.Resolver mImplicitNamespacesResolver;
+
+ public XmlPullParserResolver(
+ @NonNull XmlPullParser parser,
+ @NonNull ResourceNamespace.Resolver implicitNamespacesResolver) {
+ mParser = parser;
+ mImplicitNamespacesResolver = implicitNamespacesResolver;
+ }
+
+ @Override
+ @Nullable
+ public String prefixToUri(@NonNull String namespacePrefix) {
+ String result = mParser.getNamespace(namespacePrefix);
+ if (result == null) {
+ result = mImplicitNamespacesResolver.prefixToUri(namespacePrefix);
+ }
+
+ return result;
+ }
+
+ @Override
+ @Nullable
+ public String uriToPrefix(@NonNull String namespaceUri) {
+ // This is needed when creating new XML snippets, we don't need to support that.
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java b/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
index aedcc48..0c2ef8b 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
@@ -33,19 +33,19 @@
public class DesignLibUtil {
public static final String[] CN_COORDINATOR_LAYOUT = {
"android.support.design.widget.CoordinatorLayout",
- "androidx.widget.CoordinatorLayout"
+ "androidx.coordinatorlayout.widget.CoordinatorLayout"
};
public static final String[] CN_APPBAR_LAYOUT = {
"android.support.design.widget.AppBarLayout",
- "androidx.design.widget.AppBarLayout"
+ "com.google.android.material.widget.AppBarLayout"
};
public static final String[] CN_COLLAPSING_TOOLBAR_LAYOUT = {
"android.support.design.widget.CollapsingToolbarLayout",
- "androidx.design.widget.CollapsingToolbarLayout"
+ "com.google.android.material.widget.CollapsingToolbarLayout"
};
public static final String[] CN_TOOLBAR = {
"android.support.v7.widget.Toolbar",
- "androidx.widget.Toolbar"
+ "androidx.appcompat.widget.Toolbar"
};
/**
diff --git a/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java b/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java
index 4b9f674..1124a21 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java
@@ -35,7 +35,7 @@
public static final String[] CN_DRAWER_LAYOUT = {
"android.support.v4.widget.DrawerLayout",
- "androidx.widget.DrawerLayout"
+ "androidx.drawerlayout.widget.DrawerLayout"
};
public static void openDrawer(View drawerLayout, @Nullable String drawerGravity) {
diff --git a/bridge/src/com/android/layoutlib/bridge/android/support/FragmentTabHostUtil.java b/bridge/src/com/android/layoutlib/bridge/android/support/FragmentTabHostUtil.java
index 138d914..da2d3ee 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/FragmentTabHostUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/FragmentTabHostUtil.java
@@ -34,12 +34,12 @@
public static final String[] CN_FRAGMENT_TAB_HOST = {
"android.support.v4.app.FragmentTabHost",
- "androidx.app.FragmentTabHost"
+ "androidx.fragment.app.FragmentTabHost"
};
private static final String[] CN_FRAGMENT_MANAGER = {
"android.support.v4.app.FragmentManager",
- "androidx.app.FragmentManager"
+ "androidx.fragment.app.FragmentManager"
};
/**
diff --git a/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
index bf9a7e5..894937c 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
@@ -18,7 +18,6 @@
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.internal.widget.RecyclerView;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
@@ -41,7 +40,7 @@
public class RecyclerViewUtil {
public static final String[] CN_RECYCLER_VIEW = {
"android.support.v7.widget.RecyclerView",
- "androidx.widget.RecyclerView"
+ "androidx.recyclerview.widget.RecyclerView"
};
private static final Class<?>[] LLM_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class};
@@ -55,7 +54,8 @@
*/
public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context,
@NonNull LayoutlibCallback layoutlibCallback, int adapterLayout, int itemCount) {
- String recyclerViewClassName = recyclerView.getClass().getName();
+ String recyclerViewClassName =
+ ReflectionUtils.getParentClass(recyclerView, RecyclerViewUtil.CN_RECYCLER_VIEW);
String adapterClassName = recyclerViewClassName + "$Adapter";
String layoutMgrClassName = recyclerViewClassName + "$LayoutManager";
@@ -98,7 +98,7 @@
@NonNull String linearLayoutMgrClassName, @NonNull LayoutlibCallback callback)
throws ReflectionException {
try {
- return callback.loadView(linearLayoutMgrClassName, LLM_CONSTRUCTOR_SIGNATURE,
+ return callback.loadClass(linearLayoutMgrClassName, LLM_CONSTRUCTOR_SIGNATURE,
new Object[]{context});
} catch (Exception e) {
throw new ReflectionException(e);
@@ -112,14 +112,14 @@
@Nullable
private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback,
- @NonNull String layoutMgrClassName) throws ReflectionException {
+ @NonNull String adapterClassName) throws ReflectionException {
Boolean ideSupport =
layoutlibCallback.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT);
if (ideSupport != Boolean.TRUE) {
return null;
}
try {
- return layoutlibCallback.loadClass(layoutMgrClassName, new Class[0], new Object[0]);
+ return layoutlibCallback.loadClass(adapterClassName, new Class[0], new Object[0]);
} catch (Exception e) {
throw new ReflectionException(e);
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java b/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
index fda4693..171edb9 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
@@ -18,11 +18,15 @@
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -114,14 +118,21 @@
* Returns a themed wrapper context of {@link BridgeContext} with the theme specified in
* ?attr/preferenceTheme applied to it.
*/
- @Nullable
+ @NotNull
private static Context getThemedContext(@NonNull BridgeContext bridgeContext) {
RenderResources resources = bridgeContext.getRenderResources();
- ResourceValue preferenceTheme = resources.findItemInTheme("preferenceTheme", false);
+ ResourceValue preferenceTheme = resources.findItemInTheme(
+ bridgeContext.createAppCompatAttrReference("preferenceTheme"));
if (preferenceTheme != null) {
// resolve it, if needed.
preferenceTheme = resources.resolveResValue(preferenceTheme);
+ } else {
+ // The current theme does not define "preferenceTheme" so we will use the default
+ // "PreferenceThemeOverlay" if available.
+ preferenceTheme = resources.getStyle(
+ bridgeContext.createAppCompatResourceReference(ResourceType.STYLE,
+ "PreferenceThemeOverlay"));
}
if (preferenceTheme instanceof StyleResourceValue) {
int styleId = bridgeContext.getDynamicIdByStyle(((StyleResourceValue) preferenceTheme));
@@ -130,7 +141,9 @@
}
}
- return null;
+ // We were not able to find any preferences theme so return the original Context without
+ // any theme wrapping
+ return bridgeContext;
}
/**
@@ -223,12 +236,7 @@
try {
LayoutlibCallback callback = bridgeContext.getLayoutlibCallback();
-
Context context = getThemedContext(bridgeContext);
- if (context == null) {
- // Probably we couldn't find the "preferenceTheme" in the theme
- return null;
- }
// Create PreferenceManager
Object preferenceManager = instantiateClass(callback, preferenceManagerClassName,
@@ -251,22 +259,26 @@
ArrayList<Object> viewCookie = new ArrayList<>();
if (parser instanceof BridgeXmlBlockParser) {
// Setup a parser that stores the XmlTag
- parser = new BridgeXmlBlockParser(parser, null, false) {
- @Override
- public Object getViewCookie() {
- return ((BridgeXmlBlockParser) getParser()).getViewCookie();
- }
+ parser =
+ new BridgeXmlBlockParser(
+ parser,
+ null,
+ ((BridgeXmlBlockParser) parser).getFileResourceNamespace()) {
+ @Override
+ public Object getViewCookie() {
+ return ((BridgeXmlBlockParser) getParser()).getViewCookie();
+ }
- @Override
- public int next() throws XmlPullParserException, IOException {
- int ev = super.next();
- if (ev == XmlPullParser.START_TAG) {
- viewCookie.add(this.getViewCookie());
- }
+ @Override
+ public int next() throws XmlPullParserException, IOException {
+ int ev = super.next();
+ if (ev == XmlPullParser.START_TAG) {
+ viewCookie.add(this.getViewCookie());
+ }
- return ev;
- }
- };
+ return ev;
+ }
+ };
}
// Create the PreferenceInflater
@@ -299,4 +311,20 @@
return null;
}
}
+
+ /**
+ * Returns true if the given root tag is any of the support library {@code PreferenceScreen}
+ * tags.
+ */
+ public static boolean isSupportRootTag(@Nullable String rootTag) {
+ if (rootTag != null) {
+ for (String supportPrefix : PREFERENCES_PKG_NAMES) {
+ if (rootTag.equals(supportPrefix + ".PreferenceScreen")) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java b/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
index 136256d1..bf2b527 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/view/WindowManagerImpl.java
@@ -78,4 +78,19 @@
public Region getCurrentImeTouchRegion() {
return null;
}
+
+ @Override
+ public void setShouldShowWithInsecureKeyguard(int displayId, boolean shouldShow) {
+ // pass
+ }
+
+ @Override
+ public void setShouldShowSystemDecors(int displayId, boolean shouldShow) {
+ // pass
+ }
+
+ @Override
+ public void setShouldShowIme(int displayId, boolean shouldShow) {
+ // pass
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index 919d57c..ae218ee 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.layoutlib.bridge.bars;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.StyleResourceValue;
@@ -26,7 +26,6 @@
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;
-import com.android.tools.layoutlib.annotations.NotNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,23 +43,18 @@
import java.lang.reflect.Method;
import java.util.List;
-import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
-import static com.android.resources.ResourceType.MENU;
-
-
/**
* Assumes that the AppCompat library is present in the project's classpath and creates an
* actionbar around it.
*/
public class AppCompatActionBar extends BridgeActionBar {
-
- private Object mWindowDecorActionBar;
private static final String[] WINDOW_ACTION_BAR_CLASS_NAMES = {
"android.support.v7.internal.app.WindowDecorActionBar",
"android.support.v7.app.WindowDecorActionBar", // This is used on v23.1.1 and later.
- "androidx.app.WindowDecorActionBar" // User from v27
+ "androidx.appcompat.app.WindowDecorActionBar" // User from v28
};
+ private Object mWindowDecorActionBar;
private Class<?> mWindowActionBarClass;
/**
@@ -68,9 +62,11 @@
*/
public AppCompatActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) {
super(context, params);
- int contentRootId = context.getProjectResourceValue(ResourceType.ID,
- "action_bar_activity_content", 0);
+ ResourceReference resource = context.createAppCompatResourceReference(
+ ResourceType.ID, "action_bar_activity_content");
+ int contentRootId = context.getResourceId(resource, 0);
View contentView = getDecorContent().findViewById(contentRootId);
+
if (contentView != null) {
assert contentView instanceof FrameLayout;
setContentRoot((FrameLayout) contentView);
@@ -88,22 +84,21 @@
Object[] constructorArgs = {getDecorContent()};
LayoutlibCallback callback = params.getLayoutlibCallback();
- // Find the correct WindowActionBar class
+ // Find the correct WindowActionBar class.
String actionBarClass = null;
- for (int i = WINDOW_ACTION_BAR_CLASS_NAMES.length - 1; i >= 0; i--) {
+ for (int i = WINDOW_ACTION_BAR_CLASS_NAMES.length; --i >= 0;) {
actionBarClass = WINDOW_ACTION_BAR_CLASS_NAMES[i];
try {
callback.findClass(actionBarClass);
-
break;
} catch (ClassNotFoundException ignore) {
}
}
- mWindowDecorActionBar = callback.loadView(actionBarClass,
- constructorParams, constructorArgs);
- mWindowActionBarClass = mWindowDecorActionBar == null ? null :
- mWindowDecorActionBar.getClass();
+ mWindowDecorActionBar =
+ callback.loadView(actionBarClass, constructorParams, constructorArgs);
+ mWindowActionBarClass =
+ mWindowDecorActionBar == null ? null : mWindowDecorActionBar.getClass();
inflateMenus();
setupActionBar();
} catch (Exception e) {
@@ -115,8 +110,8 @@
@Override
protected ResourceValue getLayoutResource(BridgeContext context) {
// We always assume that the app has requested the action bar.
- return context.getRenderResources().getProjectResource(ResourceType.LAYOUT,
- "abc_screen_toolbar");
+ return context.getRenderResources().getResolvedResource(
+ context.createAppCompatResourceReference(ResourceType.LAYOUT,"abc_screen_toolbar"));
}
@Override
@@ -126,7 +121,8 @@
// https://android.googlesource.com/platform/frameworks/support/+/android-5.1.0_r1/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
Context themedContext = context;
RenderResources resources = context.getRenderResources();
- ResourceValue actionBarTheme = resources.findItemInTheme("actionBarTheme", false);
+ ResourceValue actionBarTheme =
+ resources.findItemInTheme(context.createAppCompatAttrReference("actionBarTheme"));
if (actionBarTheme != null) {
// resolve it, if needed.
actionBarTheme = resources.resolveResValue(actionBarTheme);
@@ -157,12 +153,12 @@
}
@Override
- protected void setIcon(String icon) {
+ protected void setIcon(ResourceValue icon) {
// Do this only if the action bar doesn't already have an icon.
- if (icon != null && !icon.isEmpty() && mWindowDecorActionBar != null) {
+ if (icon != null && icon.getValue() != null && mWindowDecorActionBar != null) {
if (invoke(getMethod(mWindowActionBarClass, "hasIcon"), mWindowDecorActionBar)
== Boolean.TRUE) {
- Drawable iconDrawable = getDrawable(icon, false);
+ Drawable iconDrawable = getDrawable(icon);
if (iconDrawable != null) {
Method setIcon = getMethod(mWindowActionBarClass, "setIcon", Drawable.class);
invoke(setIcon, mWindowDecorActionBar, iconDrawable);
@@ -181,12 +177,12 @@
}
private void inflateMenus() {
- List<String> menuNames = getCallBack().getMenuIdNames();
- if (menuNames.isEmpty()) {
+ List<ResourceReference> menuIds = getCallBack().getMenuIds();
+ if (menuIds.isEmpty()) {
return;
}
- if (menuNames.size() > 1) {
+ if (menuIds.size() > 1) {
// Supporting multiple menus means that we would need to instantiate our own supportlib
// MenuInflater instances using reflection
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -194,20 +190,12 @@
null, null, null);
}
- String name = menuNames.get(0);
- int id;
- if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
- // Framework menu.
- name = name.substring(ANDROID_NS_NAME_PREFIX.length());
- id = mBridgeContext.getFrameworkResourceValue(MENU, name, -1);
- } else {
- // Project menu.
- id = mBridgeContext.getProjectResourceValue(MENU, name, -1);
- }
+ ResourceReference menuId = menuIds.get(0);
+ int id = mBridgeContext.getResourceId(menuId, -1);
if (id < 1) {
return;
}
- // Get toolbar decorator
+ // Get toolbar decorator.
Object mDecorToolbar = getFieldValue(mWindowDecorActionBar, "mDecorToolbar");
if (mDecorToolbar == null) {
return;
@@ -246,7 +234,7 @@
* without having to get all the types for the parameters when we do not need them
*/
@Nullable
- private static Method findMethod(@Nullable Class<?> owner, @NotNull String name) {
+ private static Method findMethod(@Nullable Class<?> owner, @NonNull String name) {
if (owner == null) {
return null;
}
@@ -260,7 +248,7 @@
}
@Nullable
- private static Object getFieldValue(@Nullable Object instance, @NotNull String name) {
+ private static Object getFieldValue(@Nullable Object instance, @NonNull String name) {
if (instance == null) {
return null;
}
@@ -295,10 +283,9 @@
// TODO: this is duplicated from FrameworkActionBarWrapper$WindowActionBarWrapper
@Nullable
- private Drawable getDrawable(@NonNull String name, boolean isFramework) {
- RenderResources res = mBridgeContext.getRenderResources();
- ResourceValue value = res.findResValue(name, isFramework);
- value = res.resolveResValue(value);
+ private Drawable getDrawable(@NonNull ResourceValue value) {
+ RenderResources resolver = mBridgeContext.getRenderResources();
+ value = resolver.resolveResValue(value);
if (value != null) {
return ResourceHelper.getDrawable(value, mBridgeContext);
}
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java b/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java
index a439e7d..8790cb5 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java
@@ -18,7 +18,6 @@
import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
-import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.layoutlib.bridge.MockView;
@@ -60,14 +59,7 @@
assert false : "Unable to find the layout for Action Bar.";
}
else {
- if (layoutName.isFramework()) {
- layoutId = context.getFrameworkResourceValue(layoutName.getResourceType(),
- layoutName.getName(), 0);
- } else {
- layoutId = context.getProjectResourceValue(layoutName.getResourceType(),
- layoutName.getName(), 0);
-
- }
+ layoutId = context.getResourceId(layoutName.asReference(), 0);
}
if (layoutId == 0) {
assert false : String.format("Unable to resolve attribute \"%1$s\" of type \"%2$s\"",
@@ -128,19 +120,12 @@
protected abstract void setTitle(CharSequence title);
protected abstract void setSubtitle(CharSequence subtitle);
- protected abstract void setIcon(String icon);
+ protected abstract void setIcon(ResourceValue icon);
protected abstract void setHomeAsUp(boolean homeAsUp);
private void setTitle() {
- RenderResources res = mBridgeContext.getRenderResources();
-
String title = mParams.getAppLabel();
- ResourceValue titleValue = res.findResValue(title, false);
- if (titleValue != null && titleValue.getValue() != null) {
- setTitle(titleValue.getValue());
- } else {
- setTitle(title);
- }
+ setTitle(title);
}
private void setSutTitle() {
@@ -151,7 +136,7 @@
}
private void setIcon() {
- String appIcon = mParams.getAppIcon();
+ ResourceValue appIcon = mParams.getAppIcon();
if (appIcon != null) {
setIcon(appIcon);
}
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/Config.java b/bridge/src/com/android/layoutlib/bridge/bars/Config.java
index 569d7b6..641ca4e 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/Config.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/Config.java
@@ -33,14 +33,27 @@
private static final String GINGERBREAD_DIR = "/bars/v9/";
private static final String JELLYBEAN_DIR = "/bars/v18/";
private static final String KITKAT_DIR = "/bars/v19/";
- private static final String DEFAULT_RESOURCE_DIR = "/bars/v21/";
+ private static final String LOLLIPOP_DIR = "/bars/v21/";
+ private static final String PI_DIR = "/bars/v28/";
- private static final List<String> sDefaultResourceDir =
- Collections.singletonList(DEFAULT_RESOURCE_DIR);
+
+ private static final List<String> sDefaultResourceDir;
private static final int WHITE = 0xFFFFFFFF;
private static final int BLACK = 0xFF000000;
+ static {
+ sDefaultResourceDir = new ArrayList<>(6);
+ sDefaultResourceDir.add(PI_DIR);
+ sDefaultResourceDir.add("/bars/");
+ // If something is not found in the default directories, we fall back to search in the
+ // old versions
+ sDefaultResourceDir.add(LOLLIPOP_DIR);
+ sDefaultResourceDir.add(KITKAT_DIR);
+ sDefaultResourceDir.add(JELLYBEAN_DIR);
+ sDefaultResourceDir.add(GINGERBREAD_DIR);
+ }
+
public static boolean showOnScreenNavBar(int platformVersion) {
return isGreaterOrEqual(platformVersion, ICE_CREAM_SANDWICH);
}
@@ -55,7 +68,7 @@
if (platformVersion == 0) {
return sDefaultResourceDir;
}
- List<String> list = new ArrayList<String>(4);
+ List<String> list = new ArrayList<String>(10);
// Gingerbread - uses custom battery and wifi icons.
if (platformVersion <= GINGERBREAD) {
list.add(GINGERBREAD_DIR);
@@ -68,7 +81,12 @@
if (platformVersion <= KITKAT) {
list.add(KITKAT_DIR);
}
- list.add(DEFAULT_RESOURCE_DIR);
+ // Lollipop - Custom for sysbar and battery
+ if (platformVersion <= LOLLIPOP) {
+ list.add(LOLLIPOP_DIR);
+ }
+
+ list.addAll(sDefaultResourceDir);
return Collections.unmodifiableList(list);
}
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
index 369f1c6..332a861 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -23,15 +23,13 @@
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.layoutlib.bridge.resources.IconLoader;
+import com.android.layoutlib.bridge.resources.SysUiResources;
import com.android.resources.Density;
import com.android.resources.LayoutDirection;
import com.android.resources.ResourceType;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import android.annotation.NonNull;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
@@ -55,20 +53,16 @@
* Base "bar" class for the window decor around the the edited layout.
* This is basically an horizontal layout that loads a given layout on creation (it is read
* through {@link Class#getResourceAsStream(String)}).
- *
+ * <p>
* The given layout should be a merge layout so that all the children belong to this class directly.
- *
+ * <p>
* It also provides a few utility methods to configure the content of the layout.
*/
abstract class CustomBar extends LinearLayout {
-
-
private final int mSimulatedPlatformVersion;
- protected abstract TextView getStyleableTextView();
-
- protected CustomBar(BridgeContext context, int orientation, String layoutPath,
- String name, int simulatedPlatformVersion) {
+ protected CustomBar(BridgeContext context, int orientation, String layoutName,
+ int simulatedPlatformVersion) {
super(context);
mSimulatedPlatformVersion = simulatedPlatformVersion;
setOrientation(orientation);
@@ -79,89 +73,83 @@
}
LayoutInflater inflater = LayoutInflater.from(mContext);
-
- XmlPullParser parser;
+ BridgeXmlBlockParser bridgeParser = loadXml(layoutName);
try {
- parser = ParserFactory.create(getClass().getResourceAsStream(layoutPath), name);
-
- BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(parser, context, false);
-
- try {
- inflater.inflate(bridgeParser, this, true);
- } finally {
- bridgeParser.ensurePopped();
- }
- } catch (XmlPullParserException e) {
- // Should not happen as the resource is bundled with the jar, and ParserFactory should
- // have been initialized.
- assert false;
+ inflater.inflate(bridgeParser, this, true);
+ } finally {
+ bridgeParser.ensurePopped();
}
}
- protected void loadIcon(int index, String iconName, Density density) {
- loadIcon(index, iconName, density, false);
+ protected abstract TextView getStyleableTextView();
+
+ protected BridgeXmlBlockParser loadXml(String layoutName) {
+ return SysUiResources.loadXml((BridgeContext) mContext, mSimulatedPlatformVersion,
+ layoutName);
}
- protected void loadIcon(int index, String iconName, Density density, boolean isRtl) {
+ protected ImageView loadIcon(ImageView imageView, String iconName, Density density) {
+ return SysUiResources.loadIcon(mContext, mSimulatedPlatformVersion, imageView, iconName,
+ density, false);
+ }
+
+ protected ImageView loadIcon(int index, String iconName, Density density, boolean isRtl) {
View child = getChildAt(index);
if (child instanceof ImageView) {
ImageView imageView = (ImageView) child;
-
- LayoutDirection dir = isRtl ? LayoutDirection.RTL : null;
- IconLoader iconLoader = new IconLoader(iconName, density, mSimulatedPlatformVersion,
- dir);
- InputStream stream = iconLoader.getIcon();
-
- if (stream != null) {
- density = iconLoader.getDensity();
- String path = iconLoader.getPath();
- // look for a cached bitmap
- Bitmap bitmap = Bridge.getCachedBitmap(path, Boolean.TRUE /*isFramework*/);
- if (bitmap == null) {
- try {
- bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density);
- Bridge.setCachedBitmap(path, bitmap, Boolean.TRUE /*isFramework*/);
- } catch (IOException e) {
- return;
- }
- }
-
- if (bitmap != null) {
- BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(),
- bitmap);
- imageView.setImageDrawable(drawable);
- }
- }
+ return SysUiResources.loadIcon(mContext, mSimulatedPlatformVersion, imageView, iconName,
+ density, isRtl);
}
+
+ return null;
}
- protected TextView setText(int index, String string, boolean reference) {
+ protected ImageView loadIcon(ImageView imageView, String iconName, Density density,
+ boolean isRtl) {
+ LayoutDirection dir = isRtl ? LayoutDirection.RTL : null;
+ IconLoader iconLoader = new IconLoader(iconName, density, mSimulatedPlatformVersion, dir);
+ InputStream stream = iconLoader.getIcon();
+
+ if (stream != null) {
+ density = iconLoader.getDensity();
+ String path = iconLoader.getPath();
+ // look for a cached bitmap
+ Bitmap bitmap = Bridge.getCachedBitmap(path, Boolean.TRUE /*isFramework*/);
+ if (bitmap == null) {
+ try {
+ bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density);
+ Bridge.setCachedBitmap(path, bitmap, Boolean.TRUE /*isFramework*/);
+ } catch (IOException e) {
+ return imageView;
+ }
+ }
+
+ if (bitmap != null) {
+ BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
+ imageView.setImageDrawable(drawable);
+ }
+ }
+
+ return imageView;
+ }
+
+ protected TextView setText(int index, String string) {
View child = getChildAt(index);
if (child instanceof TextView) {
TextView textView = (TextView) child;
- setText(textView, string, reference);
+ textView.setText(string);
return textView;
}
return null;
}
- private void setText(TextView textView, String string, boolean reference) {
- if (reference) {
- ResourceValue value = getResourceValue(string);
- if (value != null) {
- string = value.getValue();
- }
- }
- textView.setText(string);
- }
-
protected void setStyle(String themeEntryName) {
-
BridgeContext bridgeContext = getContext();
RenderResources res = bridgeContext.getRenderResources();
- ResourceValue value = res.findItemInTheme(themeEntryName, true /*isFrameworkAttr*/);
+ ResourceValue value =
+ res.findItemInTheme(BridgeContext.createFrameworkAttrReference(themeEntryName));
value = res.resolveResValue(value);
if (!(value instanceof StyleResourceValue)) {
@@ -171,8 +159,8 @@
StyleResourceValue style = (StyleResourceValue) value;
// get the background
- ResourceValue backgroundValue = res.findItemInStyle(style, "background",
- true /*isFrameworkAttr*/);
+ ResourceValue backgroundValue = res.findItemInStyle(style,
+ BridgeContext.createFrameworkAttrReference("background"));
backgroundValue = res.resolveResValue(backgroundValue);
if (backgroundValue != null) {
Drawable d = ResourceHelper.getDrawable(backgroundValue, bridgeContext);
@@ -184,14 +172,14 @@
TextView textView = getStyleableTextView();
if (textView != null) {
// get the text style
- ResourceValue textStyleValue = res.findItemInStyle(style, "titleTextStyle",
- true /*isFrameworkAttr*/);
+ ResourceValue textStyleValue = res.findItemInStyle(style,
+ BridgeContext.createFrameworkAttrReference("titleTextStyle"));
textStyleValue = res.resolveResValue(textStyleValue);
if (textStyleValue instanceof StyleResourceValue) {
StyleResourceValue textStyle = (StyleResourceValue) textStyleValue;
- ResourceValue textSize = res.findItemInStyle(textStyle, "textSize",
- true /*isFrameworkAttr*/);
+ ResourceValue textSize = res.findItemInStyle(textStyle,
+ BridgeContext.createFrameworkAttrReference("textSize"));
textSize = res.resolveResValue(textSize);
if (textSize != null) {
@@ -203,13 +191,12 @@
}
}
-
- ResourceValue textColor = res.findItemInStyle(textStyle, "textColor",
- true);
+ ResourceValue textColor = res.findItemInStyle(textStyle,
+ BridgeContext.createFrameworkAttrReference("textColor"));
textColor = res.resolveResValue(textColor);
if (textColor != null) {
- ColorStateList stateList = ResourceHelper.getColorStateList(
- textColor, bridgeContext, null);
+ ColorStateList stateList =
+ ResourceHelper.getColorStateList(textColor, bridgeContext, null);
if (stateList != null) {
textView.setTextColor(stateList);
}
@@ -240,14 +227,14 @@
}
RenderResources renderResources = getContext().getRenderResources();
// First check if the bar is translucent.
- boolean translucent = ResourceHelper.getBooleanThemeValue(renderResources,
- translucentAttrName, true, false);
+ boolean translucent = ResourceHelper.getBooleanThemeFrameworkAttrValue(renderResources,
+ translucentAttrName, false);
if (translucent) {
// Keep in sync with R.color.system_bar_background_semi_transparent from system ui.
return 0x66000000; // 40% black.
}
- boolean transparent = ResourceHelper.getBooleanThemeValue(renderResources,
- "windowDrawsSystemBarBackgrounds", true, false);
+ boolean transparent = ResourceHelper.getBooleanThemeFrameworkAttrValue(renderResources,
+ "windowDrawsSystemBarBackgrounds", false);
if (transparent) {
return getColor(renderResources, colorAttrName);
}
@@ -255,8 +242,9 @@
}
private static int getColor(RenderResources renderResources, String attr) {
- // From ?attr/foo to @color/bar. This is most likely an ItemResourceValue.
- ResourceValue resource = renderResources.findItemInTheme(attr, true);
+ // From ?attr/foo to @color/bar. This is most likely an StyleItemResourceValue.
+ ResourceValue resource =
+ renderResources.findItemInTheme(BridgeContext.createFrameworkAttrReference(attr));
// Form @color/bar to the #AARRGGBB
resource = renderResources.resolveResValue(resource);
if (resource != null) {
@@ -277,14 +265,4 @@
}
return 0;
}
-
- private ResourceValue getResourceValue(String reference) {
- RenderResources res = getContext().getRenderResources();
-
- // find the resource
- ResourceValue value = res.findResValue(reference, false);
-
- // resolve it if needed
- return res.resolveResValue(value);
- }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java b/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java
index fd49c77..230094e 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.layoutlib.bridge.bars;
import com.android.ide.common.rendering.api.RenderResources;
@@ -42,13 +41,12 @@
import android.widget.ListView;
import android.widget.RelativeLayout;
-import java.util.ArrayList;
+import java.util.List;
/**
* Creates the ActionBar as done by the framework.
*/
public class FrameworkActionBar extends BridgeActionBar {
-
private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout";
// The Action Bar
@@ -89,11 +87,11 @@
@Override
protected ResourceValue getLayoutResource(BridgeContext context) {
ResourceValue layoutName =
- context.getRenderResources().findItemInTheme(LAYOUT_ATTR_NAME, true);
+ context.getRenderResources().findItemInTheme(
+ BridgeContext.createFrameworkAttrReference(LAYOUT_ATTR_NAME));
if (layoutName != null) {
// We may need to resolve the reference obtained.
- layoutName = context.getRenderResources().findResValue(layoutName.getValue(),
- layoutName.isFramework());
+ layoutName = context.getRenderResources().dereference(layoutName);
}
if (layoutName == null) {
throw new InflateException("Unable to find action bar layout (" + LAYOUT_ATTR_NAME
@@ -124,7 +122,7 @@
}
@Override
- protected void setIcon(String icon) {
+ protected void setIcon(ResourceValue icon) {
mActionBar.setIcon(icon);
}
@@ -173,7 +171,7 @@
return false;
}
// Copied from android.widget.ActionMenuPresenter.updateMenuView()
- ArrayList<MenuItemImpl> menus = mActionBar.getMenuBuilder().getNonActionItems();
+ List<MenuItemImpl> menus = mActionBar.getMenuBuilder().getNonActionItems();
ActionMenuPresenter presenter = mActionBar.getActionMenuPresenter();
if (presenter == null) {
assert false : "Failed to create a Presenter for Action Bar Menus.";
@@ -238,7 +236,8 @@
private int getActionBarHeight() {
RenderResources resources = mBridgeContext.getRenderResources();
DisplayMetrics metrics = mBridgeContext.getMetrics();
- ResourceValue value = resources.findItemInTheme("actionBarSize", true);
+ ResourceValue value = resources.findItemInTheme(
+ BridgeContext.createFrameworkAttrReference("actionBarSize"));
// resolve it
value = resources.resolveResValue(value);
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java b/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
index 2cdc647..9811af4 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
@@ -18,6 +18,7 @@
import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.internal.R;
import com.android.internal.app.ToolbarActionBar;
@@ -47,9 +48,6 @@
import android.widget.Toolbar;
import android.widget.Toolbar_Accessor;
-import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
-import static com.android.resources.ResourceType.MENU;
-
/**
* A common API to access {@link ToolbarActionBar} and {@link WindowDecorActionBar}.
*/
@@ -106,7 +104,7 @@
mActionBar.setDisplayHomeAsUpEnabled(homeAsUp);
}
- public void setIcon(String icon) {
+ public void setIcon(ResourceValue icon) {
// Nothing to do.
}
@@ -125,17 +123,9 @@
protected void inflateMenus() {
MenuInflater inflater = new MenuInflater(getActionMenuContext());
MenuBuilder menuBuilder = getMenuBuilder();
- for (String name : mCallback.getMenuIdNames()) {
- int id;
- if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
- // Framework menu.
- name = name.substring(ANDROID_NS_NAME_PREFIX.length());
- id = mContext.getFrameworkResourceValue(MENU, name, -1);
- } else {
- // Project menu.
- id = mContext.getProjectResourceValue(MENU, name, -1);
- }
- if (id > -1) {
+ for (ResourceReference menuId : mCallback.getMenuIds()) {
+ int id = mContext.getResourceId(menuId, -1);
+ if (id >= 0) {
inflater.inflate(id, menuBuilder);
}
}
@@ -288,10 +278,10 @@
}
@Override
- public void setIcon(String icon) {
+ public void setIcon(ResourceValue icon) {
// Set the icon only if the action bar doesn't specify an icon.
if (!mActionBar.hasIcon() && icon != null) {
- Drawable iconDrawable = getDrawable(icon, false);
+ Drawable iconDrawable = getDrawable(icon);
if (iconDrawable != null) {
mActionBar.setIcon(iconDrawable);
}
@@ -365,15 +355,13 @@
}
@Nullable
- private Drawable getDrawable(@NonNull String name, boolean isFramework) {
+ private Drawable getDrawable(@NonNull ResourceValue value) {
RenderResources res = mContext.getRenderResources();
- ResourceValue value = res.findResValue(name, isFramework);
value = res.resolveResValue(value);
if (value != null) {
return ResourceHelper.getDrawable(value, mContext);
}
return null;
}
-
}
}
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
index dfbc69b..a244e2b 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
@@ -38,18 +38,18 @@
private static final int WIDTH_DEFAULT = 36;
private static final int WIDTH_SW360 = 40;
private static final int WIDTH_SW600 = 48;
- protected static final String LAYOUT_XML = "/bars/navigation_bar.xml";
- private static final String LAYOUT_600DP_XML = "/bars/navigation_bar600dp.xml";
+ protected static final String LAYOUT_XML = "navigation_bar.xml";
+ private static final String LAYOUT_600DP_XML = "navigation_bar600dp.xml";
public NavigationBar(BridgeContext context, Density density, int orientation, boolean isRtl,
- boolean rtlEnabled, int simulatedPlatformVersion) {
+ boolean rtlEnabled, int simulatedPlatformVersion, boolean quickStepEnabled) {
this(context, density, orientation, isRtl, rtlEnabled, simulatedPlatformVersion,
- getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML);
+ getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML, quickStepEnabled);
}
protected NavigationBar(BridgeContext context, Density density, int orientation, boolean isRtl,
- boolean rtlEnabled, int simulatedPlatformVersion, String layoutPath) {
- super(context, orientation, layoutPath, "navigation_bar.xml", simulatedPlatformVersion);
+ boolean rtlEnabled, int simulatedPlatformVersion, String layoutPath, boolean quickStepEnabled) {
+ super(context, orientation, layoutPath, simulatedPlatformVersion);
int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
setBackgroundColor(color == 0 ? 0xFF000000 : color);
@@ -67,11 +67,17 @@
}
//noinspection SpellCheckingInspection
- loadIcon(back, "ic_sysbar_back.png", density, isRtl);
+ loadIcon(back,
+ quickStepEnabled ? "ic_sysbar_back_quick_step.png" : "ic_sysbar_back.png",
+ density, isRtl);
//noinspection SpellCheckingInspection
- loadIcon(3, "ic_sysbar_home.png", density, isRtl);
- //noinspection SpellCheckingInspection
- loadIcon(recent, "ic_sysbar_recent.png", density, isRtl);
+ loadIcon(3, quickStepEnabled ? "ic_sysbar_home_quick_step.png" : "ic_sysbar_home.png",
+ density,
+ isRtl);
+ if (!quickStepEnabled) {
+ //noinspection SpellCheckingInspection
+ loadIcon(recent, "ic_sysbar_recent.png", density, isRtl);
+ }
setupNavBar(context, orientation);
}
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
index 2dc7c65..c653805 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
@@ -17,10 +17,12 @@
package com.android.layoutlib.bridge.bars;
import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.ResourceNamespace;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.layoutlib.bridge.resources.IconLoader;
import com.android.resources.Density;
import org.xmlpull.v1.XmlPullParserException;
@@ -37,6 +39,8 @@
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
public class StatusBar extends CustomBar {
@@ -64,8 +68,7 @@
public StatusBar(BridgeContext context, Density density, boolean isRtl, boolean rtlEnabled,
int simulatedPlatformVersion) {
// FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar.
- super(context, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml",
- simulatedPlatformVersion);
+ super(context, LinearLayout.HORIZONTAL, "status_bar.xml", simulatedPlatformVersion);
mSimulatedPlatformVersion = simulatedPlatformVersion;
// FIXME: use FILL_H?
@@ -74,46 +77,65 @@
int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
setBackgroundColor(color == 0 ? Config.getStatusBarColor(simulatedPlatformVersion) : color);
+ List<ImageView> icons = new ArrayList<>(2);
+ TextView clockView = null;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+
+ if (child instanceof ImageView) {
+ icons.add((ImageView) child);
+ } else if (child instanceof TextView) {
+ clockView = (TextView) child;
+ }
+ }
+
+ if (icons.size() != 2 || clockView == null) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to initialize statusbar", null,
+ null);
+ return;
+ }
+
// Cannot access the inside items through id because no R.id values have been
// created for them.
// We do know the order though.
// 0 is the spacer
- loadIcon(1, "stat_sys_wifi_signal_4_fully."
+ loadIcon(icons.get(0), "stat_sys_wifi_signal_4_fully."
+ Config.getWifiIconType(simulatedPlatformVersion), density);
- loadIcon(2, "stat_sys_battery_100.png", density);
- setText(3, Config.getTime(simulatedPlatformVersion), false)
- .setTextColor(Config.getTimeColor(simulatedPlatformVersion));
+ loadIcon(icons.get(1), "stat_sys_battery_100.png", density);
+ clockView.setText(Config.getTime(simulatedPlatformVersion));
+ clockView.setTextColor(Config.getTimeColor(simulatedPlatformVersion));
}
@Override
- protected void loadIcon(int index, String iconName, Density density) {
+ protected ImageView loadIcon(ImageView imageView, String iconName, Density density) {
if (!iconName.endsWith(".xml")) {
- super.loadIcon(index, iconName, density);
- return;
+ return super.loadIcon(imageView, iconName, density);
}
- View child = getChildAt(index);
- if (child instanceof ImageView) {
- ImageView imageView = (ImageView) child;
- // The xml is stored only in xhdpi.
- IconLoader iconLoader = new IconLoader(iconName, Density.XHIGH,
- mSimulatedPlatformVersion, null);
- InputStream stream = iconLoader.getIcon();
- if (stream != null) {
- try {
- BridgeXmlBlockParser parser = new BridgeXmlBlockParser(
- ParserFactory.create(stream, null), (BridgeContext) mContext, true);
- imageView.setImageDrawable(
- Drawable.createFromXml(mContext.getResources(), parser));
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e,
- null);
- } catch (IOException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e,
- null);
- }
+ // The xml is stored only in xhdpi.
+ IconLoader iconLoader = new IconLoader(iconName, Density.XHIGH,
+ mSimulatedPlatformVersion, null);
+ InputStream stream = iconLoader.getIcon();
+
+ if (stream != null) {
+ try {
+ BridgeXmlBlockParser parser =
+ new BridgeXmlBlockParser(
+ ParserFactory.create(stream, iconName),
+ (BridgeContext) mContext,
+ ResourceNamespace.ANDROID);
+ imageView.setImageDrawable(
+ Drawable.createFromXml(mContext.getResources(), parser));
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e,
+ null);
+ } catch (IOException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e,
+ null);
}
}
+
+ return imageView;
}
@Override
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java b/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java
index 0435280..523f140 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/ThemePreviewNavigationBar.java
@@ -45,7 +45,7 @@
((BridgeContext) context).getConfiguration().getLayoutDirection() ==
View.LAYOUT_DIRECTION_RTL,
(context.getApplicationInfo().flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0,
- 0, LAYOUT_XML);
+ 0, LAYOUT_XML, false);
}
@Override
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java b/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java
index 4fe1001..16578fb 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java
@@ -28,13 +28,12 @@
private TextView mTextView;
public TitleBar(BridgeContext context, String label, int simulatedPlatformVersion) {
- super(context, LinearLayout.HORIZONTAL, "/bars/title_bar.xml", "title_bar.xml",
- simulatedPlatformVersion);
+ super(context, LinearLayout.HORIZONTAL, "title_bar.xml", simulatedPlatformVersion);
// Cannot access the inside items through id because no R.id values have been
// created for them.
// We do know the order though.
- mTextView = setText(0, label, true);
+ mTextView = setText(0, label);
setStyle("windowTitleBackgroundStyle");
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java b/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
index 11da445..b71a0e2 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java
@@ -16,6 +16,7 @@
package com.android.layoutlib.bridge.impl;
+import com.android.internal.annotations.GuardedBy;
import com.android.layoutlib.bridge.util.Debug;
import com.android.layoutlib.bridge.util.SparseWeakArray;
@@ -25,9 +26,13 @@
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.Set;
+import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
+import libcore.util.NativeAllocationRegistry_Delegate;
+
/**
* Manages native delegates.
*
@@ -73,8 +78,6 @@
* @param <T> the delegate class to manage
*/
public final class DelegateManager<T> {
- @SuppressWarnings("FieldCanBeLocal")
- private final Class<T> mClass;
private static final SparseWeakArray<Object> sDelegates = new SparseWeakArray<>();
/** Set used to store delegates when their main object holds a reference to them.
* This is to ensure that the WeakReference in the SparseWeakArray doesn't get GC'ed
@@ -83,11 +86,46 @@
*/
private static final Set<Object> sJavaReferences = new HashSet<>();
private static final AtomicLong sDelegateCounter = new AtomicLong(1);
+ /**
+ * Tracks "native" allocations. This means that we know of the object in the Java side and we
+ * can attach the delegate lifecycle to the lifecycle of the Java object. If the Java object
+ * is disposed, it means we can get rid of the delegate allocation.
+ * Ideally, we would use a {@link WeakHashMap} but we do not control the equals() method of the
+ * referents so we can not safely rely on them.
+ */
+ private static final LinkedList<NativeAllocationHolder> sNativeAllocations = new LinkedList<>();
+ /**
+ * Map that allows to do a quick lookup of delegates that have been marked as native
+ * allocations.
+ * This allows us to quickly check if, when a manual dispose happens, there is work we have
+ * to do.
+ */
+ @GuardedBy("sNativeAllocations")
+ private static final WeakHashMap<Object, WeakReference<NativeAllocationHolder>>
+ sNativeAllocationsReferences = new WeakHashMap<>();
+ /**
+ * Counter of the number of native allocations. We use this counter to trigger the collection
+ * of unlinked references in the sNativeAllocations list. We do not need to do this process
+ * on every allocation so only run it every 50 allocations.
+ */
+ @GuardedBy("sNativeAllocations")
+ private static long sNativeAllocationsCount = 0;
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private final Class<T> mClass;
public DelegateManager(Class<T> theClass) {
mClass = theClass;
}
+ public synchronized static void dump(PrintStream out) {
+ for (Object reference : sJavaReferences) {
+ int idx = sDelegates.indexOfValue(reference);
+ out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName());
+ }
+ out.printf("\nTotal number of objects: %d\n", sJavaReferences.size());
+ }
+
/**
* Returns the delegate from the given native int.
* <p>
@@ -156,15 +194,80 @@
" with int " + native_object);
}
- sJavaReferences.remove(delegate);
+ if (!sJavaReferences.remove(delegate)) {
+ // We didn't have any strong references to the delegate so it might be tracked by
+ // the native allocations tracker. If so, we want to remove that reference to
+ // make it available to collect ASAP.
+ synchronized (sNativeAllocations) {
+ WeakReference<NativeAllocationHolder> holderRef = sNativeAllocationsReferences.get(delegate);
+ NativeAllocationHolder holder = holderRef.get();
+ if (holder != null) {
+ // We only null the referred delegate. We do not spend the time in finding
+ // the holder in the list and removing it since the "garbage collection" in
+ // markAsNativeAllocation will do it for us.
+ holder.mReferred = null;
+ }
+ }
+ }
}
}
- public synchronized static void dump(PrintStream out) {
- for (Object reference : sJavaReferences) {
- int idx = sDelegates.indexOfValue(reference);
- out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName());
+ /**
+ * This method marks the given native_object as a native allocation of the passed referent.
+ * This means that the lifecycle of the native_object can now be attached to the referent and
+ * if the referent is disposed, we can safely dispose the delegate.
+ * This method is called by the {@link NativeAllocationRegistry_Delegate} and allows the
+ * DelegateManager to remove the strong reference to the delegate.
+ */
+ public void markAsNativeAllocation(Object referent, long native_object) {
+ NativeAllocationHolder holder;
+ synchronized (DelegateManager.class) {
+ T delegate = getDelegate(native_object);
+ if (Debug.DEBUG) {
+ if (delegate == null) {
+ System.err.println("Unknown " + mClass.getSimpleName() + " with int " +
+ native_object);
+ }
+ else {
+ System.err.println("Marking element as native " + native_object);
+ }
+ }
+
+ assert delegate != null;
+ if (sJavaReferences.remove(delegate)) {
+ // We had a strong reference, move to the native allocation tracker.
+ holder = new NativeAllocationHolder(referent, delegate);
+ }
+ else {
+ holder = null;
+ }
}
- out.printf("\nTotal number of objects: %d\n", sJavaReferences.size());
+
+ if (holder != null) {
+ synchronized (sNativeAllocations) {
+ sNativeAllocations.add(holder);
+ // The value references the key in this case but we use a WeakReference value.
+ sNativeAllocationsReferences.put(holder.mReferred, new WeakReference<>(holder));
+
+ if (++sNativeAllocationsCount % 50 == 0) {
+ // Do garbage collection
+ boolean collected = sNativeAllocations.removeIf(e -> e.mReferent.get() == null);
+ if (Debug.DEBUG && collected) {
+ System.err.println("Elements collected");
+ }
+ }
+ }
+ }
+ }
+
+ private static class NativeAllocationHolder {
+ private final WeakReference<Object> mReferent;
+ // The referred object is not null so we can null them on demand
+ private Object mReferred;
+
+ private NativeAllocationHolder(Object referent, Object referred) {
+ mReferent = new WeakReference<>(referent);
+ mReferred = referred;
+ }
}
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/DisplayCutoutView.java b/bridge/src/com/android/layoutlib/bridge/impl/DisplayCutoutView.java
new file mode 100644
index 0000000..48a8425
--- /dev/null
+++ b/bridge/src/com/android/layoutlib/bridge/impl/DisplayCutoutView.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018 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.layoutlib.bridge.impl;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.View;
+
+class DisplayCutoutView extends View {
+
+ private final DisplayInfo mInfo = new DisplayInfo();
+ private final Paint mPaint = new Paint();
+ private final Region mBounds = new Region();
+ private final Rect mBoundingRect = new Rect();
+ private final Path mBoundingPath = new Path();
+ private final int[] mLocation = new int[2];
+ private final boolean mStart;
+
+ public DisplayCutoutView(Context context, boolean start) {
+ super(context);
+ mStart = start;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ update();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ getLocationOnScreen(mLocation);
+ canvas.translate(-mLocation[0], -mLocation[1]);
+ if (!mBoundingPath.isEmpty()) {
+ mPaint.setColor(Color.BLACK);
+ mPaint.setStyle(Paint.Style.FILL);
+ canvas.drawPath(mBoundingPath, mPaint);
+ }
+ }
+
+ private void update() {
+ requestLayout();
+ getDisplay().getDisplayInfo(mInfo);
+ mBounds.setEmpty();
+ mBoundingRect.setEmpty();
+ mBoundingPath.reset();
+ int newVisible;
+ if (hasCutout()) {
+ mBounds.set(mInfo.displayCutout.getBoundingRectTop());
+ localBounds(mBoundingRect);
+ mBounds.getBoundaryPath(mBoundingPath);
+ newVisible = VISIBLE;
+ } else {
+ newVisible = GONE;
+ }
+ if (newVisible != getVisibility()) {
+ setVisibility(newVisible);
+ }
+ }
+
+ private boolean hasCutout() {
+ final DisplayCutout displayCutout = mInfo.displayCutout;
+ if (displayCutout == null) {
+ return false;
+ }
+ if (mStart) {
+ return displayCutout.getSafeInsetLeft() > 0
+ || displayCutout.getSafeInsetTop() > 0;
+ } else {
+ return displayCutout.getSafeInsetRight() > 0
+ || displayCutout.getSafeInsetBottom() > 0;
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mBounds.isEmpty()) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+ setMeasuredDimension(
+ resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0),
+ resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
+ }
+
+ public static void boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out) {
+ Region bounds = new Region(displayCutout.getBoundingRectTop());
+ switch (gravity) {
+ case Gravity.TOP:
+ bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(),
+ Region.Op.INTERSECT);
+ out.set(bounds.getBounds());
+ break;
+ case Gravity.LEFT:
+ bounds.op(0, 0, displayCutout.getSafeInsetLeft(), Integer.MAX_VALUE,
+ Region.Op.INTERSECT);
+ out.set(bounds.getBounds());
+ break;
+ case Gravity.BOTTOM:
+ bounds.op(0, displayCutout.getSafeInsetTop() + 1, Integer.MAX_VALUE,
+ Integer.MAX_VALUE, Region.Op.INTERSECT);
+ out.set(bounds.getBounds());
+ break;
+ case Gravity.RIGHT:
+ bounds.op(displayCutout.getSafeInsetLeft() + 1, 0, Integer.MAX_VALUE,
+ Integer.MAX_VALUE, Region.Op.INTERSECT);
+ out.set(bounds.getBounds());
+ break;
+ }
+ bounds.recycle();
+ }
+
+ private void localBounds(Rect out) {
+ final DisplayCutout displayCutout = mInfo.displayCutout;
+
+ if (mStart) {
+ if (displayCutout.getSafeInsetLeft() > 0) {
+ boundsFromDirection(displayCutout, Gravity.LEFT, out);
+ } else if (displayCutout.getSafeInsetTop() > 0) {
+ boundsFromDirection(displayCutout, Gravity.TOP, out);
+ }
+ } else {
+ if (displayCutout.getSafeInsetRight() > 0) {
+ boundsFromDirection(displayCutout, Gravity.RIGHT, out);
+ } else if (displayCutout.getSafeInsetBottom() > 0) {
+ boundsFromDirection(displayCutout, Gravity.BOTTOM, out);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
index 7526e09..2ad7037 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -77,7 +77,6 @@
/** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}.
* If this is null, this does not mean there's no layer, just that the snapshot is not the
* one that created the layer.
- * @see #getLayerSnapshot()
*/
private final Layer mLocalLayer;
private final Paint_Delegate mLocalLayerPaint;
@@ -252,7 +251,6 @@
/**
* Creates the root snapshot.
- * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
*/
private GcSnapshot() {
mPrevious = null;
@@ -618,10 +616,6 @@
return;
}
- int x = 0;
- int y = 0;
- int width;
- int height;
Rectangle clipBounds = originalGraphics.getClip() != null ? originalGraphics
.getClipBounds() : null;
if (clipBounds != null) {
@@ -629,17 +623,14 @@
// Clip is 0 so no need to paint anything.
return;
}
- // If we have clipBounds available, use them as they will always be
- // smaller than the full layer size.
- x = clipBounds.x;
- y = clipBounds.y;
- width = clipBounds.width;
- height = clipBounds.height;
- } else {
- width = layer.getImage().getWidth();
- height = layer.getImage().getHeight();
}
+ // b/63692596:
+ // Don't use the size of clip bound because it may be smaller than original size.
+ // Which makes Vector Drawable pixelized.
+ int width = layer.getImage().getWidth();
+ int height = layer.getImage().getHeight();
+
// Create a temporary image to which the color filter will be applied.
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
@@ -648,25 +639,27 @@
Graphics2D imageGraphics = createCustomGraphics(
imageBaseGraphics, paint, compositeOnly,
AlphaComposite.SRC_OVER);
+
// get a Graphics2D object configured with the drawing parameters, but no shader.
Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
true /*compositeOnly*/, forceMode);
+ configuredGraphics.setTransform(new AffineTransform());
try {
// The main draw operation.
// We translate the operation to take into account that the rendering does not
// know about the clipping area.
- imageGraphics.translate(-x, -y);
+ imageGraphics.setTransform(originalGraphics.getTransform());
drawable.draw(imageGraphics, paint);
// Apply the color filter.
// Restore the original coordinates system and apply the filter only to the
// clipped area.
- imageGraphics.translate(x, y);
+ imageGraphics.setTransform(new AffineTransform());
filter.applyFilter(imageGraphics, width, height);
// Draw the tinted image on the main layer using as start point the clipping
// upper left coordinates.
- configuredGraphics.drawImage(image, x, y, null);
+ configuredGraphics.drawImage(image, 0, 0, null);
layer.change();
} finally {
// dispose Graphics2D objects
@@ -774,6 +767,10 @@
// make new one graphics
Graphics2D g = (Graphics2D) original.create();
+ if (paint == null) {
+ return g;
+ }
+
// configure it
if (paint.isAntiAliased()) {
@@ -784,39 +781,33 @@
}
// set the shader first, as it'll replace the color if it can be used it.
- boolean customShader = false;
if (!compositeOnly) {
- customShader = setShader(g, paint);
+ setShader(g, paint);
// set the stroke
g.setStroke(paint.getJavaStroke());
}
// set the composite.
- setComposite(g, paint, compositeOnly || customShader, forceMode);
+ setComposite(g, paint, compositeOnly, forceMode);
return g;
}
- private boolean setShader(Graphics2D g, Paint_Delegate paint) {
+ private void setShader(Graphics2D g, Paint_Delegate paint) {
Shader_Delegate shaderDelegate = paint.getShader();
if (shaderDelegate != null) {
if (shaderDelegate.isSupported()) {
java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
assert shaderPaint != null;
- if (shaderPaint != null) {
- g.setPaint(shaderPaint);
- return true;
- }
+ g.setPaint(shaderPaint);
+ return;
} else {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
- shaderDelegate.getSupportMessage(),
- null /*throwable*/, null /*data*/);
+ shaderDelegate.getSupportMessage(), null, null, null);
}
}
// if no shader, use the paint color
g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
-
- return false;
}
private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha,
@@ -824,6 +815,10 @@
// the alpha for the composite. Always opaque if the normal paint color is used since
// it contains the alpha
int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF;
+ Shader_Delegate shader = paint.getShader();
+ if (shader != null) {
+ alpha = (int)(alpha * shader.getAlpha());
+ }
if (forceMode != 0) {
g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
return;
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/Layout.java b/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
index 8fad194..534bc5a 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/Layout.java
@@ -18,6 +18,8 @@
import com.android.ide.common.rendering.api.HardwareConfig;
import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.layoutlib.bridge.Bridge;
@@ -35,6 +37,8 @@
import com.android.resources.ScreenOrientation;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -49,6 +53,7 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.widget.LinearLayout.VERTICAL;
+import static com.android.layoutlib.bridge.impl.ResourceHelper.getBooleanThemeFrameworkAttrValue;
import static com.android.layoutlib.bridge.impl.ResourceHelper.getBooleanThemeValue;
/**
@@ -85,7 +90,7 @@
* +--------------------------------------+
* </pre>
*/
-class Layout extends RelativeLayout {
+class Layout extends FrameLayout {
// Theme attributes used for configuring appearance of the system decor.
private static final String ATTR_WINDOW_FLOATING = "windowIsFloating";
@@ -122,6 +127,11 @@
private Builder mBuilder;
/**
+ * SysUI layout
+ */
+ private RelativeLayout mSysUiRoot;
+
+ /**
* This holds user's layout.
*/
private FrameLayout mContentRoot;
@@ -149,7 +159,7 @@
if (mBuilder.hasNavBar()) {
navBar = createNavBar(getContext(), density, isRtl, getParams().isRtlSupported(),
- simulatedPlatformVersion);
+ simulatedPlatformVersion, false);
}
if (builder.hasStatusBar()) {
@@ -177,17 +187,28 @@
frameworkActionBar = bar.getRootView();
}
- addViews(titleBar, mContentRoot == null ? (mContentRoot = createContentFrame()) : frameworkActionBar,
+ mSysUiRoot = new RelativeLayout(builder.mContext);
+ addSystemUiViews(titleBar, mContentRoot == null ? (mContentRoot = createContentFrame()) : frameworkActionBar,
statusBar, navBar, appCompatActionBar);
+ addView(mSysUiRoot);
+ //addView(createSysUiOverlay(mBuilder.mContext));
// Done with the builder. Don't hold a reference to it.
mBuilder = null;
}
@NonNull
+ private static View createSysUiOverlay(@NonNull BridgeContext context) {
+ SysUiOverlay overlay = new SysUiOverlay(context, 20, 10, 50, 40, 60);
+ overlay.setNotchColor(Color.BLACK);
+ overlay.setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ return overlay;
+ }
+
+ @NonNull
private FrameLayout createContentFrame() {
FrameLayout contentRoot = new FrameLayout(getContext());
- LayoutParams params = createLayoutParams(MATCH_PARENT, MATCH_PARENT);
- int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
+ RelativeLayout.LayoutParams params = createSysUiLayoutParams(MATCH_PARENT, MATCH_PARENT);
+ int rule = mBuilder.isNavBarVertical() ? RelativeLayout.START_OF : RelativeLayout.ABOVE;
if (mBuilder.hasSolidNavBar()) {
params.addRule(rule, getId(ID_NAV_BAR));
}
@@ -200,14 +221,14 @@
below = getId(ID_STATUS_BAR);
}
if (below != -1) {
- params.addRule(BELOW, below);
+ params.addRule(RelativeLayout.BELOW, below);
}
contentRoot.setLayoutParams(params);
return contentRoot;
}
@NonNull
- private LayoutParams createLayoutParams(int width, int height) {
+ private RelativeLayout.LayoutParams createSysUiLayoutParams(int width, int height) {
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
if (width > 0) {
width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics);
@@ -215,7 +236,7 @@
if (height > 0) {
height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics);
}
- return new LayoutParams(width, height);
+ return new RelativeLayout.LayoutParams(width, height);
}
@NonNull
@@ -244,9 +265,10 @@
boolean isRtlSupported, int simulatedPlatformVersion) {
StatusBar statusBar =
new StatusBar(context, density, isRtl, isRtlSupported, simulatedPlatformVersion);
- LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mStatusBarSize);
+ RelativeLayout.LayoutParams params = createSysUiLayoutParams(MATCH_PARENT, mBuilder
+ .mStatusBarSize);
if (mBuilder.isNavBarVertical()) {
- params.addRule(START_OF, getId(ID_NAV_BAR));
+ params.addRule(RelativeLayout.START_OF, getId(ID_NAV_BAR));
}
statusBar.setLayoutParams(params);
statusBar.setId(getId(ID_STATUS_BAR));
@@ -262,11 +284,11 @@
// AppCompat ActionBar below it
int heightRule = appCompatActionBar || !mBuilder.hasAppCompatActionBar() ? MATCH_PARENT :
WRAP_CONTENT;
- LayoutParams layoutParams = createLayoutParams(MATCH_PARENT, heightRule);
- int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
+ RelativeLayout.LayoutParams layoutParams = createSysUiLayoutParams(MATCH_PARENT, heightRule);
+ int rule = mBuilder.isNavBarVertical() ? RelativeLayout.START_OF : RelativeLayout.ABOVE;
if (mBuilder.hasSolidNavBar()) {
// If there
- if(rule == START_OF || appCompatActionBar || !mBuilder.hasAppCompatActionBar()) {
+ if(rule == RelativeLayout.START_OF || appCompatActionBar || !mBuilder.hasAppCompatActionBar()) {
layoutParams.addRule(rule, getId(ID_NAV_BAR));
}
}
@@ -278,15 +300,15 @@
id = ID_APP_COMPAT_ACTION_BAR;
if (mBuilder.hasTitleBar() || mBuilder.hasFrameworkActionBar()) {
- layoutParams.addRule(BELOW, getId(ID_FRAMEWORK_BAR));
+ layoutParams.addRule(RelativeLayout.BELOW, getId(ID_FRAMEWORK_BAR));
} else if (mBuilder.hasSolidStatusBar()) {
- layoutParams.addRule(BELOW, getId(ID_STATUS_BAR));
+ layoutParams.addRule(RelativeLayout.BELOW, getId(ID_STATUS_BAR));
}
} else {
actionBar = new FrameworkActionBar(context, params);
id = ID_FRAMEWORK_BAR;
if (mBuilder.hasSolidStatusBar()) {
- layoutParams.addRule(BELOW, getId(ID_STATUS_BAR));
+ layoutParams.addRule(RelativeLayout.BELOW, getId(ID_STATUS_BAR));
}
}
@@ -300,12 +322,12 @@
private TitleBar createTitleBar(BridgeContext context, String title,
int simulatedPlatformVersion) {
TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion);
- LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mTitleBarSize);
+ RelativeLayout.LayoutParams params = createSysUiLayoutParams(MATCH_PARENT, mBuilder.mTitleBarSize);
if (mBuilder.hasSolidStatusBar()) {
- params.addRule(BELOW, getId(ID_STATUS_BAR));
+ params.addRule(RelativeLayout.BELOW, getId(ID_STATUS_BAR));
}
if (mBuilder.isNavBarVertical() && mBuilder.hasSolidNavBar()) {
- params.addRule(START_OF, getId(ID_NAV_BAR));
+ params.addRule(RelativeLayout.START_OF, getId(ID_NAV_BAR));
}
titleBar.setLayoutParams(params);
titleBar.setId(getId(ID_FRAMEWORK_BAR));
@@ -319,26 +341,29 @@
*/
@NonNull
private NavigationBar createNavBar(BridgeContext context, Density density, boolean isRtl,
- boolean isRtlSupported, int simulatedPlatformVersion) {
+ boolean isRtlSupported, int simulatedPlatformVersion, boolean isQuickStepEnabled) {
int orientation = mBuilder.mNavBarOrientation;
int size = mBuilder.mNavBarSize;
+ // Only allow quickstep in the latest version or >= 28
+ isQuickStepEnabled = isQuickStepEnabled &&
+ (simulatedPlatformVersion == 0 || simulatedPlatformVersion >= 28);
NavigationBar navBar =
new NavigationBar(context, density, orientation, isRtl, isRtlSupported,
- simulatedPlatformVersion);
+ simulatedPlatformVersion, isQuickStepEnabled);
boolean isVertical = mBuilder.isNavBarVertical();
int w = isVertical ? size : MATCH_PARENT;
int h = isVertical ? MATCH_PARENT : size;
- LayoutParams params = createLayoutParams(w, h);
- params.addRule(isVertical ? ALIGN_PARENT_END : ALIGN_PARENT_BOTTOM);
+ RelativeLayout.LayoutParams params = createSysUiLayoutParams(w, h);
+ params.addRule(isVertical ? RelativeLayout.ALIGN_PARENT_END : RelativeLayout.ALIGN_PARENT_BOTTOM);
navBar.setLayoutParams(params);
navBar.setId(getId(ID_NAV_BAR));
return navBar;
}
- private void addViews(@NonNull View... views) {
+ private void addSystemUiViews(@NonNull View... views) {
for (View view : views) {
if (view != null) {
- addView(view);
+ mSysUiRoot.addView(view);
}
}
}
@@ -384,7 +409,8 @@
mParams = params;
mContext = context;
mResources = mParams.getResources();
- mWindowIsFloating = getBooleanThemeValue(mResources, ATTR_WINDOW_FLOATING, true, true);
+ mWindowIsFloating =
+ getBooleanThemeFrameworkAttrValue(mResources, ATTR_WINDOW_FLOATING, true);
findBackground();
@@ -398,25 +424,26 @@
private void findBackground() {
if (!mParams.isBgColorOverridden()) {
- mWindowBackground = mResources.findItemInTheme(ATTR_WINDOW_BACKGROUND, true);
+ mWindowBackground = mResources.findItemInTheme(
+ BridgeContext.createFrameworkAttrReference(ATTR_WINDOW_BACKGROUND));
mWindowBackground = mResources.resolveResValue(mWindowBackground);
}
}
private void findStatusBar() {
boolean windowFullScreen =
- getBooleanThemeValue(mResources, ATTR_WINDOW_FULL_SCREEN, true, false);
+ getBooleanThemeFrameworkAttrValue(mResources, ATTR_WINDOW_FULL_SCREEN, false);
if (!windowFullScreen && !mWindowIsFloating) {
mStatusBarSize =
- getDimension(ATTR_STATUS_BAR_HEIGHT, true, DEFAULT_STATUS_BAR_HEIGHT);
+ getFrameworkAttrDimension(ATTR_STATUS_BAR_HEIGHT, DEFAULT_STATUS_BAR_HEIGHT);
mTranslucentStatus =
- getBooleanThemeValue(mResources, ATTR_WINDOW_TRANSLUCENT_STATUS, true,
- false);
+ getBooleanThemeFrameworkAttrValue(
+ mResources, ATTR_WINDOW_TRANSLUCENT_STATUS, false);
}
}
/**
- * The behavior is different wether the App is using AppCompat or not.
+ * The behavior is different whether the App is using AppCompat or not.
* <h1>With App compat :</h1>
* <li> framework ("android:") attributes have to effect
* <li> windowNoTile=true hides the AppCompatActionBar
@@ -428,14 +455,17 @@
}
boolean windowNoTitle =
- getBooleanThemeValue(mResources, ATTR_WINDOW_NO_TITLE, false, false);
+ getBooleanThemeValue(mResources,
+ mContext.createAppCompatAttrReference(ATTR_WINDOW_NO_TITLE), false);
boolean windowActionBar =
- getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR, false, true);
+ getBooleanThemeValue(mResources,
+ mContext.createAppCompatAttrReference(ATTR_WINDOW_ACTION_BAR), true);
if (!windowNoTitle && windowActionBar) {
mAppCompatActionBarSize =
- getDimension(ATTR_ACTION_BAR_SIZE, false, DEFAULT_TITLE_BAR_HEIGHT);
+ getDimension(mContext.createAppCompatAttrReference(ATTR_ACTION_BAR_SIZE),
+ DEFAULT_TITLE_BAR_HEIGHT);
}
}
@@ -464,29 +494,28 @@
return;
}
boolean frameworkWindowNoTitle =
- getBooleanThemeValue(mResources, ATTR_WINDOW_NO_TITLE, true, false);
+ getBooleanThemeFrameworkAttrValue(mResources, ATTR_WINDOW_NO_TITLE, false);
// Check if an actionbar is needed
boolean isMenu = "menu".equals(mParams.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG));
-
boolean windowActionBar =
- getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR, true, true);
+ getBooleanThemeFrameworkAttrValue(mResources, ATTR_WINDOW_ACTION_BAR, true);
if (!frameworkWindowNoTitle || isMenu) {
if (isMenu || windowActionBar) {
mFrameworkActionBarSize =
- getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
+ getFrameworkAttrDimension(ATTR_ACTION_BAR_SIZE, DEFAULT_TITLE_BAR_HEIGHT);
} else {
- mTitleBarSize =
- getDimension(ATTR_WINDOW_TITLE_SIZE, false, DEFAULT_TITLE_BAR_HEIGHT);
+ mTitleBarSize = getDimension(
+ mContext.createAppCompatAttrReference(ATTR_WINDOW_TITLE_SIZE),
+ DEFAULT_TITLE_BAR_HEIGHT);
}
}
}
private void findNavBar() {
if (hasSoftwareButtons() && !mWindowIsFloating) {
-
// get orientation
HardwareConfig hwConfig = mParams.getHardwareConfig();
boolean barOnBottom = true;
@@ -503,19 +532,22 @@
mNavBarOrientation = barOnBottom ? LinearLayout.HORIZONTAL : VERTICAL;
mNavBarSize =
- getDimension(barOnBottom ? ATTR_NAV_BAR_HEIGHT : ATTR_NAV_BAR_WIDTH, true,
+ getFrameworkAttrDimension(
+ barOnBottom ? ATTR_NAV_BAR_HEIGHT : ATTR_NAV_BAR_WIDTH,
DEFAULT_NAV_BAR_SIZE);
mTranslucentNav =
- getBooleanThemeValue(mResources, ATTR_WINDOW_TRANSLUCENT_NAV, true, false);
+ getBooleanThemeFrameworkAttrValue(mResources, ATTR_WINDOW_TRANSLUCENT_NAV,
+ false);
}
}
@SuppressWarnings("SameParameterValue")
- private int getDimension(String attr, boolean isFramework, int defaultValue) {
- ResourceValue value = mResources.findItemInTheme(attr, isFramework);
+ private int getDimension(@NonNull ResourceReference attrRef, int defaultValue) {
+ ResourceValue value = mResources.findItemInTheme(attrRef);
value = mResources.resolveResValue(value);
if (value != null) {
- TypedValue typedValue = ResourceHelper.getValue(attr, value.getValue(), true);
+ TypedValue typedValue = ResourceHelper.getValue(attrRef.getName(), value.getValue(),
+ true);
if (typedValue != null) {
return (int) typedValue.getDimension(mContext.getMetrics());
}
@@ -523,19 +555,24 @@
return defaultValue;
}
+ @SuppressWarnings("SameParameterValue")
+ private int getFrameworkAttrDimension(@NonNull String attr, int defaultValue) {
+ return getDimension(BridgeContext.createFrameworkAttrReference(attr), defaultValue);
+ }
+
private boolean hasSoftwareButtons() {
return mParams.getHardwareConfig().hasSoftwareButtons();
}
/**
- * Return true if the nav bar is present and not translucent
+ * Returns true if the nav bar is present and not translucent.
*/
private boolean hasSolidNavBar() {
return hasNavBar() && !mTranslucentNav;
}
/**
- * Return true if the status bar is present and not translucent
+ * Returns true if the status bar is present and not translucent.
*/
private boolean hasSolidStatusBar() {
return hasStatusBar() && !mTranslucentStatus;
@@ -568,5 +605,9 @@
private boolean hasFrameworkActionBar() {
return mFrameworkActionBarSize > 0;
}
+
+ private boolean hasNotch() {
+ return !mParams.isForceNoDecor();
+ }
}
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java b/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
index 1ae9cb6..38b7aa5 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java
@@ -16,6 +16,7 @@
package com.android.layoutlib.bridge.impl;
+import com.android.ide.common.rendering.api.XmlParserFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -25,55 +26,34 @@
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* A factory for {@link XmlPullParser}.
- *
*/
public class ParserFactory {
-
public final static boolean LOG_PARSER = false;
// Used to get a new XmlPullParser from the client.
@Nullable
- private static com.android.ide.common.rendering.api.ParserFactory sParserFactory;
+ private static XmlParserFactory sParserFactory;
- public static void setParserFactory(
- @Nullable com.android.ide.common.rendering.api.ParserFactory parserFactory) {
+ public static void setParserFactory(@Nullable XmlParserFactory parserFactory) {
sParserFactory = parserFactory;
}
- @NonNull
- public static XmlPullParser create(@NonNull File f)
- throws XmlPullParserException, FileNotFoundException {
- return create(f, false);
+ @Nullable
+ public static XmlPullParser create(@NonNull String filePath)
+ throws XmlPullParserException {
+ return create(filePath, false);
}
- public static XmlPullParser create(@NonNull File f, boolean isLayout)
- throws XmlPullParserException, FileNotFoundException {
- InputStream stream = new FileInputStream(f);
- return create(stream, f.getName(), f.length(), isLayout);
- }
- @NonNull
- public static XmlPullParser create(@NonNull InputStream stream, @Nullable String name)
- throws XmlPullParserException {
- return create(stream, name, -1, false);
- }
-
- @NonNull
- private static XmlPullParser create(@NonNull InputStream stream, @Nullable String name,
- long size, boolean isLayout) throws XmlPullParserException {
- XmlPullParser parser = instantiateParser(name);
-
- stream = readAndClose(stream, name, size);
-
- parser.setInput(stream, null);
- if (isLayout) {
+ @Nullable
+ public static XmlPullParser create(@NonNull String filePath, boolean isLayout)
+ throws XmlPullParserException {
+ XmlPullParser parser = sParserFactory.createXmlParserForFile(filePath);
+ if (parser != null && isLayout) {
try {
return new LayoutParserWrapper(parser).peekTillLayoutStart();
} catch (IOException e) {
@@ -84,46 +64,38 @@
}
@NonNull
- public static XmlPullParser instantiateParser(@Nullable String name)
+ public static XmlPullParser create(@NonNull InputStream stream, @Nullable String name)
throws XmlPullParserException {
+ XmlPullParser parser = create();
+
+ stream = readAndClose(stream, name);
+
+ parser.setInput(stream, null);
+ return parser;
+ }
+
+ @NonNull
+ public static XmlPullParser create() throws XmlPullParserException {
if (sParserFactory == null) {
throw new XmlPullParserException("ParserFactory not initialized.");
}
- XmlPullParser parser = sParserFactory.createParser(name);
+ XmlPullParser parser = sParserFactory.createXmlParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
return parser;
}
@NonNull
- private static InputStream readAndClose(@NonNull InputStream stream, @Nullable String name,
- long size) throws XmlPullParserException {
- // just a sanity check. It's doubtful we'll have such big files!
- if (size > Integer.MAX_VALUE) {
- throw new XmlPullParserException("File " + name + " is too big to be parsed");
- }
- int intSize = (int) size;
+ private static InputStream readAndClose(@NonNull InputStream stream, @Nullable String name)
+ throws XmlPullParserException {
+ // Create a buffered stream to facilitate reading.
+ try (BufferedInputStream bufferedStream = new BufferedInputStream(stream)) {
+ int avail = bufferedStream.available();
- // create a buffered reader to facilitate reading.
- BufferedInputStream bufferedStream = new BufferedInputStream(stream);
- try {
- int avail;
- if (intSize != -1) {
- avail = intSize;
- } else {
- // get the size to read.
- avail = bufferedStream.available();
- }
-
- // create the initial buffer and read it.
+ // Create the initial buffer and read it.
byte[] buffer = new byte[avail];
int read = stream.read(buffer);
- // this is the easy case.
- if (read == intSize) {
- return new ByteArrayInputStream(buffer);
- }
-
- // check if there is more to read (read() does not necessarily read all that
+ // Check if there is more to read (read() does not necessarily read all that
// available() returned!)
while ((avail = bufferedStream.available()) > 0) {
if (read + avail > buffer.length) {
@@ -137,16 +109,10 @@
read += stream.read(buffer, read, avail);
}
- // return a new stream encapsulating this buffer.
+ // Return a new stream encapsulating this buffer.
return new ByteArrayInputStream(buffer);
-
} catch (IOException e) {
throw new XmlPullParserException("Failed to read " + name, null, e);
- } finally {
- try {
- bufferedStream.close();
- } catch (IOException ignored) {
- }
}
}
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
index 5b42df1..51dcae9 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java
@@ -20,17 +20,16 @@
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderParams;
import com.android.ide.common.rendering.api.RenderResources;
-import com.android.ide.common.rendering.api.RenderResources.FrameworkResourceIdProvider;
import com.android.ide.common.rendering.api.Result;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.resources.Density;
-import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;
import com.android.resources.ScreenRound;
import com.android.resources.ScreenSize;
import com.android.tools.layoutlib.annotations.VisibleForTesting;
+import android.animation.PropertyValuesHolder_Accessor;
import android.content.res.Configuration;
import android.os.HandlerThread_Delegate;
import android.util.DisplayMetrics;
@@ -39,7 +38,6 @@
import android.view.Surface;
import android.view.ViewConfiguration_Accessor;
import android.view.WindowManagerGlobal_Delegate;
-import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodManager_Accessor;
import java.util.Locale;
@@ -62,7 +60,7 @@
* @param <T> the {@link RenderParams} implementation
*
*/
-public abstract class RenderAction<T extends RenderParams> extends FrameworkResourceIdProvider {
+public abstract class RenderAction<T extends RenderParams> {
/**
* The current context being rendered. This is set through {@link #acquire(long)} and
@@ -235,16 +233,13 @@
*/
private void setUp() {
// setup the ParserFactory
- ParserFactory.setParserFactory(mParams.getLayoutlibCallback().getParserFactory());
+ ParserFactory.setParserFactory(mParams.getLayoutlibCallback());
// make sure the Resources object references the context (and other objects) for this
// scene
mContext.initResources();
sCurrentContext = mContext;
- // create an InputMethodManager
- InputMethodManager.getInstance();
-
// Set-up WindowManager
// FIXME: find those out, and possibly add them to the render params
boolean hasNavigationBar = true;
@@ -255,7 +250,6 @@
LayoutLog currentLog = mParams.getLog();
Bridge.setLog(currentLog);
- mContext.getRenderResources().setFrameworkResourceIdProvider(this);
mContext.getRenderResources().setLogger(currentLog);
}
@@ -280,16 +274,17 @@
ViewConfiguration_Accessor.clearConfigurations();
// remove the InputMethodManager
- InputMethodManager_Accessor.resetInstance();
+ InputMethodManager_Accessor.tearDownEditMode();
sCurrentContext = null;
Bridge.setLog(null);
if (mContext != null) {
- mContext.getRenderResources().setFrameworkResourceIdProvider(null);
mContext.getRenderResources().setLogger(null);
}
ParserFactory.setParserFactory(null);
+
+ PropertyValuesHolder_Accessor.clearClassCaches();
}
public static BridgeContext getCurrentContext() {
@@ -362,8 +357,8 @@
density = Density.MEDIUM;
}
- config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue();
- config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue();
+ config.screenWidthDp = hardwareConfig.getScreenWidth() * 160 / density.getDpiValue();
+ config.screenHeightDp = hardwareConfig.getScreenHeight() * 160 / density.getDpiValue();
if (config.screenHeightDp < config.screenWidthDp) {
//noinspection SuspiciousNameCombination
config.smallestScreenWidthDp = config.screenHeightDp;
@@ -413,12 +408,4 @@
return config;
}
-
-
- // --- FrameworkResourceIdProvider methods
-
- @Override
- public Integer getId(ResourceType resType, String resName) {
- return Bridge.getResourceId(resType, resName);
- }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
index d797eec..35ec77b 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
@@ -25,6 +25,7 @@
import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.resources.ResourceType;
+import android.annotation.NonNull;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
import android.graphics.Canvas;
@@ -43,28 +44,28 @@
import java.util.List;
/**
- * Action to render a given Drawable provided through {@link DrawableParams#getDrawable()}.
+ * Action to render a given {@link Drawable} provided through {@link DrawableParams#getDrawable()}.
*
* The class only provides a simple {@link #render()} method, but the full life-cycle of the
* action must be respected.
*
* @see RenderAction
- *
*/
public class RenderDrawable extends RenderAction<DrawableParams> {
- public RenderDrawable(DrawableParams params) {
+ public RenderDrawable(@NonNull DrawableParams params) {
super(new DrawableParams(params));
}
+ @NonNull
public Result render() {
checkLock();
- // get the drawable resource value
+ // Get the drawable resource value.
DrawableParams params = getParams();
HardwareConfig hardwareConfig = params.getHardwareConfig();
ResourceValue drawableResource = params.getDrawable();
- // resolve it
+ // Resolve it.
BridgeContext context = getContext();
drawableResource = context.getRenderResources().resolveResValue(drawableResource);
@@ -78,17 +79,19 @@
}
Drawable d = ResourceHelper.getDrawable(drawableResource, context);
+ if (d == null) {
+ return Status.ERROR_NOT_A_DRAWABLE.createResult();
+ }
- final Boolean allStates =
- params.getFlag(RenderParamsFlags.FLAG_KEY_RENDER_ALL_DRAWABLE_STATES);
+ Boolean allStates = params.getFlag(RenderParamsFlags.FLAG_KEY_RENDER_ALL_DRAWABLE_STATES);
if (allStates == Boolean.TRUE) {
- final List<BufferedImage> result;
+ List<BufferedImage> result;
if (d instanceof StateListDrawable) {
result = new ArrayList<BufferedImage>();
- final StateListDrawable stateList = (StateListDrawable) d;
+ StateListDrawable stateList = (StateListDrawable) d;
for (int i = 0; i < stateList.getStateCount(); i++) {
- final Drawable stateDrawable = stateList.getStateDrawable(i);
+ Drawable stateDrawable = stateList.getStateDrawable(i);
result.add(renderImage(hardwareConfig, stateDrawable, context));
}
} else {
@@ -102,33 +105,33 @@
}
}
- private BufferedImage renderImage(HardwareConfig hardwareConfig, Drawable d,
- BridgeContext context) {
- // create a simple FrameLayout
+ @NonNull
+ private BufferedImage renderImage(@NonNull HardwareConfig hardwareConfig, @NonNull Drawable d,
+ @NonNull BridgeContext context) {
+ // Create a simple FrameLayout.
FrameLayout content = new FrameLayout(context);
- // get the actual Drawable object to draw
+ // Get the actual Drawable object to draw.
content.setBackground(d);
- // set the AttachInfo on the root view.
+ // Set the AttachInfo on the root view.
AttachInfo_Accessor.setAttachInfo(content);
-
- // measure
+ // Measure.
int w = d.getIntrinsicWidth();
int h = d.getIntrinsicHeight();
- final int screenWidth = hardwareConfig.getScreenWidth();
- final int screenHeight = hardwareConfig.getScreenHeight();
+ int screenWidth = hardwareConfig.getScreenWidth();
+ int screenHeight = hardwareConfig.getScreenHeight();
if (w == -1 || h == -1) {
- // Use screen size when either intrinsic width or height isn't available
+ // Use screen size when either intrinsic width or height isn't available.
w = screenWidth;
h = screenHeight;
} else if (w > screenWidth || h > screenHeight) {
// If image wouldn't fit to the screen, resize it to avoid cropping.
- // We need to find scale such that scale * w <= screenWidth, scale * h <= screenHeight
+ // We need to find scale such that scale * w <= screenWidth, scale * h <= screenHeight.
double scale = Math.min((double) screenWidth / w, (double) screenHeight / h);
// scale * w / scale * h = w / h, so, proportions are preserved.
@@ -140,28 +143,29 @@
int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
content.measure(w_spec, h_spec);
- // now do the layout.
+ // Now do the layout.
content.layout(0, 0, w, h);
- // preDraw setup
+ // Pre-draw setup.
AttachInfo_Accessor.dispatchOnPreDraw(content);
- // draw into a new image
+ // Draw into a new image.
BufferedImage image = getImage(w, h);
- // create an Android bitmap around the BufferedImage
+ // Create an Android bitmap around the BufferedImage.
Bitmap bitmap = Bitmap_Delegate.createBitmap(image,
true /*isMutable*/, hardwareConfig.getDensity());
- // create a Canvas around the Android bitmap
+ // Create a Canvas around the Android bitmap.
Canvas canvas = new Canvas(bitmap);
canvas.setDensity(hardwareConfig.getDensity().getDpiValue());
- // and draw
+ // Draw.
content.draw(canvas);
return image;
}
+ @NonNull
protected BufferedImage getImage(int w, int h) {
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D gc = image.createGraphics();
@@ -175,5 +179,4 @@
return image;
}
-
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 7168b54..9c314b8 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -18,9 +18,9 @@
import com.android.ide.common.rendering.api.AdapterBinding;
import com.android.ide.common.rendering.api.HardwareConfig;
+import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
@@ -45,11 +45,8 @@
import com.android.layoutlib.bridge.android.support.SupportPreferencesUtil;
import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
-import com.android.layoutlib.bridge.util.ReflectionUtils;
-import com.android.resources.ResourceType;
import com.android.tools.layoutlib.java.System_Delegate;
import com.android.util.Pair;
-import com.android.util.PropertiesMap;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -57,6 +54,7 @@
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
import android.graphics.Canvas;
+import android.graphics.NinePatch_Delegate;
import android.os.Looper;
import android.preference.Preference_Delegate;
import android.view.AttachInfo_Accessor;
@@ -86,6 +84,7 @@
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -171,8 +170,9 @@
BridgeContext context = getContext();
// use default of true in case it's not found to use alpha by default
- mIsAlphaChannelImage = ResourceHelper.getBooleanThemeValue(params.getResources(),
- "windowIsFloating", true, true);
+ mIsAlphaChannelImage =
+ ResourceHelper.getBooleanThemeFrameworkAttrValue(params.getResources(),
+ "windowIsFloating", true);
mLayoutBuilder = new Layout.Builder(params, context);
@@ -180,7 +180,8 @@
mInflater = new BridgeInflater(context, params.getLayoutlibCallback());
context.setBridgeInflater(mInflater);
- mBlockParser = new BridgeXmlBlockParser(params.getLayoutDescription(), context, false);
+ ILayoutPullParser layoutParser = params.getLayoutDescription();
+ mBlockParser = new BridgeXmlBlockParser(layoutParser, context, layoutParser.getLayoutNamespace());
return SUCCESS.createResult();
}
@@ -296,7 +297,8 @@
Bridge.getLog().warning(LayoutLog.TAG_RTL_NOT_ENABLED,
"You are using a right-to-left " +
"(RTL) locale but RTL is not enabled", null);
- } else if (params.getSimulatedPlatformVersion() < 17) {
+ } else if (params.getSimulatedPlatformVersion() !=0 &&
+ params.getSimulatedPlatformVersion() < 17) {
// This will render ok because we are using the latest layoutlib but at least
// warn the user that this might fail in a real device.
Bridge.getLog().warning(LayoutLog.TAG_RTL_NOT_SUPPORTED, "You are using a " +
@@ -310,7 +312,8 @@
Fragment_Delegate.setLayoutlibCallback(params.getLayoutlibCallback());
String rootTag = params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG);
- boolean isPreference = "PreferenceScreen".equals(rootTag);
+ boolean isPreference = "PreferenceScreen".equals(rootTag) ||
+ SupportPreferencesUtil.isSupportRootTag(rootTag);
View view;
if (isPreference) {
// First try to use the support library inflater. If something fails, fallback
@@ -484,6 +487,7 @@
// it doesn't get cached.
boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag(
RenderParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING));
+
if (mNewRenderSize || mCanvas == null || disableBitmapCaching) {
mNewRenderSize = false;
if (params.getImageFactory() != null) {
@@ -518,6 +522,19 @@
} else {
mCanvas.setBitmap(bitmap);
}
+
+ boolean enableImageResizing =
+ mImage.getWidth() != mMeasuredScreenWidth &&
+ mImage.getHeight() != mMeasuredScreenHeight &&
+ Boolean.TRUE.equals(params.getFlag(
+ RenderParamsFlags.FLAG_KEY_RESULT_IMAGE_AUTO_SCALE));
+
+ if (enableImageResizing) {
+ float scaleX = (float)mImage.getWidth() / mMeasuredScreenWidth;
+ float scaleY = (float)mImage.getHeight() / mMeasuredScreenHeight;
+ mCanvas.scale(scaleX, scaleY);
+ }
+
mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
}
@@ -715,12 +732,7 @@
if (!hasToolbar(collapsingToolbar)) {
return;
}
- RenderResources res = context.getRenderResources();
String title = params.getAppLabel();
- ResourceValue titleValue = res.findResValue(title, false);
- if (titleValue != null && titleValue.getValue() != null) {
- title = titleValue.getValue();
- }
DesignLibUtil.setTitle(collapsingToolbar, title);
}
@@ -832,7 +844,7 @@
// this must be called before addTab() so that the TabHost searches its TabWidget
// and FrameLayout.
- if (ReflectionUtils.isInstanceOf(tabHost, FragmentTabHostUtil.CN_FRAGMENT_TAB_HOST)) {
+ if (isInstanceOf(tabHost, FragmentTabHostUtil.CN_FRAGMENT_TAB_HOST)) {
FragmentTabHostUtil.setup(tabHost, getContext());
} else {
tabHost.setup();
@@ -853,10 +865,10 @@
@SuppressWarnings("ConstantConditions") // child cannot be null.
int id = child.getId();
@SuppressWarnings("deprecation")
- Pair<ResourceType, String> resource = layoutlibCallback.resolveResourceId(id);
+ ResourceReference resource = layoutlibCallback.resolveResourceId(id);
String name;
if (resource != null) {
- name = resource.getSecond();
+ name = resource.getName();
} else {
name = String.format("Tab %d", i+1); // default name if id is unresolved.
}
@@ -992,10 +1004,14 @@
// The view is part of the layout added by the user. Hence,
// the ViewCookie may be obtained only through the Context.
+ int shiftX = -scrollX + Math.round(view.getTranslationX()) + hOffset;
+ int shiftY = -scrollY + Math.round(view.getTranslationY()) + vOffset;
result = new ViewInfo(view.getClass().getName(),
- getContext().getViewKey(view), -scrollX + view.getLeft() + hOffset,
- -scrollY + view.getTop() + vOffset, -scrollX + view.getRight() + hOffset,
- -scrollY + view.getBottom() + vOffset,
+ getContext().getViewKey(view),
+ shiftX + view.getLeft(),
+ shiftY + view.getTop(),
+ shiftX + view.getRight(),
+ shiftY + view.getBottom(),
view, view.getLayoutParams());
} else {
// We are part of the system decor.
@@ -1098,10 +1114,24 @@
return mSystemViewInfoList;
}
- public Map<Object, PropertiesMap> getDefaultProperties() {
+ public Map<Object, Map<ResourceReference, ResourceValue>> getDefaultNamespacedProperties() {
return getContext().getDefaultProperties();
}
+ public Map<Object, String> getDefaultStyles() {
+ Map<Object, String> defaultStyles = new IdentityHashMap<>();
+ Map<Object, ResourceReference> namespacedStyles = getDefaultNamespacedStyles();
+ for (Object key : namespacedStyles.keySet()) {
+ ResourceReference style = namespacedStyles.get(key);
+ defaultStyles.put(key, style.getQualifiedName());
+ }
+ return defaultStyles;
+ }
+
+ public Map<Object, ResourceReference> getDefaultNamespacedStyles() {
+ return getContext().getDefaultNamespacedStyles();
+ }
+
public void setScene(RenderSession session) {
mScene = session;
}
@@ -1133,6 +1163,7 @@
mImage = null;
mViewRoot = null;
mContentRoot = null;
+ NinePatch_Delegate.clearCache();
if (createdLooper) {
Choreographer_Delegate.dispose();
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index 16f92f3..d3b934f 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -13,38 +13,42 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.layoutlib.bridge.impl;
import com.android.SdkConstants;
+import com.android.ide.common.rendering.api.AssetRepository;
import com.android.ide.common.rendering.api.DensityBasedResourceValue;
+import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.internal.util.XmlUtils;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeContext.Key;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.ninepatch.NinePatch;
import com.android.ninepatch.NinePatchChunk;
import com.android.resources.Density;
+import com.android.resources.ResourceType;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.BridgeAssetManager;
import android.content.res.ColorStateList;
import android.content.res.ComplexColor;
import android.content.res.ComplexColor_Accessor;
-import android.content.res.FontResourcesParser;
import android.content.res.GradientColor;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
-import android.graphics.Color;
import android.graphics.NinePatch_Delegate;
import android.graphics.Rect;
import android.graphics.Typeface;
@@ -54,30 +58,33 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
-import android.text.FontConfig;
import android.util.TypedValue;
-import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
+import java.util.HashSet;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import static android.content.res.AssetManager.ACCESS_STREAMING;
+
/**
* Helper class to provide various conversion method used in handling android resources.
*/
public final class ResourceHelper {
+ private static final Key<Set<ResourceValue>> KEY_GET_DRAWABLE =
+ Key.create("ResourceHelper.getDrawable");
+ private static final Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)");
+ private static final float[] sFloatOut = new float[1];
- private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)");
- private final static float[] sFloatOut = new float[1];
-
- private final static TypedValue mValue = new TypedValue();
+ private static final TypedValue mValue = new TypedValue();
/**
- * Returns the color value represented by the given string value
+ * Returns the color value represented by the given string value.
+ *
* @param value the color value
* @return the color as an int
* @throws NumberFormatException if the conversion failed.
@@ -242,10 +249,12 @@
/**
* Returns a drawable from the given value.
+ *
* @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
- * or an hexadecimal color
+ * or an hexadecimal color
* @param context the current context
*/
+ @Nullable
public static Drawable getDrawable(ResourceValue value, BridgeContext context) {
return getDrawable(value, context, null);
}
@@ -256,38 +265,43 @@
*/
@Nullable
public static BridgeXmlBlockParser getXmlBlockParser(@NonNull BridgeContext context,
- @NonNull ResourceValue value)
- throws FileNotFoundException, XmlPullParserException {
+ @NonNull ResourceValue value) throws XmlPullParserException {
String stringValue = value.getValue();
if (RenderResources.REFERENCE_NULL.equals(stringValue)) {
return null;
}
XmlPullParser parser = null;
+ ResourceNamespace namespace;
+ LayoutlibCallback layoutlibCallback = context.getLayoutlibCallback();
// Framework values never need a PSI parser. They do not change and the do not contain
// aapt:attr attributes.
if (!value.isFramework()) {
- parser = context.getLayoutlibCallback().getParser(value);
+ parser = layoutlibCallback.getParser(value);
}
- if (parser == null) {
- File xmlFile = new File(stringValue);
- if (xmlFile.isFile()) {
- parser = ParserFactory.create(xmlFile);
- }
+ if (parser != null) {
+ namespace = ((ILayoutPullParser) parser).getLayoutNamespace();
+ } else {
+ parser = ParserFactory.create(stringValue);
+ namespace = value.getNamespace();
}
- return new BridgeXmlBlockParser(parser, context, value.isFramework());
+ return parser == null
+ ? null
+ : new BridgeXmlBlockParser(parser, context, namespace);
}
/**
* Returns a drawable from the given value.
+ *
* @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable,
- * or an hexadecimal color
+ * or an hexadecimal color
* @param context the current context
* @param theme the theme to be used to inflate the drawable.
*/
+ @Nullable
public static Drawable getDrawable(ResourceValue value, BridgeContext context, Theme theme) {
if (value == null) {
return null;
@@ -314,27 +328,35 @@
}
if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) {
- File file = new File(stringValue);
- if (file.isFile()) {
- try {
- return getNinePatchDrawable(new FileInputStream(file), density,
- value.isFramework(), stringValue, context);
- } catch (IOException e) {
- // failed to read the file, we'll return null below.
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed lot load " + file.getAbsolutePath(), e, null /*data*/);
- }
+ try {
+ return getNinePatchDrawable(density, value.isFramework(), stringValue, context);
+ } catch (IOException e) {
+ // failed to read the file, we'll return null below.
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed to load " + stringValue, e, null /*data*/);
}
return null;
- } else if (lowerCaseValue.endsWith(".xml") || stringValue.startsWith("@aapt:_aapt/")) {
+ } else if (lowerCaseValue.endsWith(".xml") ||
+ value.getResourceType() == ResourceType.AAPT) {
// create a block parser for the file
try {
BridgeXmlBlockParser blockParser = getXmlBlockParser(context, value);
if (blockParser != null) {
+ Set<ResourceValue> visitedValues = context.getUserData(KEY_GET_DRAWABLE);
+ if (visitedValues == null) {
+ visitedValues = new HashSet<>();
+ context.putUserData(KEY_GET_DRAWABLE, visitedValues);
+ }
+ if (!visitedValues.add(value)) {
+ Bridge.getLog().error(null, "Cyclic dependency in " + stringValue, null);
+ return null;
+ }
+
try {
return Drawable.createFromXml(context.getResources(), blockParser, theme);
} finally {
+ visitedValues.remove(value);
blockParser.ensurePopped();
}
}
@@ -347,15 +369,22 @@
return null;
} else {
- File bmpFile = new File(stringValue);
- if (bmpFile.isFile()) {
+ AssetRepository repository = getAssetRepository(context);
+ if (repository.isFileResource(stringValue)) {
try {
Bitmap bitmap = Bridge.getCachedBitmap(stringValue,
value.isFramework() ? null : context.getProjectKey());
if (bitmap == null) {
+ InputStream stream;
+ try {
+ stream = repository.openNonAsset(0, stringValue, ACCESS_STREAMING);
+
+ } catch (FileNotFoundException e) {
+ stream = null;
+ }
bitmap =
- Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/, density);
+ Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density);
Bridge.setCachedBitmap(stringValue, bitmap,
value.isFramework() ? null : context.getProjectKey());
}
@@ -364,7 +393,7 @@
} catch (IOException e) {
// we'll return null below
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed lot load " + bmpFile.getAbsolutePath(), e, null /*data*/);
+ "Failed to load " + stringValue, e, null /*data*/);
}
}
}
@@ -372,6 +401,11 @@
return null;
}
+ private static AssetRepository getAssetRepository(@NonNull BridgeContext context) {
+ BridgeAssetManager assetManager = context.getAssets();
+ return assetManager.getAssetRepository();
+ }
+
/**
* Returns a {@link Typeface} given a font name. The font name, can be a system font family
* (like sans-serif) or a full path if the font is to be loaded from resources.
@@ -404,24 +438,29 @@
return getFont(value.getValue(), context, theme, value.isFramework());
}
- private static Drawable getNinePatchDrawable(InputStream inputStream, Density density,
- boolean isFramework, String cacheKey, BridgeContext context) throws IOException {
+ private static Drawable getNinePatchDrawable(Density density, boolean isFramework,
+ String path, BridgeContext context) throws IOException {
// see if we still have both the chunk and the bitmap in the caches
- NinePatchChunk chunk = Bridge.getCached9Patch(cacheKey,
+ NinePatchChunk chunk = Bridge.getCached9Patch(path,
isFramework ? null : context.getProjectKey());
- Bitmap bitmap = Bridge.getCachedBitmap(cacheKey,
+ Bitmap bitmap = Bridge.getCachedBitmap(path,
isFramework ? null : context.getProjectKey());
// if either chunk or bitmap is null, then we reload the 9-patch file.
if (chunk == null || bitmap == null) {
try {
- NinePatch ninePatch = NinePatch.load(inputStream, true /*is9Patch*/,
+ AssetRepository repository = getAssetRepository(context);
+ if (!repository.isFileResource(path)) {
+ return null;
+ }
+ InputStream stream = repository.openNonAsset(0, path, ACCESS_STREAMING);
+ NinePatch ninePatch = NinePatch.load(stream, true /*is9Patch*/,
false /* convert */);
if (ninePatch != null) {
if (chunk == null) {
chunk = ninePatch.getChunk();
- Bridge.setCached9Patch(cacheKey, chunk,
+ Bridge.setCached9Patch(path, chunk,
isFramework ? null : context.getProjectKey());
}
@@ -430,7 +469,7 @@
false /*isMutable*/,
density);
- Bridge.setCachedBitmap(cacheKey, bitmap,
+ Bridge.setCachedBitmap(path, bitmap,
isFramework ? null : context.getProjectKey());
}
}
@@ -455,14 +494,13 @@
* Looks for an attribute in the current theme.
*
* @param resources the render resources
- * @param name the name of the attribute
+ * @param attr the attribute reference
* @param defaultValue the default value.
- * @param isFrameworkAttr if the attribute is in android namespace
* @return the value of the attribute or the default one if not found.
*/
- public static boolean getBooleanThemeValue(@NonNull RenderResources resources, String name,
- boolean isFrameworkAttr, boolean defaultValue) {
- ResourceValue value = resources.findItemInTheme(name, isFrameworkAttr);
+ public static boolean getBooleanThemeValue(@NonNull RenderResources resources,
+ @NonNull ResourceReference attr, boolean defaultValue) {
+ ResourceValue value = resources.findItemInTheme(attr);
value = resources.resolveResValue(value);
if (value == null) {
return defaultValue;
@@ -470,6 +508,20 @@
return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue);
}
+ /**
+ * Looks for a framework attribute in the current theme.
+ *
+ * @param resources the render resources
+ * @param name the name of the attribute
+ * @param defaultValue the default value.
+ * @return the value of the attribute or the default one if not found.
+ */
+ public static boolean getBooleanThemeFrameworkAttrValue(@NonNull RenderResources resources,
+ @NonNull String name, boolean defaultValue) {
+ ResourceReference attrRef = BridgeContext.createFrameworkAttrReference(name);
+ return getBooleanThemeValue(resources, attrRef, defaultValue);
+ }
+
// ------- TypedValue stuff
// This is taken from //device/libs/utils/ResourceTypes.cpp
@@ -487,7 +539,7 @@
}
}
- private final static UnitEntry[] sUnitNames = new UnitEntry[] {
+ private static final UnitEntry[] sUnitNames = new UnitEntry[] {
new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f),
new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/SysUiOverlay.java b/bridge/src/com/android/layoutlib/bridge/impl/SysUiOverlay.java
new file mode 100644
index 0000000..8900c06
--- /dev/null
+++ b/bridge/src/com/android/layoutlib/bridge/impl/SysUiOverlay.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 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.layoutlib.bridge.impl;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+
+class SysUiOverlay extends View {
+ private final Path mCornerPath;
+ private final Path mNotchPath;
+ private final Paint mCornerPaint;
+ private final Paint mNotchPaint;
+ private final int mNotchTopWidth;
+
+
+ private static Path createCornerPath(float width, float height, float r) {
+ Path corner = new Path();
+ corner.moveTo(0, 0);
+ corner.lineTo(width, 0);
+ corner.cubicTo(width, 0, width /2 - r, height /2 - r, 0, height);
+ corner.close();
+
+ return corner;
+ }
+
+ private static Path createNotchPath(float topWidth, float bottomWidth, float height, float r) {
+ Path corner = new Path();
+ corner.moveTo(0, 0);
+ float widthDiff = topWidth - bottomWidth;
+ corner.lineTo(topWidth, 0);
+ corner.lineTo(topWidth - widthDiff/2, height);
+ corner.lineTo(widthDiff/2, height);
+ corner.close();
+
+ return corner;
+ }
+
+
+ private static float dpToPx(DisplayMetrics metrics, float value) {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, value, metrics);
+ }
+
+
+ public SysUiOverlay(Context context, int cornerSizeDp, int radiusDp, int topNotchWidthDp, int bottomNotchWidthDp, int notchHeightDp) {
+ super(context);
+
+ DisplayMetrics metrics = context.getResources().getDisplayMetrics();
+
+ float cornerSizePx = dpToPx(metrics, cornerSizeDp);
+ float radiusPx = dpToPx(metrics, radiusDp);
+
+ float topNotchWidthPx = dpToPx(metrics, topNotchWidthDp);
+ float bottomNotchWidthPx = dpToPx(metrics, bottomNotchWidthDp);
+ float notchHeightPx = dpToPx(metrics, notchHeightDp);
+
+ mCornerPath = createCornerPath(cornerSizePx, cornerSizePx, radiusPx);
+ mNotchPath = createNotchPath(topNotchWidthPx, bottomNotchWidthPx, notchHeightPx, 0);
+ mNotchTopWidth = topNotchWidthDp;
+
+ mCornerPaint = new Paint();
+ mCornerPaint.setColor(Color.BLACK);
+ mCornerPaint.setStrokeWidth(0);
+ mCornerPaint.setAntiAlias(true);
+
+ mNotchPaint = new Paint();
+ mNotchPaint.setColor(Color.BLACK);
+ mNotchPaint.setStrokeWidth(0);
+ mNotchPaint.setAntiAlias(true);
+ }
+
+ public void setCornerColor(int color) {
+ mCornerPaint.setColor(color);
+ }
+
+ public void setNotchColor(int color) {
+ mNotchPaint.setColor(color);
+ }
+
+ private void paintRoundedBorders(Canvas canvas) {
+ // Top left
+ canvas.drawPath(mCornerPath, mCornerPaint);
+ // Top right
+ canvas.save();
+ canvas.translate(getWidth(), 0);
+ canvas.rotate(90);
+ canvas.drawPath(mCornerPath, mCornerPaint);
+ canvas.restore();
+ // Bottom right
+ canvas.save();
+ canvas.translate( getWidth(), getHeight());
+ canvas.rotate(180);
+ canvas.drawPath(mCornerPath, mCornerPaint);
+ canvas.restore();
+ // Bottom left
+ canvas.save();
+ canvas.translate( 0, getHeight());
+ canvas.rotate(270);
+ canvas.drawPath(mCornerPath, mCornerPaint);
+ canvas.restore();
+ }
+
+ private void paintNotch(Canvas canvas) {
+ canvas.translate(getWidth() / 2 - mNotchTopWidth / 2, 0);
+ canvas.drawPath(mNotchPath, mNotchPaint);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ paintRoundedBorders(canvas);
+ paintNotch(canvas);
+
+ }
+}
\ No newline at end of file
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java b/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
index c0ca1d5..f830b07 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/binding/AdapterHelper.java
@@ -19,8 +19,8 @@
import com.android.ide.common.rendering.api.DataBindingItem;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback.ViewAttribute;
import com.android.ide.common.rendering.api.ResourceReference;
-import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.RenderAction;
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java b/bridge/src/com/android/layoutlib/bridge/resources/IconLoader.java
similarity index 93%
rename from bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java
rename to bridge/src/com/android/layoutlib/bridge/resources/IconLoader.java
index 9ab2e82..df4f252 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java
+++ b/bridge/src/com/android/layoutlib/bridge/resources/IconLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.layoutlib.bridge.bars;
+package com.android.layoutlib.bridge.resources;
+import com.android.layoutlib.bridge.bars.Config;
import com.android.resources.Density;
import com.android.resources.LayoutDirection;
@@ -31,7 +32,8 @@
private Density mCurrentDensity;
private StringBuilder mCurrentPath;
- IconLoader(String iconName, Density density, int platformVersion, LayoutDirection direction) {
+ public IconLoader(String iconName, Density density, int platformVersion, LayoutDirection
+ direction) {
mIconName = iconName;
mDesiredDensity = density;
mPlatformVersion = platformVersion;
diff --git a/bridge/src/com/android/layoutlib/bridge/resources/SysUiResources.java b/bridge/src/com/android/layoutlib/bridge/resources/SysUiResources.java
new file mode 100644
index 0000000..96a6e34
--- /dev/null
+++ b/bridge/src/com/android/layoutlib/bridge/resources/SysUiResources.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 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.layoutlib.bridge.resources;
+
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
+import com.android.layoutlib.bridge.bars.Config;
+import com.android.layoutlib.bridge.impl.ParserFactory;
+import com.android.resources.Density;
+import com.android.resources.LayoutDirection;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.drawable.BitmapDrawable;
+import android.widget.ImageView;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class SysUiResources {
+ private static final ResourceNamespace PRIVATE_LAYOUTLIB_NAMESPACE =
+ ResourceNamespace.fromPackageName("com.android.layoutlib");
+
+ @NotNull
+ public static BridgeXmlBlockParser loadXml(BridgeContext context, int apiLevel, String
+ layoutName) {
+ for (String resourceRepository : Config.getResourceDirs(apiLevel)) {
+ String path = resourceRepository + layoutName;
+ InputStream stream = SysUiResources.class.getResourceAsStream(path);
+ if (stream != null) {
+ try {
+ XmlPullParser parser = ParserFactory.create(stream, layoutName);
+
+ // TODO(namespaces): does the namespace matter here?
+ return new BridgeXmlBlockParser(parser, context, PRIVATE_LAYOUTLIB_NAMESPACE);
+ } catch (XmlPullParserException e) {
+ // Should not happen as the resource is bundled with the jar, and ParserFactory should
+ // have been initialized.
+ assert false;
+ }
+ }
+ }
+
+ assert false;
+ return null;
+ }
+
+ public static ImageView loadIcon(Context context, int api, ImageView imageView, String
+ iconName,
+ Density density, boolean
+ isRtl) {
+ LayoutDirection dir = isRtl ? LayoutDirection.RTL : null;
+ IconLoader iconLoader = new IconLoader(iconName, density, api,
+ dir);
+ InputStream stream = iconLoader.getIcon();
+
+ if (stream != null) {
+ density = iconLoader.getDensity();
+ String path = iconLoader.getPath();
+ // look for a cached bitmap
+ Bitmap bitmap = Bridge.getCachedBitmap(path, Boolean.TRUE /*isFramework*/);
+ if (bitmap == null) {
+ try {
+ bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density);
+ Bridge.setCachedBitmap(path, bitmap, Boolean.TRUE /*isFramework*/);
+ } catch (IOException e) {
+ return imageView;
+ }
+ }
+
+ if (bitmap != null) {
+ BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
+ imageView.setImageDrawable(drawable);
+ }
+ }
+
+ return imageView;
+ }
+}
diff --git a/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java b/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
index f149b6c..75e4a2b 100644
--- a/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
+++ b/bridge/src/com/android/layoutlib/bridge/util/NinePatchInputStream.java
@@ -16,9 +16,13 @@
package com.android.layoutlib.bridge.util;
+import com.android.tools.layoutlib.annotations.NotNull;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
/**
* Simpler wrapper around FileInputStream. This is used when the input stream represent
@@ -26,17 +30,22 @@
* This is useful when the InputStream is created in a method but used in another that needs
* to know whether this is 9-patch or not, such as BitmapFactory.
*/
-public class NinePatchInputStream extends FileInputStream {
+public class NinePatchInputStream extends InputStream {
+ private final InputStream mDelegate;
private boolean mFakeMarkSupport = true;
+
public NinePatchInputStream(File file) throws FileNotFoundException {
- super(file);
+ mDelegate = new FileInputStream(file);
+ }
+
+ public NinePatchInputStream(@NotNull InputStream stream) {
+ mDelegate = stream;
}
@Override
public boolean markSupported() {
// this is needed so that BitmapFactory doesn't wrap this in a BufferedInputStream.
- return mFakeMarkSupport || super.markSupported();
-
+ return mFakeMarkSupport || mDelegate.markSupported();
}
public void disableFakeMarkSupport() {
@@ -44,4 +53,44 @@
// we don't lie to them.
mFakeMarkSupport = false;
}
+
+ @Override
+ public int read() throws IOException {
+ return mDelegate.read();
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return mDelegate.read(b);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ return mDelegate.read(b, off, len);
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ return mDelegate.skip(n);
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mDelegate.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mDelegate.close();
+ }
+
+ @Override
+ public void mark(int readlimit) {
+ mDelegate.mark(readlimit);
+ }
+
+ @Override
+ public void reset() throws IOException {
+ mDelegate.reset();
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
index 64d100a..00348ea 100644
--- a/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
+++ b/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
@@ -77,21 +77,30 @@
}
/**
- * Check if the object is an instance of any of the class named in {@code className}. This
- * doesn't work for interfaces.
+ * Check if the object is an instance of a class named {@code className}. This doesn't work
+ * for interfaces.
*/
public static boolean isInstanceOf(Object object, String[] classNames) {
+ return getParentClass(object, classNames) != null;
+ }
+
+ /**
+ * Check if the object is an instance of any of the class named in {@code className} and
+ * returns the name of the parent class that matched. This doesn't work for interfaces.
+ */
+ @Nullable
+ public static String getParentClass(Object object, String[] classNames) {
Class superClass = object.getClass();
while (superClass != null) {
String name = superClass.getName();
for (String className : classNames) {
if (name.equals(className)) {
- return true;
+ return className;
}
}
superClass = superClass.getSuperclass();
}
- return false;
+ return null;
}
@NonNull
diff --git a/bridge/src/dalvik/system/VMRuntime_Delegate.java b/bridge/src/dalvik/system/VMRuntime_Delegate.java
index 36efc3a..648d2be 100644
--- a/bridge/src/dalvik/system/VMRuntime_Delegate.java
+++ b/bridge/src/dalvik/system/VMRuntime_Delegate.java
@@ -75,4 +75,10 @@
}
}
+ @LayoutlibDelegate
+ /*package*/ static int getNotifyNativeInterval() {
+ // This cannot return 0, otherwise it is responsible for triggering an exception
+ // whenever trying to use a NativeAllocationRegistry with size 0
+ return 1;
+ }
}
diff --git a/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java b/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
index 04fabc2..c739c65 100644
--- a/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
+++ b/bridge/src/libcore/util/NativeAllocationRegistry_Delegate.java
@@ -19,6 +19,10 @@
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import libcore.util.NativeAllocationRegistry.CleanerRunner;
+import libcore.util.NativeAllocationRegistry.CleanerThunk;
+import sun.misc.Cleaner;
+
/**
* Delegate implementing the native methods of {@link NativeAllocationRegistry}
*
@@ -51,6 +55,44 @@
}
@LayoutlibDelegate
+ /*package*/ static void registerNativeAllocation(long size) {
+ NativeAllocationRegistry.registerNativeAllocation_Original(size);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Runnable registerNativeAllocation(NativeAllocationRegistry registry,
+ Object referent,
+ long nativePtr) {
+ // Mark the object as already "natively" tracked.
+ // This allows the DelegateManager to dispose objects without waiting
+ // for an explicit call when the referent does not exist anymore.
+ sManager.markAsNativeAllocation(referent, nativePtr);
+ if (referent == null) {
+ throw new IllegalArgumentException("referent is null");
+ }
+ if (nativePtr == 0) {
+ throw new IllegalArgumentException("nativePtr is null");
+ }
+
+ CleanerThunk thunk;
+ CleanerRunner result;
+ try {
+ thunk = registry.new CleanerThunk();
+ Cleaner cleaner = Cleaner.create(referent, thunk);
+ result = new CleanerRunner(cleaner);
+ registerNativeAllocation(registry.size);
+ } catch (VirtualMachineError vme /* probably OutOfMemoryError */) {
+ applyFreeFunction(registry.freeFunction, nativePtr);
+ throw vme;
+ } // Other exceptions are impossible.
+ // Enable the cleaner only after we can no longer throw anything, including OOME.
+ thunk.setNativePtr(nativePtr);
+ // Needs to call Reference.reachabilityFence(referent) to ensure that cleaner doesn't
+ // get invoked before we enable it. Unfortunately impossible in OpenJDK 8.
+ return result;
+ }
+
+ @LayoutlibDelegate
/*package*/ static void applyFreeFunction(long freeFunction, long nativePtr) {
// This method MIGHT run in the context of the finalizer thread. If the delegate method
// crashes, it could bring down the VM. That's why we catch all the exceptions and ignore
diff --git a/bridge/tests/.classpath b/bridge/tests/.classpath
deleted file mode 100644
index 2b32e09..0000000
--- a/bridge/tests/.classpath
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="src" path="res"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry combineaccessrules="false" kind="src" path="/layoutlib_bridge"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/>
- <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/bridge/tests/.project b/bridge/tests/.project
deleted file mode 100644
index 2325eed..0000000
--- a/bridge/tests/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>layoutlib_bridge-tests</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
diff --git a/bridge/tests/res/testApp/MyApplication/.gitignore b/bridge/tests/res/testApp/MyApplication/.gitignore
index a2ce0dc..135b1bc 100644
--- a/bridge/tests/res/testApp/MyApplication/.gitignore
+++ b/bridge/tests/res/testApp/MyApplication/.gitignore
@@ -11,4 +11,4 @@
!/build/intermediates/
/build/intermediates/*
-!/build/intermediates/classes/
+!/build/intermediates/javac/
diff --git a/bridge/tests/res/testApp/MyApplication/build.gradle b/bridge/tests/res/testApp/MyApplication/build.gradle
index c9058b5..0c6deb4 100644
--- a/bridge/tests/res/testApp/MyApplication/build.gradle
+++ b/bridge/tests/res/testApp/MyApplication/build.gradle
@@ -1,9 +1,10 @@
buildscript {
repositories {
jcenter()
+ google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.0.0-beta5'
+ classpath 'com.android.tools.build:gradle:3.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -13,18 +14,18 @@
allprojects {
repositories {
jcenter()
+ google()
}
}
apply plugin: 'com.android.application'
android {
- compileSdkVersion 26
- buildToolsVersion '26.0.2'
+ compileSdkVersion 28
defaultConfig {
applicationId 'com.android.layoutlib.test.myapplication'
- minSdkVersion 21
- targetSdkVersion 26
+ minSdkVersion 24
+ targetSdkVersion 28
versionCode 1
versionName '1.0'
}
@@ -44,5 +45,5 @@
}
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
}
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/BuildConfig.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/BuildConfig.class
deleted file mode 100644
index 1ca7e01..0000000
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/BuildConfig.class
+++ /dev/null
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/R.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/R.class
deleted file mode 100644
index a734a21..0000000
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/R.class
+++ /dev/null
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class
deleted file mode 100644
index d279a3e..0000000
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$array.class
+++ /dev/null
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class
deleted file mode 100644
index a73fcca..0000000
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$color.class
+++ /dev/null
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
deleted file mode 100644
index 93c5977..0000000
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class
+++ /dev/null
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
deleted file mode 100644
index e580ef5..0000000
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class
+++ /dev/null
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
deleted file mode 100644
index 9bdc44f..0000000
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class
+++ /dev/null
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
deleted file mode 100644
index 11e0686..0000000
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class
+++ /dev/null
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
deleted file mode 100644
index e91c774..0000000
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class
+++ /dev/null
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
similarity index 98%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
index ea2e30d..af7d768 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/BuildConfig.class
similarity index 100%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/BuildConfig.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/DrawableArrayWidget.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/DrawableArrayWidget.class
new file mode 100644
index 0000000..db12785
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/DrawableArrayWidget.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/MyActivity.class
similarity index 96%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/MyActivity.class
index be984f0..3c4d1b5 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/MyActivity.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$anim.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$anim.class
new file mode 100644
index 0000000..8cf720c
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$anim.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$array.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$array.class
new file mode 100644
index 0000000..a4660d3
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$array.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$attr.class
similarity index 71%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$attr.class
index 2b98809..4710082 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$attr.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$color.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$color.class
new file mode 100644
index 0000000..4e9a632
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$color.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$dimen.class
similarity index 63%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$dimen.class
index 6f6bd54..f354028 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$dimen.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$drawable.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$drawable.class
new file mode 100644
index 0000000..5ac9e72
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$drawable.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$font.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$font.class
similarity index 63%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$font.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$font.class
index a3e9926..6fe46c5 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$font.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$font.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$id.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$id.class
new file mode 100644
index 0000000..15e3e52
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$id.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$integer.class
similarity index 73%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$integer.class
index 4f1a852..64df0c3 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$integer.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$layout.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$layout.class
new file mode 100644
index 0000000..c0bb96d
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$layout.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$menu.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$menu.class
new file mode 100644
index 0000000..afeb357
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$menu.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$string.class
similarity index 64%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$string.class
index 331ea4a..c8d5e0b 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$string.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$style.class
similarity index 61%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$style.class
index 3b47ac9..ccfecc8 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R$style.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R.class
new file mode 100644
index 0000000..4538161
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/R.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ThemableWidget.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/ThemableWidget.class
similarity index 98%
rename from bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ThemableWidget.class
rename to bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/ThemableWidget.class
index 8d96ac1..5171dad 100644
--- a/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ThemableWidget.class
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/android/layoutlib/test/myapplication/ThemableWidget.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/activity.png b/bridge/tests/res/testApp/MyApplication/golden/activity.png
index 773c5be..1750fa8 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/activity.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/activity.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
index 4391f47..0638f32 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png b/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
index bca3347..8869159 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/array_check.png b/bridge/tests/res/testApp/MyApplication/golden/array_check.png
index 0835d51..28000f1 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/array_check.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/array_check.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/auto-scale-image.png b/bridge/tests/res/testApp/MyApplication/golden/auto-scale-image.png
new file mode 100644
index 0000000..a053e8e
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/auto-scale-image.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/canvas.png b/bridge/tests/res/testApp/MyApplication/golden/canvas.png
new file mode 100644
index 0000000..e3098ab
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/canvas.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/color_interpolation.png b/bridge/tests/res/testApp/MyApplication/golden/color_interpolation.png
new file mode 100644
index 0000000..16a457d
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/color_interpolation.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/font_test.png b/bridge/tests/res/testApp/MyApplication/golden/font_test.png
index 7e0c29a..a8a4c90 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/font_test.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/font_test.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/four_corners.png b/bridge/tests/res/testApp/MyApplication/golden/four_corners.png
index ffba2b5..2be5248 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/four_corners.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/four_corners.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png b/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png
index b73b359..f9e728a 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png b/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png
index 09fd279..4d5a38c 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/four_corners_translucent_land.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/gradient_alpha_drawable.png b/bridge/tests/res/testApp/MyApplication/golden/gradient_alpha_drawable.png
new file mode 100644
index 0000000..794b546
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/gradient_alpha_drawable.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/gradient_colors.png b/bridge/tests/res/testApp/MyApplication/golden/gradient_colors.png
new file mode 100644
index 0000000..de3abe6
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/gradient_colors.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/large_shadows_test_high_quality.png b/bridge/tests/res/testApp/MyApplication/golden/large_shadows_test_high_quality.png
new file mode 100644
index 0000000..0e2848e
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/large_shadows_test_high_quality.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/many_line_breaks.png b/bridge/tests/res/testApp/MyApplication/golden/many_line_breaks.png
new file mode 100644
index 0000000..5800b24
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/many_line_breaks.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/rtl_ltr.png b/bridge/tests/res/testApp/MyApplication/golden/rtl_ltr.png
index 62fb869..b200928 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/rtl_ltr.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/rtl_ltr.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/shadow_sizes_test_high_quality.png b/bridge/tests/res/testApp/MyApplication/golden/shadow_sizes_test_high_quality.png
new file mode 100644
index 0000000..d219a1b
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/shadow_sizes_test_high_quality.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png b/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
index 8212d04..6331256 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/shadows_test_high_quality.png b/bridge/tests/res/testApp/MyApplication/golden/shadows_test_high_quality.png
new file mode 100644
index 0000000..1639a1a
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/shadows_test_high_quality.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/shadows_test_high_quality_rounded_edge.png b/bridge/tests/res/testApp/MyApplication/golden/shadows_test_high_quality_rounded_edge.png
new file mode 100644
index 0000000..c5ba2c9
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/shadows_test_high_quality_rounded_edge.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/shadows_test_no_shadow.png b/bridge/tests/res/testApp/MyApplication/golden/shadows_test_no_shadow.png
new file mode 100644
index 0000000..c21852c
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/shadows_test_no_shadow.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png b/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png
index b6f3737..8ef9d55 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/simple_activity-old-theme.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png b/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png
index fd6e6b6..0ff09d7 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png b/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png
index ef20727..614c000 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/translate_test.png b/bridge/tests/res/testApp/MyApplication/golden/translate_test.png
new file mode 100644
index 0000000..413fd73
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/translate_test.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/typed_arrays.png b/bridge/tests/res/testApp/MyApplication/golden/typed_arrays.png
new file mode 100644
index 0000000..372eccd
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/typed_arrays.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vecitor_drawable_with_tint_in_image_view.png b/bridge/tests/res/testApp/MyApplication/golden/vecitor_drawable_with_tint_in_image_view.png
new file mode 100644
index 0000000..a67ef73
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/vecitor_drawable_with_tint_in_image_view.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
index eddb5e6..a81fa50 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png
index e9dca69..56c35f9 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png
index 8bb1677..ea3d2d9 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png
index 2a3f3dd..ad4fed8 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_in_image_view.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_in_image_view.png
new file mode 100644
index 0000000..7e6814c
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_in_image_view.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_itself.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_itself.png
new file mode 100644
index 0000000..8866a3d
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_itself.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/view_boundaries.png b/bridge/tests/res/testApp/MyApplication/golden/view_boundaries.png
index bf358fa..dbfd73a 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/view_boundaries.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/view_boundaries.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/view_stub.png b/bridge/tests/res/testApp/MyApplication/golden/view_stub.png
new file mode 100644
index 0000000..00f0246
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/view_stub.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties b/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
index 565238f..5048215 100644
--- a/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
+++ b/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Mar 17 15:13:06 PDT 2015
+#Fri Sep 28 14:21:00 BST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/DrawableArrayWidget.java b/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/DrawableArrayWidget.java
new file mode 100644
index 0000000..c87f605
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/DrawableArrayWidget.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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.layoutlib.test.myapplication;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+public class DrawableArrayWidget extends LinearLayout {
+ public DrawableArrayWidget(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DrawableArrayWidget(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public DrawableArrayWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ TypedArray drawableArray = context.getResources().obtainTypedArray(R.array.drawable_array);
+ for (int i = 0; i < drawableArray.length(); i++) {
+ addImageView(context, drawableArray.getDrawable(i));
+ }
+ drawableArray.recycle();
+ }
+
+ private void addImageView(Context context, Drawable drawable) {
+ ImageView imageView = new ImageView(context);
+ imageView.setImageDrawable(drawable);
+ addView(imageView);
+ }
+}
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CanvasTestView.java b/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CanvasTestView.java
new file mode 100644
index 0000000..8cf166d
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/CanvasTestView.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 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.layoutlib.test.myapplication.widgets;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class CanvasTestView extends ImageView {
+ public CanvasTestView(Context context) {
+ super(context);
+ }
+
+ public CanvasTestView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CanvasTestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ int savedLayer = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null);
+ super.onDraw(canvas);
+ canvas.restoreToCount(savedLayer);
+ }
+}
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/anim/color_interpolation.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/anim/color_interpolation.xml
new file mode 100644
index 0000000..4d008e6
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/anim/color_interpolation.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2018 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.
+ -->
+
+<objectAnimator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="450"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:propertyName="strokeColor"
+ android:startOffset="50"
+ android:valueFrom="#121f21"
+ android:valueTo="#dadce0"
+ android:valueType="colorType" />
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/color/multiple_alpha_items_gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/color/multiple_alpha_items_gradient.xml
new file mode 100644
index 0000000..e0f1d35
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/color/multiple_alpha_items_gradient.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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.
+ -->
+
+<gradient
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:startX="0"
+ android:endX="117"
+ android:type="linear">
+ <item
+ android:color="#33000000"
+ android:offset="0.3" />
+ <item
+ android:color="#ddff00ff"
+ android:offset="0.6" />
+ <item
+ android:color="#55ff0000"
+ android:offset="0.1" />
+ <item
+ android:color="#ee00ffff"
+ android:offset="0.8" />
+</gradient>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/color/multiple_offset_items_gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/color/multiple_offset_items_gradient.xml
new file mode 100644
index 0000000..c6ec363
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/color/multiple_offset_items_gradient.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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.
+ -->
+
+<gradient
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:startX="117"
+ android:endX="234"
+ android:type="linear">
+ <item
+ android:color="#000000"
+ android:offset="-0.3" />
+ <item
+ android:color="#ff00ff"
+ android:offset="0.6" />
+ <item
+ android:color="#ee00ffff"
+ android:offset="1.8" />
+</gradient>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/avd_color_interpolator.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/avd_color_interpolator.xml
new file mode 100644
index 0000000..81b3601
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/avd_color_interpolator.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2018 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.
+ -->
+
+<animated-vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/headset">
+
+ <target
+ android:name="outline"
+ android:animation="@anim/color_interpolation" />
+
+</animated-vector>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/clipped_even_odd.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/clipped_even_odd.xml
new file mode 100644
index 0000000..2e4a87b
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/clipped_even_odd.xml
@@ -0,0 +1,37 @@
+<!--
+ ~ Copyright (C) 2018 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="150dp"
+ android:height="150dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+
+ <clip-path
+ android:pathData="M 4.8 12 C 4.8 8.024 8.024 4.8 12 4.8 C 15.976 4.8 19.2 8.024 19.2 12
+ C 19.2 15.976 15.976 19.2 12 19.2 C 8.024 19.2 4.8 15.976 4.8 12 M 9.6 12
+ C 9.6 10.675 10.675 9.6 12 9.6 C 13.325 9.6 14.4 10.675 14.4 12
+ C 14.4 13.325 13.325 14.4 12 14.4 C 10.675 14.4 9.6 13.325 9.6 12"
+ />
+
+ <path
+ android:pathData="M 4.8 12 C 4.8 8.024 8.024 4.8 12 4.8 C 15.976 4.8 19.2 8.024 19.2 12
+ C 19.2 15.976 15.976 19.2 12 19.2 C 8.024 19.2 4.8 15.976 4.8 12"
+ android:fillColor="#FFFF0000"
+ android:fillType="evenOdd"
+ />
+
+</vector>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/corner.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/corner.xml
new file mode 100644
index 0000000..d74a750
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/corner.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ Copyright (C) 2018 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#f0f" />
+ <corners android:topLeftRadius="4dp" />
+</shape>
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/gradient.xml
new file mode 100644
index 0000000..40511c4
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/gradient.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ Copyright (C) 2018 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="234.0dp"
+ android:height="210.0dp"
+ android:viewportWidth="234.0"
+ android:viewportHeight="210.0">
+ <path
+ android:pathData="M117,183 C114.95,183 112.91,182.22 111.34,180.66 L38.48,107.53 C30.15,99.21 25,87.7 25,75 C25,49.59 45.59,29 71,29 C83.7,29 95.2,34.15 103.53,42.47 L117,55.83 C117,55.83 117,183 117,183 "
+ android:fillColor="@color/multiple_alpha_items_gradient">
+ </path>
+ <path
+ android:pathData="M117,183 C119.05,183 121.09,182.22 122.66,180.66 L195.52,107.53 C203.85,99.21 209,87.7 209,75 C209,49.59 188.41,29 163,29 C150.3,29 138.8,34.15 130.47,42.47 L117,55.83 C117,55.83 117,183 117,183 "
+ android:fillColor="@color/multiple_offset_items_gradient">
+ </path>
+</vector>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml
index 897c411..fc6692e 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/headset.xml
@@ -20,6 +20,7 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
+ android:name="outline"
android:fillColor="#FF000000"
android:pathData="m12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h4v1h-7v2h6c1.66,0 3,-1.34 3,-3V10c0,-4.97 -4.03,-9 -9,-9z"/>
</vector>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/rounded_edge_rect.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/rounded_edge_rect.xml
new file mode 100644
index 0000000..57f8062
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/rounded_edge_rect.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+<!--<?xml version="1.0" encoding="UTF-8"?>-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#B1BCBE"/>
+ <stroke android:width="3dp" android:color="#B1BCBE" />
+ <corners android:radius="20dp"/>
+ <padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
+</shape>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/vector_drawable_with_tint.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/vector_drawable_with_tint.xml
new file mode 100644
index 0000000..3bdd704
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/vector_drawable_with_tint.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+
+<vector android:height="100dp" android:tint="#6A8BD2"
+ android:viewportHeight="24.0" android:viewportWidth="24.0"
+ android:width="100dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000" android:pathData="M13,9V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v3.68l7.83,7.83L21,16v-2l-8,-5zM3,5.27l4.99,4.99L2,14v2l8,-2.5V19l-2,1.5V22l3.5,-1 3.5,1v-1.5L13,19v-3.73L18.73,21 20,19.73 4.27,4 3,5.27z"/>
+</vector>
+
+
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/vector_drawable_without_tint.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/vector_drawable_without_tint.xml
new file mode 100644
index 0000000..9efda52
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/vector_drawable_without_tint.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+
+<vector android:height="24dp" android:viewportHeight="300.0"
+ android:viewportWidth="300.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000" android:fillType="evenOdd" android:pathData="M150.5,162.9c-5.4,0 -9.9,4.4 -9.9,9.8c0,5.4 4.4,9.8 9.9,9.8c5.5,0 9.9,-4.4 9.9,-9.8C160.3,167.3 155.9,162.9 150.5,162.9zM150.5,176c-1.8,0 -3.3,-1.5 -3.3,-3.3c0,-1.8 1.5,-3.3 3.3,-3.3s3.3,1.5 3.3,3.3C153.8,174.5 152.3,176 150.5,176zM136.6,103.3c-3.4,-3.5 -8.7,-5.3 -15.8,-5.3c-18.2,0 -23.1,17.2 -23.2,17.4v0l-13.3,48.4c1.5,-1.2 3.2,-2.2 4.9,-3.1l12.9,-47c0.5,-1.8 2.3,-2.8 4,-2.3c1.8,0.5 2.8,2.3 2.3,4l-11.6,42.5c2.4,-0.6 4.9,-0.8 7.5,-0.8c12.1,0 22.8,6.6 28.5,16.4l7.9,-59.7C140.8,112.2 140.3,107.2 136.6,103.3zM104.4,173.5c-9.1,0 -16.5,7.4 -16.5,16.4c0,9.1 7.4,16.4 16.5,16.4c9.1,0 16.5,-7.4 16.5,-16.4C120.8,180.8 113.5,173.5 104.4,173.5zM180.1,91.4c2.7,0 5.1,0.3 7.3,0.8l-0.8,-3.2c-0.3,-0.6 -2.2,-4.2 -9.9,-4.2c-3.3,0 -5.7,0.9 -7.2,2.8c-0.7,0.9 -1,1.8 -1.1,2.2l0.3,3.3C172.1,92 175.9,91.4 180.1,91.4zM159.6,159.1l-5.3,-40.6c-0.3,-0.3 -1.4,-0.8 -3.8,-0.8s-3.5,0.5 -3.8,0.8l-5.3,40.6c2.6,-1.7 5.7,-2.7 9.1,-2.7C153.8,156.3 157,157.3 159.6,159.1zM132.2,93.1l0.3,-3.3c-0.1,-0.5 -0.5,-1.5 -1.3,-2.4c-1.5,-1.7 -3.8,-2.6 -7,-2.6c-7.7,0 -9.6,3.6 -9.9,4.2l-0.8,3.2c2.2,-0.5 4.7,-0.8 7.3,-0.8C125.1,91.4 128.9,92 132.2,93.1zM104.4,163.6c-14.5,0 -26.4,11.8 -26.4,26.3c0,14.5 11.8,26.3 26.4,26.3c14.5,0 26.3,-11.8 26.3,-26.3C130.7,175.4 118.9,163.6 104.4,163.6zM104.4,212.9c-12.7,0 -23.1,-10.3 -23.1,-23s10.3,-23 23.1,-23c12.7,0 23.1,10.3 23.1,23S117.1,212.9 104.4,212.9zM196.6,173.5c-9.1,0 -16.5,7.4 -16.5,16.4c0,9.1 7.4,16.4 16.5,16.4c9.1,0 16.5,-7.4 16.5,-16.4C213.1,180.8 205.7,173.5 196.6,173.5zM196.6,163.6c-14.5,0 -26.4,11.8 -26.4,26.3c0,14.5 11.8,26.3 26.4,26.3c14.5,0 26.4,-11.8 26.4,-26.3C223,175.4 211.1,163.6 196.6,163.6zM196.6,212.9c-12.7,0 -23.1,-10.3 -23.1,-23s10.3,-23 23.1,-23c12.7,0 23.1,10.3 23.1,23S209.3,212.9 196.6,212.9zM150,4.5C69.6,4.5 4.5,69.6 4.5,150S69.6,295.5 150,295.5S295.5,230.4 295.5,150S230.4,4.5 150,4.5zM196.6,222.8c-18.1,0 -32.8,-14.6 -32.9,-32.6l-0.9,-6.6c-3,3.4 -7.4,5.6 -12.3,5.6c-4.9,0 -9.3,-2.1 -12.3,-5.5l-0.9,6.5c-0.1,18 -14.9,32.6 -32.9,32.6c-18.2,0 -32.9,-14.7 -32.9,-32.8c0,-4.3 0.8,-8.3 2.3,-12.1l17.6,-64.2c0.1,-0.2 1.6,-5.7 5.9,-11.1c2,-2.5 4.9,-5.3 8.8,-7.5l1.9,-8c0.1,-0.4 2.9,-8.9 16.2,-8.9c6.8,0 10.5,2.9 12.4,5.3c2.1,2.6 2.5,5.3 2.5,5.6l0,0.7l-0.5,6.5c1.1,0.8 2.1,1.6 3,2.6c4,4.2 5.3,9.3 5.7,12.5c1,-0.2 2,-0.3 3.3,-0.3s2.3,0.1 3.3,0.3c0.4,-3.2 1.7,-8.2 5.7,-12.5c0.9,-1 1.9,-1.8 3,-2.6l-0.5,-6.5c0,-0.2 0,-0.5 0,-0.7c0,-0.3 0.4,-3 2.5,-5.6c1.9,-2.4 5.6,-5.3 12.4,-5.3c13.3,0 16.1,8.5 16.2,8.9l0,0.2l1.9,7.8c3.9,2.2 6.8,4.9 8.8,7.5c4.3,5.4 5.9,10.9 5.9,11.1l17.6,64.2c1.5,3.7 2.3,7.8 2.3,12.1C229.5,208 214.8,222.8 196.6,222.8zM196.6,157.1c2.6,0 5.1,0.3 7.5,0.8l-11.6,-42.5c-0.5,-1.8 0.6,-3.6 2.3,-4c1.8,-0.5 3.6,0.6 4,2.3l12.9,47c1.7,0.9 3.4,1.9 4.9,3.1l-13.3,-48.4c0,0 -1.3,-4.5 -4.9,-8.9c-4.6,-5.6 -10.8,-8.5 -18.3,-8.5c-7.1,0 -12.4,1.8 -15.8,5.3c-3.7,3.8 -4.1,8.8 -4.2,10.4l7.9,59.7C173.8,163.6 184.5,157.1 196.6,157.1z"/>
+</vector>
+
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/vector_gradient_alpha.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/vector_gradient_alpha.xml
new file mode 100644
index 0000000..a4394ad
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/vector_gradient_alpha.xml
@@ -0,0 +1,41 @@
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:pathData="M0,0h108v108h-108z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M70.87,73.52c4.76,-4.44 7.79,-11.05 7.79,-19.52c0,-1.54 -0.14,-3.3 -0.49,-4.88H54v10.05h13.89c-0.7,3.51 -2.6,6.18 -5.32,7.91v1.29l6.78,5.14L70.87,73.52L70.87,73.52z"
+ android:fillColor="#4285F4"/>
+ <path
+ android:pathData="M39.37,58.81c2.01,6.14 7.77,10.56 14.59,10.56c3.33,0 6.27,-0.79 8.61,-2.28l8.3,6.44C66.34,77.75 60.29,80 53.96,80c-10.27,0 -19.09,-5.89 -23.31,-14.5l0.29,-1.76l7.04,-4.93H39.37z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M39.38,49.16c-0.5,1.52 -0.78,3.14 -0.78,4.84c0,1.69 0.27,3.32 0.78,4.84l-8.72,6.68C28.95,62.05 28,58.14 28,54s0.96,-8.05 2.66,-11.52l1.73,-0.37l6.6,5.16L39.38,49.16z"
+ android:fillColor="#FBBC05"/>
+ <path
+ android:pathData="M53.96,38.64c3.66,0 6.97,1.3 9.56,3.43l7.56,-7.57C66.47,30.48 60.56,28 53.95,28c-10.27,0 -19.09,5.89 -23.3,14.49l8.73,6.68C41.39,43.04 47.14,38.64 53.96,38.64z"
+ android:fillColor="#EA4335"/>
+ <path
+ android:pathData="M0,0h108v108h-108z"
+ android:fillAlpha="0.3"
+ android:fillColor="@color/gradient"/>
+</vector>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/large_view_shadows_test.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/large_view_shadows_test.xml
new file mode 100644
index 0000000..2209e50
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/large_view_shadows_test.xml
@@ -0,0 +1,46 @@
+
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <Button
+ android:layout_width="100dp"
+ android:layout_height="200dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginLeft="28dp"
+ android:layout_alignParentLeft="true"
+ android:elevation="5.5dp"
+ android:stateListAnimator="@null"
+ android:text="5.5"/>
+ <Button
+ android:layout_width="100dp"
+ android:layout_height="200dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginRight="61dp"
+ android:layout_alignParentRight="true"
+ android:elevation="36dp"
+ android:stateListAnimator="@null"
+ android:text="36"/>
+ </RelativeLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadow_sizes_test.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadow_sizes_test.xml
new file mode 100644
index 0000000..5c53ca0
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadow_sizes_test.xml
@@ -0,0 +1,165 @@
+<!--
+ ~ Copyright (C) 2019 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <Button
+ android:layout_width="100dp"
+ android:layout_height="200dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginLeft="28dp"
+ android:layout_alignParentLeft="true"
+ android:elevation="5.5dp"
+ android:stateListAnimator="@null"
+ android:text="5.5"/>
+ <Button
+ android:layout_width="100dp"
+ android:layout_height="200dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginRight="61dp"
+ android:layout_alignParentRight="true"
+ android:elevation="36dp"
+ android:stateListAnimator="@null"
+ android:text="36"/>
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <Button
+ android:layout_width="46dp"
+ android:layout_height="22dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginLeft="103dp"
+ android:layout_alignParentLeft="true"
+ android:elevation="15dp"
+ android:stateListAnimator="@null"
+ android:text="15"/>
+ <Button
+ android:id="@+id/button14"
+ android:layout_width="23dp"
+ android:layout_height="21dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginLeft="54dp"
+ android:layout_alignParentLeft="true"
+ android:elevation="15dp"
+ android:stateListAnimator="@null"
+ android:text="15"/>
+ <Button
+ android:id="@+id/button12"
+ android:layout_width="76dp"
+ android:layout_height="20dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginLeft="176dp"
+ android:layout_alignParentLeft="true"
+ android:elevation="15dp"
+ android:stateListAnimator="@null"
+ android:text="15"/>
+ <Button
+ android:id="@+id/button11"
+ android:layout_width="15dp"
+ android:layout_height="23dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginLeft="28dp"
+ android:layout_alignParentLeft="true"
+ android:elevation="15dp"
+ android:stateListAnimator="@null"
+ android:text="15"/>
+ <Button
+ android:layout_width="39dp"
+ android:layout_height="21dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginStart="278dp"
+ android:layout_marginLeft="278dp"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:elevation="5dp"
+ android:stateListAnimator="@null"
+ android:text="5"/>
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <Button
+ android:layout_width="20dp"
+ android:layout_height="36dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginLeft="136dp"
+ android:layout_alignParentLeft="true"
+ android:elevation="5.5dp"
+ android:stateListAnimator="@null"
+ android:text="5.5"/>
+ <Button
+ android:id="@+id/button17"
+ android:layout_width="22dp"
+ android:layout_height="85dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginLeft="77dp"
+ android:layout_alignParentLeft="true"
+ android:elevation="5.5dp"
+ android:stateListAnimator="@null"
+ android:text="5.5"/>
+ <Button
+ android:id="@+id/button16"
+ android:layout_width="23dp"
+ android:layout_height="131dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginLeft="28dp"
+ android:layout_alignParentLeft="true"
+ android:elevation="5.5dp"
+ android:stateListAnimator="@null"
+ android:text="5.5"/>
+ <Button
+ android:layout_width="33dp"
+ android:layout_height="115dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginRight="155dp"
+ android:layout_alignParentRight="true"
+ android:elevation="36dp"
+ android:stateListAnimator="@null"
+ android:text="36"/>
+ <Button
+ android:id="@+id/button20"
+ android:layout_width="29dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="28dp"
+ android:layout_marginRight="92dp"
+ android:layout_alignParentRight="true"
+ android:elevation="36dp"
+ android:stateListAnimator="@null"
+ android:text="36"/>
+ <Button
+ android:id="@+id/button18"
+ android:layout_width="14dp"
+ android:layout_height="23dp"
+ android:layout_marginTop="28dp"
+ android:layout_marginRight="50dp"
+ android:layout_alignParentRight="true"
+ android:elevation="36dp"
+ android:stateListAnimator="@null"
+ android:text="36"/>
+ </RelativeLayout>
+</LinearLayout>
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_rounded_edge_test.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_rounded_edge_test.xml
new file mode 100644
index 0000000..943e103
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_rounded_edge_test.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+
+ <Button
+ android:layout_marginLeft="40dp"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:elevation="48dp"
+ android:stateListAnimator="@null"
+ android:background="@drawable/rounded_edge_rect"/>
+
+ <Button
+ android:layout_marginRight="40dp"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:elevation="48dp"
+ android:background="@drawable/rounded_edge_rect"
+ android:stateListAnimator="@null"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+
+ <Button
+ android:layout_marginLeft="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:elevation="0dp"
+ android:background="@drawable/rounded_edge_rect"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:layout_marginRight="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:elevation="100dp"
+ android:background="@drawable/rounded_edge_rect"
+ android:stateListAnimator="@null"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+
+ <Button
+ android:layout_marginLeft="40dp"
+ android:layout_width="40dp"
+ android:layout_height="48dp"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:elevation="12dp"
+ android:background="@drawable/rounded_edge_rect"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:layout_marginRight="40dp"
+ android:layout_width="40dp"
+ android:layout_height="48dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:elevation="36dp"
+ android:background="@drawable/rounded_edge_rect"
+ android:stateListAnimator="@null"/>
+
+ </RelativeLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <Button
+ android:layout_marginLeft="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentLeft="true"
+ android:layout_centerVertical="true"
+ android:elevation="12dp"
+ android:stateListAnimator="@null"
+ android:background="@drawable/rounded_edge_rect"
+ android:alpha="0.1"/>
+
+ <Button
+ android:layout_marginRight="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:elevation="12dp"
+ android:stateListAnimator="@null"
+ android:background="@drawable/rounded_edge_rect"
+ android:alpha="0.5"/>
+ </RelativeLayout>
+</LinearLayout>
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
index c083a6f..498a63f 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml
@@ -32,6 +32,7 @@
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:elevation="48dp"
+ android:text="48"
android:stateListAnimator="@null"/>
<Button
@@ -41,6 +42,7 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:elevation="48dp"
+ android:text="48"
android:stateListAnimator="@null"/>
</RelativeLayout>
@@ -57,6 +59,7 @@
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:elevation="0dp"
+ android:text="0"
android:stateListAnimator="@null"/>
<Button
@@ -66,6 +69,7 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:elevation="100dp"
+ android:text="100"
android:stateListAnimator="@null"/>
</RelativeLayout>
@@ -82,6 +86,7 @@
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:elevation="12dp"
+ android:text="12"
android:stateListAnimator="@null"/>
<Button
@@ -91,6 +96,7 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:elevation="36dp"
+ android:text="36"
android:stateListAnimator="@null"/>
</RelativeLayout>
@@ -107,6 +113,7 @@
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:elevation="12dp"
+ android:text="12"
android:stateListAnimator="@null"
android:alpha="0.1"/>
@@ -117,6 +124,7 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:elevation="12dp"
+ android:text="12"
android:stateListAnimator="@null"
android:alpha="0.5"/>
@@ -137,7 +145,22 @@
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:elevation="12dp"
+ android:text="12"
+ android:alpha="5"
android:stateListAnimator="@null" />
+ <Button
+ android:translationX="5dp"
+ android:translationY="20dp"
+ android:layout_marginRight="40dp"
+ android:layout_width="48dp"
+ android:layout_height="40dp"
+ android:layout_alignParentRight="true"
+ android:layout_centerVertical="true"
+ android:elevation="12dp"
+ android:text="12"
+ android:stateListAnimator="@null"
+ android:background="@drawable/corner" />
+
</RelativeLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/translate_test.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/translate_test.xml
new file mode 100644
index 0000000..f42da40
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/translate_test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/button2"
+ android:layout_width="100dp"
+ android:layout_height="50dp"
+ android:translationX="100px"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/typed_array.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/typed_array.xml
new file mode 100644
index 0000000..b0ca4c3
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/typed_array.xml
@@ -0,0 +1,34 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text1"
+ android:text="@string/hello_world"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:autoSizeTextType="uniform"
+ android:autoSizePresetSizes="@array/autosize_text_sizes" />
+
+ <TextView
+ android:id="@+id/text2"
+ android:text="@string/hello_world"
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:autoSizeTextType="uniform"
+ android:autoSizePresetSizes="@array/autosize_text_sizes" />
+
+ <TextView
+ android:id="@+id/text3"
+ android:text="@string/hello_world"
+ android:layout_width="match_parent"
+ android:layout_height="20dp"
+ android:autoSizeTextType="uniform"
+ android:autoSizePresetSizes="@array/autosize_text_sizes" />
+
+ <com.android.layoutlib.test.myapplication.DrawableArrayWidget
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
index 1059d2e..3cf19da 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/values/arrays.xml
@@ -19,8 +19,18 @@
<item>@android:string/unknownName</item> <!-- value = Unknown -->
<item>\?EC</item>
</string-array>
-
+ <array name="autosize_text_sizes">
+ <item>10sp</item>
+ <item>12sp</item>
+ <item>20sp</item>
+ <item>40sp</item>
+ <item>100sp</item>
+ </array>
+ <array name="drawable_array">
+ <item>@drawable/gradient</item>
+ <item>@drawable/headset</item>
+ </array>
<!-- resources that the above array can refer to -->
<integer name="ten">10</integer>
<integer name="twelve">12</integer>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/bridge/tests/run_tests.sh b/bridge/tests/run_tests.sh
new file mode 100755
index 0000000..4d9cde2
--- /dev/null
+++ b/bridge/tests/run_tests.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+SCRIPT_DIR="$(dirname $0)"
+DIST_DIR="$1"
+
+STUDIO_JDK=${SCRIPT_DIR}"/../../../../prebuilts/studio/jdk/linux"
+MISC_COMMON=${SCRIPT_DIR}"/../../../../prebuilts/misc/common"
+M2_REPO=${SCRIPT_DIR}"/../../../../prebuilts/tools/common/m2/repository"
+JAVA_LIBRARIES=${SCRIPT_DIR}"/../../../../out/host/common/obj/JAVA_LIBRARIES"
+
+${STUDIO_JDK}/bin/java -ea \
+ -Dtest_res.dir=${SCRIPT_DIR}/res \
+ -Dtest_failure.dir=${DIST_DIR}/layoutlib_failures \
+ -cp ${M2_REPO}/junit/junit/4.12/junit-4.12.jar:${M2_REPO}/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar:${MISC_COMMON}/tools-common/tools-common-prebuilt.jar:${MISC_COMMON}/sdk-common/sdk-common.jar:${MISC_COMMON}/layoutlib_api/layoutlib_api-prebuilt.jar:${MISC_COMMON}/kxml2/kxml2-2.3.0.jar:${M2_REPO}/com/google/guava/guava/22.0/guava-22.0.jar:${JAVA_LIBRARIES}/layoutlib-tests_intermediates/javalib.jar:${JAVA_LIBRARIES}/layoutlib_intermediates/javalib.jar:${JAVA_LIBRARIES}/mockito-host_intermediates/javalib.jar:${JAVA_LIBRARIES}/objenesis-host_intermediates/javalib.jar \
+ org.junit.runner.JUnitCore \
+ com.android.layoutlib.bridge.intensive.Main
+
diff --git a/bridge/tests/src/android/graphics/Color_DelegateTest.java b/bridge/tests/src/android/graphics/Color_DelegateTest.java
new file mode 100644
index 0000000..1b9c13e
--- /dev/null
+++ b/bridge/tests/src/android/graphics/Color_DelegateTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 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.graphics;
+
+import junit.framework.TestCase;
+
+public class Color_DelegateTest extends TestCase {
+
+ public void testRGBToHSV() {
+ float[] hsv = new float[3];
+ Color_Delegate.nativeRGBToHSV(14, 203, 49, hsv);
+ assertTrue(131.1111 - hsv[0] < 0.001);
+ assertTrue(0.9310 - hsv[1] < 0.001);
+ assertTrue(0.7961 - hsv[2] < 0.001);
+ }
+
+ public void testHSVToColor() {
+ assertEquals(2077003642,
+ Color_Delegate.nativeHSVToColor(123, new float[]{15.0f, 0.4f, 0.8f}));
+ assertEquals(603979776,
+ Color_Delegate.nativeHSVToColor(36, new float[]{15.0f, 25.0f, -17.0f}));
+ }
+}
diff --git a/bridge/tests/src/android/graphics/Matrix_DelegateTest.java b/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
index aa51773..0886132 100644
--- a/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
+++ b/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
@@ -52,13 +52,15 @@
Matrix m1 = new Matrix();
Matrix inverse = new Matrix();
m1.setValues(new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9});
- m1.invert(inverse);
+ assertFalse(m1.invert(inverse));
+ m1.setValues(new float[]{3, 5, 6, 2, 5, 7, 4, 8, 2});
+ m1.invert(inverse);
float[] values = new float[9];
inverse.getValues(values);
assertTrue(Arrays.equals(values,
- new float[]{-1.6666666f, 0.6666667f, 1.0f, 1.3333334f, -0.33333334f, -2.0f, 0.0f,
- 0.0f, 1.0f}));
+ new float[]{1.0952381f, -0.9047619f, -0.11904762f, -0.5714286f, 0.42857143f,
+ 0.21428572f, 0.0952381f, 0.0952381f, -0.11904762f}));
}
}
diff --git a/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java b/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java
index 2fcec8e..f995d1a 100644
--- a/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java
+++ b/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java
@@ -16,7 +16,10 @@
package android.util;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceNamespace.Resolver;
import com.android.layoutlib.bridge.BridgeConstants;
import com.android.layoutlib.bridge.android.BridgeContext;
@@ -31,6 +34,7 @@
import static org.mockito.Mockito.when;
public class BridgeXmlPullAttributesTest {
+
@Test
public void testGetAttributeIntValueForEnums() {
RenderResources renderResources = new RenderResources();
@@ -41,18 +45,22 @@
when(parser.getAttributeName(0)).thenReturn("layout_width");
when(parser.getAttributeNamespace(0)).thenReturn(BridgeConstants.NS_RESOURCES);
// Return every value twice since there is one test using name and other using index
- when(parser.getAttributeValue("http://custom", "my_custom_attr"))
+ when(parser.getAttributeValue(BridgeConstants.NS_APP_RES_AUTO, "my_custom_attr"))
.thenReturn("a", "a", "b", "b", "invalid", "invalid");
when(parser.getAttributeName(1)).thenReturn("my_custom_attr");
- when(parser.getAttributeNamespace(1)).thenReturn("http://custom");
+ when(parser.getAttributeNamespace(1)).thenReturn(BridgeConstants.NS_APP_RES_AUTO);
BridgeContext context = mock(BridgeContext.class);
when(context.getRenderResources()).thenReturn(renderResources);
+ LayoutlibCallback callback = mock(LayoutlibCallback.class);
+ when(callback.getImplicitNamespaces()).thenReturn(Resolver.EMPTY_RESOLVER);
+ when(context.getLayoutlibCallback()).thenReturn(callback);
+
BridgeXmlPullAttributes attributes = new BridgeXmlPullAttributes(
parser,
context,
- false,
+ ResourceNamespace.RES_AUTO,
attrName -> {
if ("layout_width".equals(attrName)) {
return ImmutableMap.of(
@@ -60,7 +68,7 @@
}
return ImmutableMap.of();
},
- attrName -> {
+ (ns, attrName) -> {
if ("my_custom_attr".equals(attrName)) {
return ImmutableMap.of(
"a", 1,
@@ -80,16 +88,16 @@
assertEquals(500, attributes.getAttributeIntValue(2, 500));
// Test project defined enum attribute
- assertEquals(1, attributes.getAttributeIntValue("http://custom",
+ assertEquals(1, attributes.getAttributeIntValue(BridgeConstants.NS_APP_RES_AUTO,
"my_custom_attr", 500));
assertEquals(1, attributes.getAttributeIntValue(1, 500));
- assertEquals(2, attributes.getAttributeIntValue("http://custom",
+ assertEquals(2, attributes.getAttributeIntValue(BridgeConstants.NS_APP_RES_AUTO,
"my_custom_attr", 500));
assertEquals(2, attributes.getAttributeIntValue(1, 500));
// Test an invalid enum
boolean exception = false;
try {
- attributes.getAttributeIntValue("http://custom", "my_custom_attr", 500);
+ attributes.getAttributeIntValue(BridgeConstants.NS_APP_RES_AUTO, "my_custom_attr", 500);
} catch(NumberFormatException e) {
exception = true;
}
@@ -103,8 +111,8 @@
assertTrue(exception);
// Test non existing project attribute
- assertEquals(500, attributes.getAttributeIntValue("http://custom",
+ assertEquals(500, attributes.getAttributeIntValue(BridgeConstants.NS_APP_RES_AUTO,
"my_other_attr", 500));
}
-}
\ No newline at end of file
+}
diff --git a/bridge/tests/src/android/util/imagepool/ImagePoolHelperTest.java b/bridge/tests/src/android/util/imagepool/ImagePoolHelperTest.java
new file mode 100644
index 0000000..25aa466
--- /dev/null
+++ b/bridge/tests/src/android/util/imagepool/ImagePoolHelperTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import org.junit.Test;
+
+import android.util.imagepool.Bucket.BucketCreationMetaData;
+import android.util.imagepool.ImagePool.Image.Orientation;
+
+import java.awt.image.BufferedImage;
+import java.lang.ref.SoftReference;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class ImagePoolHelperTest {
+
+ @Test
+ public void testGetBufferedImage() {
+ int width = 10;
+ int height = 10;
+ int numberOfCopiesInBucket = 10;
+ int maxCacheSize = width * height * 4 * 5; // can fit 5 width | height buffer
+ Bucket bucket = new Bucket();
+ BucketCreationMetaData metaData = new BucketCreationMetaData(
+ width, height, BufferedImage.TYPE_INT_ARGB, numberOfCopiesInBucket, Orientation
+ .NONE, maxCacheSize);
+ ImagePoolStats stats = new ImagePoolStatsProdImpl();
+
+ assertNotNull(ImagePoolHelper.getBufferedImage(bucket, metaData, stats));
+ }
+
+ @Test
+ public void testGetBufferedImageRecurse() {
+ int width = 10;
+ int height = 10;
+ int numberOfCopiesToRequestInBucket = 1;
+ int numberOfCopiesInBucket = 10;
+ int maxCacheSize = width * height * 4 * numberOfCopiesToRequestInBucket;
+
+ Bucket bucket = new Bucket();
+ for (int i = 0; i < numberOfCopiesInBucket; i++) {
+ bucket.mBufferedImageRef.add(new SoftReference<>(null));
+ }
+ BucketCreationMetaData metaData = new BucketCreationMetaData(
+ width, height, BufferedImage.TYPE_INT_ARGB, numberOfCopiesToRequestInBucket, Orientation
+ .NONE, maxCacheSize);
+ ImagePoolStats stats = new ImagePoolStatsProdImpl();
+
+ assertNotNull(ImagePoolHelper.getBufferedImage(bucket, metaData, stats));
+ }
+
+ @Test
+ public void testRecurseThenHitCacheLimit() {
+ int width = 10;
+ int height = 10;
+ int numberOfCopiesToRequestInBucket = 1;
+ int numberOfCopiesInBucket = 10;
+ int maxCacheSize = width * height * 4 * numberOfCopiesToRequestInBucket / 2;
+
+ Bucket bucket = new Bucket();
+ for (int i = 0; i < numberOfCopiesInBucket; i++) {
+ bucket.mBufferedImageRef.add(new SoftReference<>(null));
+ }
+ BucketCreationMetaData metaData = new BucketCreationMetaData(
+ width, height, BufferedImage.TYPE_INT_ARGB, numberOfCopiesToRequestInBucket, Orientation
+ .NONE, maxCacheSize);
+ ImagePoolStats stats = new ImagePoolStatsProdImpl();
+
+ assertNull(ImagePoolHelper.getBufferedImage(bucket, metaData, stats));
+ }
+
+ @Test
+ public void testBucketHasImageToReturn() {
+ int width = 10;
+ int height = 10;
+ int numberOfCopiesToRequestInBucket = 1;
+ int numberOfCopiesInBucket = 10;
+ int maxCacheSize = width * height * 4 * numberOfCopiesToRequestInBucket / 2;
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
+
+ Bucket bucket = new Bucket();
+ for (int i = 0; i < numberOfCopiesInBucket; i++) {
+ bucket.mBufferedImageRef.add(new SoftReference<>(null));
+ }
+ bucket.mBufferedImageRef.add(new SoftReference<>(image));
+ BucketCreationMetaData metaData = new BucketCreationMetaData(
+ width, height, BufferedImage.TYPE_INT_ARGB, numberOfCopiesToRequestInBucket, Orientation
+ .NONE, maxCacheSize);
+ ImagePoolStats stats = new ImagePoolStatsProdImpl();
+
+ assertNotNull(ImagePoolHelper.getBufferedImage(bucket, metaData, stats));
+ }
+}
\ No newline at end of file
diff --git a/bridge/tests/src/android/util/imagepool/ImagePoolImplTest.java b/bridge/tests/src/android/util/imagepool/ImagePoolImplTest.java
new file mode 100644
index 0000000..e8f0f17
--- /dev/null
+++ b/bridge/tests/src/android/util/imagepool/ImagePoolImplTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2018 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.util.imagepool;
+
+import org.junit.Test;
+
+import android.util.imagepool.ImagePool.Image;
+import android.util.imagepool.ImagePool.ImagePoolPolicy;
+
+import java.awt.image.BufferedImage;
+import java.lang.ref.SoftReference;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ImagePoolImplTest {
+
+ private static final long TIMEOUT_SEC = 3;
+
+ @Test
+ public void testImagePoolInstance() {
+ ImagePool pool1 = ImagePoolProvider.get();
+ ImagePool pool2 = ImagePoolProvider.get();
+ assertNotNull(pool1);
+ assertNotNull(pool2);
+ assertEquals(pool1, pool2);
+ }
+
+
+ @Test
+ public void testImageDispose() throws InterruptedException {
+ int width = 700;
+ int height = 800;
+ int type = BufferedImage.TYPE_INT_ARGB;
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ ImagePoolImpl pool = getSimpleSingleBucketPool(width, height);
+ Image img1 = pool.acquire(width, height, type,
+ bufferedImage -> countDownLatch.countDown());
+ BufferedImage img = getImg(img1);
+ assertNotNull(img);
+ img1 = null;
+
+ // ensure dispose actually loses buffered image link so it can be gc'd
+ gc();
+ assertTrue(countDownLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS));
+ }
+ @Test
+ public void testImageDisposeFromFunction() throws InterruptedException {
+ int width = 700;
+ int height = 800;
+ int type = BufferedImage.TYPE_INT_ARGB;
+ CountDownLatch cd = new CountDownLatch(1);
+ ImagePoolImpl pool = getSimpleSingleBucketPool(width, height);
+
+ BufferedImage img = createImageAndReturnBufferedImage(pool, width, height, type, cd);
+ assertNotNull(img);
+
+ // ensure dispose actually loses buffered image link so it can be gc'd
+ gc();
+ assertTrue(cd.await(TIMEOUT_SEC, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testImageDisposedAndRecycled() throws InterruptedException {
+ int width = 700;
+ int height = 800;
+ int bucketWidth = 800;
+ int bucketHeight = 800;
+ int variant = 1;
+ int type = BufferedImage.TYPE_INT_ARGB;
+ ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy(
+ new int[]{bucketWidth, bucketHeight},
+ new int[]{1, 1},
+ bucketHeight * bucketWidth * 4 * 3));
+
+ // acquire first image and draw something.
+ BufferedImage bufferedImageForImg1;
+ final CountDownLatch countDownLatch1 = new CountDownLatch(1);
+ {
+ Image img1 = pool.acquire(width, height, type,
+ bufferedImage -> countDownLatch1.countDown());
+ bufferedImageForImg1 = getImg(img1);
+ img1 = null; // this is still needed.
+ }
+ // dispose
+ gc();
+ assertTrue(countDownLatch1.await(TIMEOUT_SEC, TimeUnit.SECONDS));
+
+ // ensure dispose actually loses buffered image link so it can be gc'd
+ assertNotNull(bufferedImageForImg1);
+ assertEquals(bufferedImageForImg1.getWidth(), bucketWidth);
+ assertEquals(bufferedImageForImg1.getHeight(), bucketHeight);
+
+ // get 2nd image with the same spec
+ final CountDownLatch countDownLatch2 = new CountDownLatch(1);
+ BufferedImage bufferedImageForImg2;
+ {
+ Image img2 = pool.acquire(width - variant, height - variant, type,
+ bufferedImage -> countDownLatch2.countDown());
+ bufferedImageForImg2 = getImg(img2);
+ assertEquals(bufferedImageForImg1, bufferedImageForImg2);
+ img2 = null;
+ }
+ // dispose
+ gc();
+ assertTrue(countDownLatch2.await(TIMEOUT_SEC, TimeUnit.SECONDS));
+
+ // ensure that we're recycling previously created buffered image.
+ assertNotNull(bufferedImageForImg1);
+ assertNotNull(bufferedImageForImg2);
+ }
+
+
+ @Test
+ public void testBufferedImageReleased() throws InterruptedException {
+ int width = 700;
+ int height = 800;
+ int bucketWidth = 800;
+ int bucketHeight = 800;
+ ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy(
+ new int[]{bucketWidth, bucketHeight},
+ new int[]{1, 1},
+ bucketWidth * bucketWidth * 4 * 2));
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ Image image1 = pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB,
+ bufferedImage -> countDownLatch.countDown());
+ BufferedImage internalPtr = getImg(image1);
+ // dispose
+ image1 = null;
+ gc();
+ assertTrue(countDownLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS));
+
+ // Simulate BufferedBitmaps being gc'd. Bucket filled with null soft refs.
+ for (Bucket bucket : ((ImagePoolImpl) pool).mPool.values()) {
+ bucket.mBufferedImageRef.clear();
+ bucket.mBufferedImageRef.add(new SoftReference<>(null));
+ bucket.mBufferedImageRef.add(new SoftReference<>(null));
+ }
+
+ assertNotEquals(internalPtr,
+ getImg(pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB)));
+ }
+
+ @Test
+ public void testPoolWidthHeightNotBigEnough() {
+ int width = 1000;
+ int height = 1000;
+ int bucketWidth = 999;
+ int bucketHeight = 800;
+ ImagePool pool = new ImagePoolImpl(
+ new ImagePoolPolicy(new int[]{bucketWidth, bucketHeight}, new int[]{1, 1},
+ bucketWidth * bucketWidth * 4 * 2));
+ ImageImpl image = (ImageImpl) pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB);
+
+ assertEquals(getTooBigForPoolCount(pool), 1);
+ }
+
+ @Test
+ public void testSizeNotBigEnough() {
+ int width = 500;
+ int height = 500;
+ int bucketWidth = 800;
+ int bucketHeight = 800;
+ ImagePoolImpl pool = new ImagePoolImpl(
+ new ImagePoolPolicy(new int[]{bucketWidth, bucketHeight}, new int[]{1, 1},
+ bucketWidth * bucketWidth)); // cache not big enough.
+ ImageImpl image = (ImageImpl) pool.acquire(width, height, BufferedImage.TYPE_INT_ARGB);
+
+ assertEquals(getTooBigForPoolCount(pool), 1);
+ assertEquals(image.getWidth(), width);
+ assertEquals(image.getHeight(), height);
+ }
+
+ @Test
+ public void testImageMultipleCopies() throws InterruptedException {
+ int width = 700;
+ int height = 800;
+ int bucketWidth = 800;
+ int bucketHeight = 800;
+ int type = BufferedImage.TYPE_INT_ARGB;
+ ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy(
+ new int[]{bucketWidth, bucketHeight},
+ new int[]{2, 2},
+ bucketHeight * bucketWidth * 4 * 4));
+
+ // create 1, and 2 different instances.
+ final CountDownLatch cd1 = new CountDownLatch(1);
+ Image img1 = pool.acquire(width, height, type, bufferedImage -> cd1.countDown());
+ BufferedImage bufferedImg1 = getImg(img1);
+
+ Image img2 = pool.acquire(width, height, type);
+ BufferedImage bufferedImg2 = getImg(img2);
+
+ assertNotEquals(bufferedImg1, bufferedImg2);
+
+ // disposing img1. Since # of copies == 2, this buffer should be recycled.
+ img1 = null;
+ gc();
+ cd1.await(TIMEOUT_SEC, TimeUnit.SECONDS);
+
+ // Ensure bufferedImg1 is recycled in newly acquired img3.
+ Image img3 = pool.acquire(width, height, type);
+ BufferedImage bufferedImage3 = getImg(img3);
+ assertNotEquals(bufferedImg2, bufferedImage3);
+ assertEquals(bufferedImg1, bufferedImage3);
+ }
+
+ @Test
+ public void testPoolDispose() throws InterruptedException {
+ int width = 700;
+ int height = 800;
+ int bucketWidth = 800;
+ int bucketHeight = 800;
+ int type = BufferedImage.TYPE_INT_ARGB;
+
+ // Pool barely enough for 1 image.
+ ImagePoolImpl pool = new ImagePoolImpl(new ImagePoolPolicy(
+ new int[]{bucketWidth, bucketHeight},
+ new int[]{2, 2},
+ bucketHeight * bucketWidth * 4));
+
+ // create 1, and 2 different instances.
+ final CountDownLatch cd1 = new CountDownLatch(1);
+ Image img1 = pool.acquire(width, height, type, bufferedImage -> cd1.countDown());
+ BufferedImage bufferedImg1 = getImg(img1);
+ assertEquals(getAllocatedTotalBytes(pool), bucketWidth * bucketHeight * 4);
+ assertEquals(getTooBigForPoolCount(pool), 0);
+
+ // Release the img1.
+ img1 = null;
+ gc();
+ cd1.await(TIMEOUT_SEC, TimeUnit.SECONDS);
+
+ // Dispose pool.
+ pool.dispose();
+ assertEquals(getAllocatedTotalBytes(pool), 0);
+
+ // Request the same sized image as previous.
+ // If the pool was not disposed, this would return the image with bufferedImg1.
+ Image img2 = pool.acquire(width, height, type);
+ BufferedImage bufferedImg2 = getImg(img2);
+ assertEquals(getAllocatedTotalBytes(pool), bucketWidth * bucketHeight * 4);
+ assertEquals(getTooBigForPoolCount(pool), 0);
+
+ // Pool disposed before. No buffered image should be recycled.
+ assertNotEquals(img1, img2);
+ assertNotEquals(bufferedImg1, bufferedImg2);
+ }
+
+ private static BufferedImage createImageAndReturnBufferedImage(ImagePoolImpl pool, int width,
+ int height
+ , int type, CountDownLatch cd) {
+ Image img1 = pool.acquire(width, height, type, bufferedImage -> cd.countDown());
+ return getImg(img1);
+ // At this point img1 should have no reference, causing finalizable to trigger
+ }
+
+ private static ImagePoolImpl getSimpleSingleBucketPool(int width, int height) {
+
+ int bucketWidth = Math.max(width, height);
+ int bucketHeight = Math.max(width, height);
+ return new ImagePoolImpl(new ImagePoolPolicy(
+ new int[]{bucketWidth, bucketHeight},
+ new int[]{1, 1},
+ bucketHeight * bucketWidth * 4 * 3));
+ }
+
+ // Try to force a gc round
+ private static void gc() {
+ System.gc();
+ System.gc();
+ System.gc();
+ }
+
+ private static int getTooBigForPoolCount(ImagePool pool) {
+ return ((ImagePoolStatsProdImpl) ((ImagePoolImpl) pool).mImagePoolStats).mTooBigForPoolCount;
+ }
+
+ private static long getAllocatedTotalBytes(ImagePool pool) {
+ return ((ImagePoolStatsProdImpl) ((ImagePoolImpl) pool).mImagePoolStats).mAllocateTotalBytes;
+ }
+
+ private static BufferedImage getImg(Image image) {
+ return ((ImageImpl) image).mImg;
+ }
+}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/BridgeRenderSessionTest.java b/bridge/tests/src/com/android/layoutlib/bridge/BridgeRenderSessionTest.java
index 63b9b43..723311d 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/BridgeRenderSessionTest.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/BridgeRenderSessionTest.java
@@ -16,7 +16,6 @@
package com.android.layoutlib.bridge;
-import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.Result.Status;
import org.junit.Test;
@@ -32,6 +31,7 @@
assertNotNull(renderSession.getImage());
assertNotNull(renderSession.getRootViews());
assertNotNull(renderSession.getSystemRootViews());
- assertNotNull(renderSession.getDefaultProperties());
+ assertNotNull(renderSession.getDefaultNamespacedProperties());
+ assertNotNull(renderSession.getDefaultNamespacedStyles());
}
}
\ No newline at end of file
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
index 46e3778..8c69e3e 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java
@@ -48,7 +48,8 @@
final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES;
mErrors.clear();
for (String clazz : classes) {
- loadAndCompareClasses(clazz, clazz + "_Delegate");
+ String targetClassName = clazz.replace('$', '_') + "_Delegate";
+ loadAndCompareClasses(clazz, targetClassName);
}
assertTrue(getErrors(), mErrors.isEmpty());
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
index 77c997b..1a66c94 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java
@@ -16,6 +16,8 @@
package com.android.layoutlib.bridge.android;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.XmlParserFactory;
import com.android.layoutlib.bridge.impl.ParserFactory;
import org.junit.AfterClass;
@@ -27,6 +29,12 @@
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
import static org.junit.Assert.assertEquals;
@@ -39,12 +47,11 @@
@Test
public void testXmlBlockParser() throws Exception {
-
XmlPullParser parser = ParserFactory.create(
getClass().getResourceAsStream("/com/android/layoutlib/testdata/layout1.xml"),
"layout1.xml");
- parser = new BridgeXmlBlockParser(parser, null, false /* platformResourceFlag */);
+ parser = new BridgeXmlBlockParser(parser, null, ResourceNamespace.RES_AUTO);
assertEquals(XmlPullParser.START_DOCUMENT, parser.next());
@@ -125,12 +132,29 @@
ParserFactory.setParserFactory(null);
}
- private static class ParserFactoryImpl
- extends com.android.ide.common.rendering.api.ParserFactory {
-
- @NonNull
+ private static class ParserFactoryImpl implements XmlParserFactory {
@Override
- public XmlPullParser createParser(String displayName) throws XmlPullParserException {
+ @Nullable
+ public XmlPullParser createXmlParserForPsiFile(@NonNull String fileName) {
+ return createXmlParserForFile(fileName);
+ }
+
+ @Override
+ @Nullable
+ public XmlPullParser createXmlParserForFile(@NonNull String fileName) {
+ try {
+ InputStream stream = new BufferedInputStream(new FileInputStream(fileName));
+ XmlPullParser parser = new KXmlParser();
+ parser.setInput(stream, null);
+ return parser;
+ } catch (FileNotFoundException | XmlPullParserException e) {
+ return null;
+ }
+ }
+
+ @Override
+ @NonNull
+ public XmlPullParser createXmlParser() {
return new KXmlParser();
}
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 1360334..e2d5e6a 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -27,6 +27,9 @@
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
+import android.app.SystemServiceRegistry_AccessorTest;
+import android.content.res.Resources_DelegateTest;
+import android.graphics.Color_DelegateTest;
import android.graphics.Matrix_DelegateTest;
import android.util.BridgeXmlPullAttributesTest;
@@ -38,7 +41,9 @@
RenderTests.class, LayoutParserWrapperTest.class,
BridgeXmlBlockParserTest.class, BridgeXmlPullAttributesTest.class,
Matrix_DelegateTest.class, TestDelegates.class,
- BridgeRenderSessionTest.class, ResourceHelperTest.class, BridgeContextTest.class
+ BridgeRenderSessionTest.class, ResourceHelperTest.class, BridgeContextTest.class,
+ SystemServiceRegistry_AccessorTest.class, Resources_DelegateTest.class,
+ Color_DelegateTest.class,
})
public class Main {
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
index 56d1d65..36c09ad 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -101,7 +101,7 @@
/** Location of the app's asset dir inside {@link #TEST_RES_DIR} */
private static final String APP_TEST_ASSET = APP_TEST_DIR + "/src/main/assets/";
private static final String APP_CLASSES_LOCATION =
- APP_TEST_DIR + "/build/intermediates/classes/debug/";
+ APP_TEST_DIR + "/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/";
protected static Bridge sBridge;
/** List of log messages generated by a render call. It can be used to find specific errors */
protected static ArrayList<String> sRenderMessages = Lists.newArrayList();
@@ -312,7 +312,7 @@
File buildProp = new File(PLATFORM_DIR, "build.prop");
File attrs = new File(res, "values" + File.separator + "attrs.xml");
sBridge = new Bridge();
- sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
+ sBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation, null,
ConfigGenerator.getEnumMap(attrs), getLayoutLog());
Bridge.getLock().lock();
try {
@@ -419,8 +419,9 @@
}
@Override
- public void fidelityWarning(String tag, String message, Throwable throwable,
- Object viewCookie, Object data) {
+ public void fidelityWarning(@Nullable String tag, String message,
+ Throwable throwable, Object cookie, Object data) {
+
System.out.println("FidelityWarning " + tag + ": " + message);
if (throwable != null) {
throwable.printStackTrace();
@@ -520,9 +521,10 @@
* doesn't throw any exceptions and matches the provided image.
*/
@Nullable
- protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName)
+ protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
+ boolean decoration)
throws ClassNotFoundException, FileNotFoundException {
- return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
+ return renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5, decoration);
}
/**
@@ -531,8 +533,12 @@
*/
@Nullable
protected RenderResult renderAndVerify(String layoutFileName, String goldenFileName,
- ConfigGenerator deviceConfig) throws ClassNotFoundException, FileNotFoundException {
+ ConfigGenerator deviceConfig, boolean decoration) throws ClassNotFoundException,
+ FileNotFoundException {
SessionParams params = createSessionParams(layoutFileName, deviceConfig);
+ if (!decoration) {
+ params.setForceNoDecor();
+ }
return renderAndVerify(params, goldenFileName);
}
@@ -566,7 +572,7 @@
.setProjectResources(sProjectResources)
.setTheme("AppTheme", true)
.setRenderingMode(RenderingMode.NORMAL)
- .setTargetSdk(22)
+ .setTargetSdk(28)
.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true)
.setAssetRepository(new TestAssetRepository(TEST_RES_DIR + "/" + APP_TEST_ASSET));
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
index 3b4851f..2c91ade 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
@@ -18,10 +18,12 @@
import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.ResourceNamespace;
-import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValueImpl;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.ide.common.rendering.api.XmlParserFactory;
import com.android.internal.R;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
@@ -35,15 +37,14 @@
import com.android.resources.Density;
import com.android.resources.Navigation;
import com.android.resources.ResourceType;
-import com.android.resources.ResourceUrl;
import org.junit.After;
import org.junit.Test;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.AssetManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -83,7 +84,7 @@
@Test
public void testActivity() throws ClassNotFoundException, FileNotFoundException {
- renderAndVerify("activity.xml", "activity.png");
+ renderAndVerify("activity.xml", "activity.png", true);
}
@Test
@@ -151,7 +152,18 @@
@Test
public void testAllWidgets() throws ClassNotFoundException, FileNotFoundException {
- renderAndVerify("allwidgets.xml", "allwidgets.png");
+ LayoutPullParser parser = createParserFromPath("allwidgets.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ layoutLibCallback.setUseShadow(false);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(ConfigGenerator.NEXUS_5)
+ .setCallback(layoutLibCallback)
+ .build();
+
+ renderAndVerify(params, "allwidgets.png");
// We expect fidelity warnings for Path.isConvex. Fail for anything else.
sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
@@ -159,15 +171,30 @@
@Test
public void testArrayCheck() throws ClassNotFoundException, FileNotFoundException {
- renderAndVerify("array_check.xml", "array_check.png");
+ renderAndVerify("array_check.xml", "array_check.png", false);
+
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(
+ message -> message.equals("Font$Builder.nAddAxis is not supported."));
}
@Test
public void testAllWidgetsTablet() throws ClassNotFoundException, FileNotFoundException {
- renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
+ LayoutPullParser parser = createParserFromPath("allwidgets.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ layoutLibCallback.setUseShadow(false);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(ConfigGenerator.NEXUS_7_2012)
+ .setCallback(layoutLibCallback)
+ .build();
+ renderAndVerify(params, "allwidgets_tab.png");
// We expect fidelity warnings for Path.isConvex. Fail for anything else.
sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+ sRenderMessages.removeIf(message -> message.equals("Font$Builder.nAddAxis is not supported."));
}
@Test
@@ -370,6 +397,7 @@
.setParser(parser)
.setCallback(layoutLibCallback)
.setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .disableDecoration()
.setRenderingMode(RenderingMode.V_SCROLL)
.build();
@@ -396,6 +424,10 @@
" android:layout_height=\"wrap_content\"\n" +
" android:layout_width=\"wrap_content\"\n" +
" android:src=\"@drawable/headset\"/>\n" +
+ " <ImageView\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:src=\"@drawable/clipped_even_odd\"/>\n" +
"</LinearLayout>");
// Create LayoutLibCallback.
LayoutLibTestCallback layoutLibCallback =
@@ -413,6 +445,61 @@
}
/**
+ * Test a ImageView which has a vector drawable as its src and tint attribute.
+ */
+ @Test
+ public void testVectorDrawableWithTintInImageView() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = LayoutPullParser.createFromString(
+ "<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:src=\"@drawable/vector_drawable_without_tint\"\n" +
+ " android:tint=\"#FF00FF00\" />");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
+
+ renderAndVerify(params, "vector_drawable_with_tint_in_image_view.png",
+ TimeUnit.SECONDS.toNanos(2));
+ }
+
+ /**
+ * Test a vector drawable which has tint attribute.
+ */
+ @Test
+ public void testVectorDrawableWithTintInItself() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = LayoutPullParser.createFromString(
+ "<ImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:src=\"@drawable/vector_drawable_with_tint\" />");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
+
+ renderAndVerify(params, "vector_drawable_with_tint_itself.png",
+ TimeUnit.SECONDS.toNanos(2));
+ }
+
+ /**
* Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives
* for vector drawables (lines, moves and cubic and quadratic curves).
*/
@@ -520,6 +607,79 @@
TimeUnit.SECONDS.toNanos(2));
}
+ /**
+ * Tests that the gradients are correctly displayed when using transparent colors
+ * and a wide range of offset values.
+ * <p/>
+ * http://b/112759140
+ */
+ @Test
+ public void testGradientColors() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = LayoutPullParser.createFromString(
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:padding=\"16dp\"\n" +
+ " android:orientation=\"horizontal\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\">\n" +
+ " <ImageView\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:src=\"@drawable/gradient\" />\n\n" +
+ "</LinearLayout>");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "gradient_colors.png",
+ TimeUnit.SECONDS.toNanos(2));
+ }
+
+ /**
+ * Tests that the gradients are correctly combined with alpha values.
+ * <p/>
+ * http://b/122260583
+ */
+ @Test
+ public void testGradientAlphaDrawable() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = LayoutPullParser.createFromString(
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:padding=\"16dp\"\n" +
+ " android:orientation=\"horizontal\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\">\n" +
+ " <ImageView\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:src=\"@drawable/vector_gradient_alpha\" />\n\n" +
+ "</LinearLayout>");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "gradient_alpha_drawable.png",
+ TimeUnit.SECONDS.toNanos(2));
+ }
+
/** Test activity.xml */
@Test
public void testScrollingAndMeasure() throws ClassNotFoundException, FileNotFoundException {
@@ -535,8 +695,8 @@
.setCallback(layoutLibCallback)
.setTheme("Theme.Material.NoActionBar.Fullscreen", false)
.setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
.build();
- params.setForceNoDecor();
params.setExtendedViewInfoMode(true);
// Do an only-measure pass
@@ -568,8 +728,8 @@
.setCallback(layoutLibCallback)
.setTheme("Theme.Material.NoActionBar.Fullscreen", false)
.setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
.build();
- params.setForceNoDecor();
params.setExtendedViewInfoMode(true);
result = renderAndVerify(params, "scrolled.png");
@@ -607,9 +767,9 @@
assertEquals("android", resources.getResourcePackageName(android.R.style.ButtonBar));
assertEquals("ButtonBar", resources.getResourceEntryName(android.R.style.ButtonBar));
assertEquals("style", resources.getResourceTypeName(android.R.style.ButtonBar));
- int id = Resources_Delegate.getLayoutlibCallback(resources).getResourceId(
- ResourceType.STRING,
- "app_name");
+ Integer id = Resources_Delegate.getLayoutlibCallback(resources).getOrGenerateResourceId(
+ new ResourceReference(ResourceNamespace.RES_AUTO, ResourceType.STRING, "app_name"));
+ assertNotNull(id);
assertEquals("com.android.layoutlib.test.myapplication:string/app_name",
resources.getResourceName(id));
assertEquals("com.android.layoutlib.test.myapplication",
@@ -644,9 +804,14 @@
Resources resources = Resources_Delegate.initSystem(context, assetManager, metrics,
configuration, params.getLayoutlibCallback());
- int id = Resources_Delegate.getLayoutlibCallback(resources).getResourceId(
- ResourceType.ARRAY,
- "string_array");
+ Integer id =
+ Resources_Delegate.getLayoutlibCallback(resources)
+ .getOrGenerateResourceId(
+ new ResourceReference(
+ ResourceNamespace.RES_AUTO,
+ ResourceType.ARRAY,
+ "string_array"));
+ assertNotNull(id);
String[] strings = resources.getStringArray(id);
assertArrayEquals(
new String[]{"mystring", "Hello world!", "candidates", "Unknown", "?EC"},
@@ -659,7 +824,9 @@
@Test
public void testFonts() throws ClassNotFoundException, FileNotFoundException {
// TODO: styles seem to be broken in TextView
- renderAndVerify("fonts_test.xml", "font_test.png");
+ renderAndVerify("fonts_test.xml", "font_test.png", false);
+ sRenderMessages.removeIf(
+ message -> message.equals("Font$Builder.nAddAxis is not supported."));
}
@Test
@@ -771,9 +938,22 @@
// Setup
// Create the layout pull parser for our resources (empty.xml can not be part of the test
// app as it won't compile).
- ParserFactory.setParserFactory(new com.android.ide.common.rendering.api.ParserFactory() {
+ ParserFactory.setParserFactory(new XmlParserFactory() {
@Override
- public XmlPullParser createParser(String debugName) throws XmlPullParserException {
+ @Nullable
+ public XmlPullParser createXmlParserForPsiFile(@NonNull String fileName) {
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public XmlPullParser createXmlParserForFile(@NonNull String fileName) {
+ return null;
+ }
+
+ @Override
+ @NonNull
+ public XmlPullParser createXmlParser() {
return new KXmlParser();
}
});
@@ -801,7 +981,7 @@
try {
ColorStateList stateList = ResourceHelper.getColorStateList(
- new ResourceValue(
+ new ResourceValueImpl(
ResourceNamespace.RES_AUTO,
ResourceType.COLOR,
"test_list",
@@ -823,7 +1003,7 @@
Resources.Theme theme = mContext.getResources().newTheme();
theme.applyStyle(R.style.ThemeOverlay_Material_Light, true);
stateList = ResourceHelper.getColorStateList(
- new ResourceValue(
+ new ResourceValueImpl(
ResourceNamespace.RES_AUTO,
ResourceType.COLOR,
"test_list",
@@ -847,8 +1027,115 @@
}
@Test
+ public void testShadowFlagsNoShadows() throws Exception {
+ LayoutPullParser parser = createParserFromPath("shadows_test.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ layoutLibCallback.setUseShadow(false);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(ConfigGenerator.NEXUS_5)
+ .setCallback(layoutLibCallback)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "shadows_test_no_shadow.png");
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+ }
+
+ @Test
public void testRectangleShadow() throws Exception {
- renderAndVerify("shadows_test.xml", "shadows_test.png");
+ LayoutPullParser parser = createParserFromPath("shadows_test.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ layoutLibCallback.setShadowQuality(false);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(ConfigGenerator.NEXUS_5)
+ .setCallback(layoutLibCallback)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "shadows_test.png");
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+ }
+
+ @Test
+ public void testHighQualityRectangleShadow() throws Exception {
+ LayoutPullParser parser = createParserFromPath("shadows_test.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ layoutLibCallback.setShadowQuality(true);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(ConfigGenerator.NEXUS_5)
+ .setCallback(layoutLibCallback)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "shadows_test_high_quality.png");
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+ }
+
+ @Test
+ public void testHighQualityRoundedEdgeRectangleShadow() throws Exception {
+ LayoutPullParser parser = createParserFromPath("shadows_rounded_edge_test.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ layoutLibCallback.setShadowQuality(true);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(ConfigGenerator.NEXUS_5)
+ .setCallback(layoutLibCallback)
+ .build();
+
+ renderAndVerify(params, "shadows_test_high_quality_rounded_edge.png");
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+ }
+
+ @Test
+ public void testHighQualityLargeViewShadow() throws Exception {
+ LayoutPullParser parser = createParserFromPath("large_view_shadows_test.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ layoutLibCallback.setShadowQuality(true);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(ConfigGenerator.NEXUS_5)
+ .setCallback(layoutLibCallback)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "large_shadows_test_high_quality.png");
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
+ }
+
+ @Test
+ public void testHighQualityShadowSizes() throws Exception {
+ LayoutPullParser parser = createParserFromPath("shadow_sizes_test.xml");
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ layoutLibCallback.setShadowQuality(true);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(ConfigGenerator.NEXUS_5)
+ .setCallback(layoutLibCallback)
+ .build();
+
+ renderAndVerify(params, "shadow_sizes_test_high_quality.png");
+ // We expect fidelity warnings for Path.isConvex. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("Path.isConvex is not supported."));
}
@Test
@@ -874,12 +1161,23 @@
params.getTargetSdkVersion(), params.isRtlSupported());
Resources resources = Resources_Delegate.initSystem(context, assetManager, metrics,
configuration, params.getLayoutlibCallback());
- int id = Resources_Delegate.getLayoutlibCallback(resources).getResourceId(
- ResourceType.STRING,
- "app_name");
- assertEquals(id, resources.getIdentifier("string/app_name", null, null));
- assertEquals(id, resources.getIdentifier("app_name", "string", null));
- assertEquals(0, resources.getIdentifier("string/does_not_exist", null, null));
+ Integer id =
+ Resources_Delegate.getLayoutlibCallback(resources)
+ .getOrGenerateResourceId(
+ new ResourceReference(
+ ResourceNamespace.RES_AUTO,
+ ResourceType.STRING,
+ "app_name"));
+ assertNotNull(id);
+ assertEquals(id.intValue(),
+ resources.getIdentifier("com.android.layoutlib.test.myapplication:string/app_name",
+ null, null));
+ assertEquals(id.intValue(), resources.getIdentifier("app_name", "string",
+ "com.android.layoutlib.test.myapplication"));
+ assertEquals(0, resources.getIdentifier("string/app_name", null, null));
+ assertEquals(0, resources.getIdentifier("string/app_name", null, "com.foo.bar"));
+ assertEquals(0, resources.getIdentifier("string/does_not_exist", null,
+ "com.android.layoutlib.test.myapplication"));
assertEquals(R.string.accept, resources.getIdentifier("android:string/accept", null,
null));
assertEquals(R.string.accept, resources.getIdentifier("string/accept", null,
@@ -1131,8 +1429,8 @@
.setCallback(layoutLibCallback)
.setTheme("Theme.Material.NoActionBar.Fullscreen", false)
.setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
.build();
- params.setForceNoDecor();
RenderResult result = RenderTestBase.render(sBridge, params, -1);
BufferedImage image = result.getImage();
@@ -1145,4 +1443,254 @@
RenderTestBase.verify("view_boundaries.png", image);
}
+
+ /**
+ * Test rendering of strings that have mixed RTL and LTR scripts.
+ * <p>
+ * http://b/37510906
+ */
+ @Test
+ public void testMixedRtlLtrRendering() throws Exception {
+ //
+ final String layout =
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:orientation=\"vertical\">\n" + "\n" +
+ " <TextView\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:textSize=\"30sp\"\n" +
+ " android:background=\"#55FF0000\"\n" +
+ " android:text=\"این یک رشته ایرانی است\"/>\n" +
+ " <TextView\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:textSize=\"30sp\"\n" +
+ " android:background=\"#55FF00FF\"\n" +
+ " android:text=\"این یک رشته ایرانی است(\"/>\n" +
+ " <TextView\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:textSize=\"30sp\"\n" +
+ " android:background=\"#55FAF012\"\n" +
+ " android:text=\")(این یک رشته ایرانی است(\"/>\n" +
+ "</LinearLayout>";
+
+ LayoutPullParser parser = LayoutPullParser.createFromString(layout);
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "rtl_ltr.png", -1);
+ }
+
+ @Test
+ public void testViewStub() throws Exception {
+ //
+ final String layout =
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:orientation=\"vertical\">\n" + "\n" +
+ " <ViewStub\n" +
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:layout=\"@layout/four_corners\"\n" +
+ " tools:visibility=\"visible\" />" +
+ "</LinearLayout>";
+
+ // Create the layout pull parser.
+ LayoutPullParser parser = LayoutPullParser.createFromString(layout);
+
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "view_stub.png", -1);
+ }
+
+ @Test
+ public void testImageResize() throws ClassNotFoundException {
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ layoutLibCallback.setUseShadow(false);
+
+ LayoutPullParser parser = LayoutPullParser.createFromString(
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:background=\"@drawable/ninepatch\"\n" +
+ " android:layout_margin=\"20dp\"\n" +
+ " android:orientation=\"vertical\">\n\n" +
+ " <Button\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n\n" +
+ " <Button\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:text=\"Button\" />\n"
+ + "</LinearLayout>");
+
+ // Ask for an image that it's 1/10th the size of the actual device image
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setImageFactory((width, height) ->
+ new BufferedImage(width / 10, height / 10,
+ BufferedImage.TYPE_INT_ARGB))
+ .setFlag(RenderParamsFlags.FLAG_KEY_RESULT_IMAGE_AUTO_SCALE, true)
+ .build();
+
+ renderAndVerify(params, "auto-scale-image.png");
+ }
+
+ @Test
+ public void testTranslation() throws ClassNotFoundException, FileNotFoundException {
+ RenderResult res = renderAndVerify("translate_test.xml", "translate_test.png", false);
+ ViewInfo rootInfo = res.getRootViews().get(0);
+ ViewInfo buttonInfo = rootInfo.getChildren().get(0);
+ assertEquals(100, buttonInfo.getLeft());
+ }
+
+ /**
+ * Test a vector drawable that uses trimStart and trimEnd. It also tests all the primitives
+ * for vector drawables (lines, moves and cubic and quadratic curves).
+ */
+ @Test
+ public void testCanvas() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = LayoutPullParser.createFromString(
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:padding=\"16dp\"\n" +
+ " android:orientation=\"horizontal\"\n" +
+ " android:layout_width=\"fill_parent\"\n" +
+ " android:layout_height=\"fill_parent\">\n" +
+ " <com.android.layoutlib.test.myapplication.widgets.CanvasTestView\n" +
+ " android:layout_height=\"fill_parent\"\n" +
+ " android:layout_width=\"fill_parent\"\n" +
+ " android:src=\"@drawable/android\" />\n" + "\n" +
+ "</LinearLayout>");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "canvas.png", TimeUnit.SECONDS.toNanos(2));
+ }
+
+ @Test
+ public void testTypedArrays() throws ClassNotFoundException, FileNotFoundException {
+ renderAndVerify("typed_array.xml", "typed_arrays.png", false);
+ }
+
+ /**
+ * Tests that the gradients are correctly displayed when using transparent colors
+ * and a wide range of offset values.
+ * <p/>
+ * http://b/112759140
+ */
+ @Test
+ public void testAnimatedVectorDrawableWithColorInterpolator() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = LayoutPullParser.createFromString(
+ "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:padding=\"16dp\"\n" +
+ " android:orientation=\"horizontal\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\">\n" +
+ " <ImageView\n" +
+ " android:layout_height=\"match_parent\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:src=\"@drawable/avd_color_interpolator\" />\n\n" +
+ "</LinearLayout>");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "color_interpolation.png",
+ TimeUnit.SECONDS.toNanos(2));
+ }
+
+ @Test
+ public void testManyLineBreaks() throws Exception {
+ String layout =
+ "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\">\n" + "\n" +
+ " <EditText\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:fallbackLineSpacing=\"true\"\n" +
+ " android:text=\"A very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very very very very very very very very " +
+ "very very very very very very very long text\"/>\n" +
+ "</FrameLayout>";
+
+ LayoutPullParser parser = LayoutPullParser.createFromString(layout);
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .disableDecoration()
+ .build();
+
+ renderAndVerify(params, "many_line_breaks.png",
+ TimeUnit.SECONDS.toNanos(2));
+ }
+
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
index 184538e..f2d62d4 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java
@@ -21,14 +21,11 @@
import com.android.ide.common.rendering.api.AdapterBinding;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.ParserFactory;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams.Key;
-import com.android.ide.common.resources.IntArrayWrapper;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
import com.android.resources.ResourceType;
-import com.android.util.Pair;
import com.android.utils.ILogger;
import org.kxml2.io.KXmlParser;
@@ -38,30 +35,33 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
+import java.util.HashMap;
import java.util.Map;
-import com.google.android.collect.Maps;
+import com.google.common.io.ByteStreams;
-import static org.junit.Assert.fail;
+import static com.android.ide.common.rendering.api.ResourceNamespace.RES_AUTO;
-@SuppressWarnings("deprecation") // For Pair
public class LayoutLibTestCallback extends LayoutlibCallback {
-
- private static final String PROJECT_CLASSES_LOCATION = "/testApp/MyApplication/build/intermediates/classes/debug/";
private static final String PACKAGE_NAME = "com.android.layoutlib.test.myapplication";
- private final Map<Integer, Pair<ResourceType, String>> mProjectResources = Maps.newHashMap();
- private final Map<IntArrayWrapper, String> mStyleableValueToNameMap = Maps.newHashMap();
- private final Map<ResourceType, Map<String, Integer>> mResources = Maps.newHashMap();
+ private final Map<Integer, ResourceReference> mProjectResources = new HashMap<>();
+ private final Map<ResourceReference, Integer> mResources = new HashMap<>();
private final ILogger mLog;
private final ActionBarCallback mActionBarCallback = new ActionBarCallback();
private final ClassLoader mModuleClassLoader;
private String mAdaptiveIconMaskPath;
+ private boolean mSetUseShadow = true;
+ private boolean mHighShadowQuality = true;
public LayoutLibTestCallback(ILogger logger, ClassLoader classLoader) {
mLog = logger;
@@ -75,27 +75,22 @@
final ResourceType resType = ResourceType.getEnum(resClass.getSimpleName());
if (resType != null) {
- final Map<String, Integer> resName2Id = Maps.newHashMap();
- mResources.put(resType, resName2Id);
-
for (Field field : resClass.getDeclaredFields()) {
final int modifiers = field.getModifiers();
if (Modifier.isStatic(modifiers)) { // May not be final in library projects
final Class<?> type = field.getType();
try {
- if (type.isArray() && type.getComponentType() == int.class) {
- mStyleableValueToNameMap.put(
- new IntArrayWrapper((int[]) field.get(null)),
- field.getName());
- } else if (type == int.class) {
+ if (type == int.class) {
final Integer value = (Integer) field.get(null);
- mProjectResources.put(value, Pair.of(resType, field.getName()));
- resName2Id.put(field.getName(), value);
- } else {
+ ResourceReference reference =
+ new ResourceReference(RES_AUTO, resType, field.getName());
+ mProjectResources.put(value, reference);
+ mResources.put(reference, value);
+ } else if (!(type.isArray() && type.getComponentType() == int.class)) {
mLog.error(null, "Unknown field type in R class: %1$s", type);
}
- } catch (IllegalAccessException ignored) {
- mLog.error(ignored, "Malformed R class: %1$s", PACKAGE_NAME + ".R");
+ } catch (IllegalAccessException e) {
+ mLog.error(e, "Malformed R class: %1$s", PACKAGE_NAME + ".R");
}
}
}
@@ -105,7 +100,7 @@
@Override
- public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
+ public Object loadView(@NonNull String name, @NonNull Class[] constructorSignature, Object[] constructorArgs)
throws Exception {
Class<?> viewClass = mModuleClassLoader.loadClass(name);
Constructor<?> viewConstructor = viewClass.getConstructor(constructorSignature);
@@ -113,6 +108,7 @@
return viewConstructor.newInstance(constructorArgs);
}
+ @NonNull
@Override
public String getNamespace() {
return String.format(SdkConstants.NS_CUSTOM_RESOURCES_S,
@@ -120,32 +116,18 @@
}
@Override
- public Pair<ResourceType, String> resolveResourceId(int id) {
+ public ResourceReference resolveResourceId(int id) {
return mProjectResources.get(id);
}
@Override
- public String resolveResourceId(int[] id) {
- return mStyleableValueToNameMap.get(new IntArrayWrapper(id));
+ public int getOrGenerateResourceId(@NonNull ResourceReference resource) {
+ Integer id = mResources.get(resource);
+ return id != null ? id : 0;
}
@Override
- public Integer getResourceId(ResourceType type, String name) {
- Map<String, Integer> resName2Id = mResources.get(type);
- if (resName2Id == null) {
- return null;
- }
- return resName2Id.get(name);
- }
-
- @Override
- public ILayoutPullParser getParser(String layoutName) {
- fail("This method shouldn't be called by this version of LayoutLib.");
- return null;
- }
-
- @Override
- public ILayoutPullParser getParser(ResourceValue layoutResource) {
+ public ILayoutPullParser getParser(@NonNull ResourceValue layoutResource) {
try {
return LayoutPullParser.createFromFile(new File(layoutResource.getValue()));
} catch (FileNotFoundException e) {
@@ -177,20 +159,36 @@
return false;
}
- @NonNull
@Override
- public ParserFactory getParserFactory() {
- return new ParserFactory() {
- @NonNull
- @Override
- public XmlPullParser createParser(@Nullable String debugName)
- throws XmlPullParserException {
- return new KXmlParser();
- }
- };
+ @Nullable
+ public XmlPullParser createXmlParserForPsiFile(@NonNull String fileName) {
+ return createXmlParserForFile(fileName);
}
@Override
+ @Nullable
+ public XmlPullParser createXmlParserForFile(@NonNull String fileName) {
+ try (FileInputStream fileStream = new FileInputStream(fileName)) {
+ // Read data fully to memory to be able to close the file stream.
+ ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+ ByteStreams.copy(fileStream, byteOutputStream);
+ KXmlParser parser = new KXmlParser();
+ parser.setInput(new ByteArrayInputStream(byteOutputStream.toByteArray()), null);
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+ return parser;
+ } catch (IOException | XmlPullParserException e) {
+ return null;
+ }
+ }
+
+ @Override
+ @NonNull
+ public XmlPullParser createXmlParser() {
+ return new KXmlParser();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked") // The Key<T> API is based on unchecked casts.
public <T> T getFlag(Key<T> key) {
if (key.equals(RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE)) {
return (T) PACKAGE_NAME;
@@ -198,10 +196,32 @@
if (key.equals(RenderParamsFlags.FLAG_KEY_ADAPTIVE_ICON_MASK_PATH)) {
return (T) mAdaptiveIconMaskPath;
}
+ if (key.equals(RenderParamsFlags.FLAG_RENDER_HIGH_QUALITY_SHADOW)) {
+ return (T) new Boolean(mHighShadowQuality);
+ }
+ if (key.equals(RenderParamsFlags.FLAG_ENABLE_SHADOW)) {
+ return (T) new Boolean(mSetUseShadow);
+ }
return null;
}
public void setAdaptiveIconMaskPath(String adaptiveIconMaskPath) {
mAdaptiveIconMaskPath = adaptiveIconMaskPath;
}
+
+ /**
+ * Enables shadow from rendering. Shadow rendering is enabled by default.
+ * @param useShadow true to enable shadow. False to disable.
+ */
+ public void setUseShadow(boolean useShadow) {
+ mSetUseShadow = useShadow;
+ }
+
+ /**
+ * Sets high quality shadow rendering. Turned off by default.
+ * @param useHighQuality true to use high quality shadow. False otherwise.
+ */
+ public void setShadowQuality(boolean useHighQuality) {
+ mHighShadowQuality = useHighQuality;
+ }
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
index 526613f..af0c9e5 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
@@ -17,6 +17,7 @@
package com.android.layoutlib.bridge.intensive.setup;
import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.ResourceNamespace;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -29,7 +30,7 @@
import java.io.FileNotFoundException;
import java.io.IOError;
import java.io.InputStream;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@@ -41,6 +42,9 @@
import static com.android.SdkConstants.TOOLS_URI;
public class LayoutPullParser extends KXmlParser implements ILayoutPullParser{
+
+ private ResourceNamespace mLayoutNamespace = ResourceNamespace.RES_AUTO;
+
@NonNull
public static LayoutPullParser createFromFile(@NonNull File layoutFile)
throws FileNotFoundException {
@@ -63,7 +67,7 @@
@NonNull
public static LayoutPullParser createFromString(@NonNull String contents) {
return new LayoutPullParser(new ByteArrayInputStream(
- contents.getBytes(Charset.forName("UTF-8"))));
+ contents.getBytes(StandardCharsets.UTF_8)));
}
private LayoutPullParser(@NonNull InputStream inputStream) {
@@ -96,7 +100,7 @@
continue;
}
if (map == null) {
- map = new HashMap<String, String>(4);
+ map = new HashMap<>(4);
}
map.put(attribute, getAttributeValue(i));
}
@@ -109,10 +113,12 @@
}
@Override
- @Deprecated
- public ILayoutPullParser getParser(String layoutName) {
- // Studio returns null.
- return null;
+ @NonNull
+ public ResourceNamespace getLayoutNamespace() {
+ return mLayoutNamespace;
}
+ public void setLayoutNamespace(@NonNull ResourceNamespace layoutNamespace) {
+ mLayoutNamespace = layoutNamespace;
+ }
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
index 4866051..b1801c6 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/ImageUtils.java
@@ -182,7 +182,7 @@
assertTrue(deleted);
}
ImageIO.write(deltaImage, "PNG", output);
- error += " - see details in " + output.getPath() + "\n";
+ error += " - see details in file://" + output.getPath() + "\n";
error = saveImageAndAppendMessage(image, error, relativePath);
System.out.println(error);
fail(error);
@@ -303,8 +303,14 @@
*/
@NonNull
private static File getFailureDir() {
- String workingDirString = System.getProperty("user.dir");
- File failureDir = new File(workingDirString, "out/failures");
+ File failureDir;
+ String failureDirString = System.getProperty("test_failure.dir");
+ if (failureDirString != null) {
+ failureDir = new File(failureDirString);
+ } else {
+ String workingDirString = System.getProperty("user.dir");
+ failureDir = new File(workingDirString, "out/failures");
+ }
//noinspection ResultOfMethodCallIgnored
failureDir.mkdirs();
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java
index 1c7857c..321bb77 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java
@@ -18,6 +18,7 @@
import com.android.SdkConstants;
import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.ide.common.rendering.api.IImageFactory;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.ResourceNamespace;
@@ -57,6 +58,8 @@
private LayoutLog mLayoutLog;
private Map<SessionParams.Key, Object> mFlags = new HashMap<>();
private AssetRepository mAssetRepository = null;
+ private boolean mDecor = true;
+ private IImageFactory mImageFactory = null;
@NonNull
public SessionParamsBuilder setParser(@NonNull LayoutPullParser layoutParser) {
@@ -146,6 +149,18 @@
}
@NonNull
+ public SessionParamsBuilder disableDecoration() {
+ mDecor = false;
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setImageFactory(@NonNull IImageFactory imageFactory) {
+ mImageFactory = imageFactory;
+ return this;
+ }
+
+ @NonNull
public SessionParams build() {
assert mFrameworkResources != null;
assert mProjectResources != null;
@@ -157,7 +172,7 @@
ResourceResolver resourceResolver = ResourceResolver.create(
ImmutableMap.of(
ResourceNamespace.ANDROID, mFrameworkResources.getConfiguredResources(config),
- ResourceNamespace.TODO, mProjectResources.getConfiguredResources(config)),
+ ResourceNamespace.TODO(), mProjectResources.getConfiguredResources(config)),
new ResourceReference(
ResourceNamespace.fromBoolean(!isProjectTheme),
ResourceType.STYLE,
@@ -166,10 +181,17 @@
SessionParams params = new SessionParams(mLayoutParser, mRenderingMode, mProjectKey /* for
caching */, mConfigGenerator.getHardwareConfig(), resourceResolver, mLayoutlibCallback,
mMinSdk, mTargetSdk, mLayoutLog);
+ if (mImageFactory != null) {
+ params.setImageFactory(mImageFactory);
+ }
mFlags.forEach(params::setFlag);
params.setAssetRepository(mAssetRepository);
+ if (!mDecor) {
+ params.setForceNoDecor();
+ }
+
return params;
}
}
diff --git a/create/.classpath b/create/.classpath
deleted file mode 100644
index 25c3b3e..0000000
--- a/create/.classpath
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src"/>
- <classpathentry excluding="mock_data/" kind="src" path="tests"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
- <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/asm/asm-4.0.jar" sourcepath="/ANDROID_PLAT_SRC/prebuilts/misc/common/asm/src.zip"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/create/.project b/create/.project
deleted file mode 100644
index e100d17..0000000
--- a/create/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>layoutlib_create</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.eclipse.jdt.core.javabuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.eclipse.jdt.core.javanature</nature>
- </natures>
-</projectDescription>
diff --git a/create/Android.bp b/create/Android.bp
index 3ac790e..961456a 100644
--- a/create/Android.bp
+++ b/create/Android.bp
@@ -22,6 +22,7 @@
main_class: "com.android.tools.layoutlib.create.Main",
static_libs: [
"asm-6.0",
+ "guava",
"layoutlib-common",
],
}
diff --git a/create/create.iml b/create/create.iml
index 8c05400..8814d91 100644
--- a/create/create.iml
+++ b/create/create.iml
@@ -24,5 +24,16 @@
</orderEntry>
<orderEntry type="library" scope="TEST" name="junit" level="project" />
<orderEntry type="module" module-name="common" />
+ <orderEntry type="module-library">
+ <library name="guava">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/22.0/guava-22.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/22.0/guava-22.0-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
</component>
-</module>
\ No newline at end of file
+</module>
diff --git a/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index 919ef9f..b77708b 100644
--- a/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -16,6 +16,9 @@
package com.android.tools.layoutlib.create;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
@@ -27,16 +30,24 @@
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -47,20 +58,49 @@
*/
public class AsmAnalyzer {
+ public static class Result {
+ private final Map<String, ClassReader> mFound;
+ private final Map<String, ClassReader> mDeps;
+ private final Map<String, InputStream> mFilesFound;
+ private final Set<String> mReplaceMethodCallClasses;
+
+ private Result(Map<String, ClassReader> found, Map<String, ClassReader> deps,
+ Map<String, InputStream> filesFound, Set<String> replaceMethodCallClasses) {
+ mFound = found;
+ mDeps = deps;
+ mFilesFound = filesFound;
+ mReplaceMethodCallClasses = replaceMethodCallClasses;
+ }
+
+ public Map<String, ClassReader> getFound() {
+ return mFound;
+ }
+
+ public Map<String, ClassReader> getDeps() {
+ return mDeps;
+ }
+
+ public Map<String, InputStream> getFilesFound() {
+ return mFilesFound;
+ }
+
+ public Set<String> getReplaceMethodCallClasses() {
+ return mReplaceMethodCallClasses;
+ }
+ }
+
// Note: a bunch of stuff has package-level access for unit tests. Consider it private.
/** Output logger. */
private final Log mLog;
/** The input source JAR to parse. */
private final List<String> mOsSourceJar;
- /** The generator to fill with the class list and dependency list. */
- private final AsmGenerator mGen;
/** Keep all classes that derive from these one (these included). */
private final String[] mDeriveFrom;
/** Glob patterns of classes to keep, e.g. "com.foo.*" */
private final String[] mIncludeGlobs;
- /** The set of classes to exclude.*/
- private final Set<String> mExcludedClasses;
+ /** Glob patterns of classes to exclude.*/
+ private final String[] mExcludedGlobs;
/** Glob patterns of files to keep as is. */
private final String[] mIncludeFileGlobs;
/** Internal names of classes that contain method calls that need to be rewritten. */
@@ -71,47 +111,65 @@
*
* @param log The log output.
* @param osJarPath The input source JARs to parse.
- * @param gen The generator to fill with the class list and dependency list.
* @param deriveFrom Keep all classes that derive from these one (these included).
* @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*"
* ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
* @param includeFileGlobs Glob patterns of files which are kept as is. This is only for files
* not ending in .class.
*/
- public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
- String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses,
+ public AsmAnalyzer(Log log, List<String> osJarPath,
+ String[] deriveFrom, String[] includeGlobs, String[] excludedGlobs,
String[] includeFileGlobs) {
mLog = log;
- mGen = gen;
- mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
+ mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<>();
mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
- mExcludedClasses = excludeClasses;
+ mExcludedGlobs = excludedGlobs != null ? excludedGlobs : new String[0];
mIncludeFileGlobs = includeFileGlobs != null ? includeFileGlobs : new String[0];
}
/**
- * Starts the analysis using parameters from the constructor.
- * Fills the generator with classes & dependencies found.
+ * Starts the analysis using parameters from the constructor and returns the result.
*/
- public void analyze() throws IOException, LogAbortException {
-
- TreeMap<String, ClassReader> zipClasses = new TreeMap<>();
+ @NotNull
+ public Result analyze() throws IOException {
+ Map<String, ClassReader> zipClasses = new TreeMap<>();
Map<String, InputStream> filesFound = new TreeMap<>();
parseZip(mOsSourceJar, zipClasses, filesFound);
mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
mOsSourceJar.size() > 1 ? "s" : "");
- Map<String, ClassReader> found = findIncludes(zipClasses);
- Map<String, ClassReader> deps = findDeps(zipClasses, found);
+ Pattern[] includePatterns = Arrays.stream(mIncludeGlobs).parallel()
+ .map(AsmAnalyzer::getPatternFromGlob)
+ .toArray(Pattern[]::new);
+ Pattern[] excludePatterns = Arrays.stream(mExcludedGlobs).parallel()
+ .map(AsmAnalyzer::getPatternFromGlob)
+ .toArray(Pattern[]::new);
- if (mGen != null) {
- mGen.setKeep(found);
- mGen.setDeps(deps);
- mGen.setCopyFiles(filesFound);
- mGen.setRewriteMethodCallClasses(mReplaceMethodCallClasses);
- }
+
+ Map<String, ClassReader> found = new HashMap<>();
+ findIncludes(mLog, includePatterns, mDeriveFrom, zipClasses, entry -> {
+ if (!matchesAny(entry.getKey(), excludePatterns)) {
+ found.put(entry.getKey(), entry.getValue());
+ }
+ });
+
+ Map<String, ClassReader> deps = new HashMap<>();
+ findDeps(mLog, zipClasses, found, keepEntry -> {
+ if (!matchesAny(keepEntry.getKey(), excludePatterns)) {
+ found.put(keepEntry.getKey(), keepEntry.getValue());
+ }
+ }, depEntry -> {
+ if (!matchesAny(depEntry.getKey(), excludePatterns)) {
+ deps.put(depEntry.getKey(), depEntry.getValue());
+ }
+ });
+
+ mLog.info("Found %1$d classes to keep, %2$d class dependencies.",
+ found.size(), deps.size());
+
+ return new Result(found, deps, filesFound, mReplaceMethodCallClasses);
}
/**
@@ -134,27 +192,39 @@
includeFilePatterns[i] = getPatternFromGlob(mIncludeFileGlobs[i]);
}
+ List<ForkJoinTask<?>> futures = new ArrayList<>();
for (String jarPath : jarPathList) {
- ZipFile zip = new ZipFile(jarPath);
- Enumeration<? extends ZipEntry> entries = zip.entries();
- ZipEntry entry;
- while (entries.hasMoreElements()) {
- entry = entries.nextElement();
- if (entry.getName().endsWith(".class")) {
- ClassReader cr = new ClassReader(zip.getInputStream(entry));
- String className = classReaderToClassName(cr);
- classes.put(className, cr);
- } else {
- for (Pattern includeFilePattern : includeFilePatterns) {
- if (includeFilePattern.matcher(entry.getName()).matches()) {
- filesFound.put(entry.getName(), zip.getInputStream(entry));
- break;
+ futures.add(ForkJoinPool.commonPool().submit(() -> {
+ try {
+ ZipFile zip = new ZipFile(jarPath);
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ ZipEntry entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.nextElement();
+ if (entry.getName().endsWith(".class")) {
+ ClassReader cr = new ClassReader(zip.getInputStream(entry));
+ String className = classReaderToClassName(cr);
+ synchronized (classes) {
+ classes.put(className, cr);
+ }
+ } else {
+ for (Pattern includeFilePattern : includeFilePatterns) {
+ if (includeFilePattern.matcher(entry.getName()).matches()) {
+ synchronized (filesFound) {
+ filesFound.put(entry.getName(), zip.getInputStream(entry));
+ }
+ break;
+ }
+ }
}
}
+ } catch (IOException e) {
+ e.printStackTrace();
}
- }
+ }));
}
+ futures.forEach(ForkJoinTask::join);
}
/**
@@ -173,7 +243,7 @@
* Utility that returns the fully qualified binary class name from a path-like FQCN.
* E.g. it returns android.view.View from android/view/View.
*/
- static String internalToBinaryClassName(String className) {
+ private static String internalToBinaryClassName(String className) {
if (className == null) {
return null;
} else {
@@ -181,25 +251,43 @@
}
}
+ private static boolean matchesAny(@Nullable String className, @NotNull Pattern[] patterns) {
+ for (int i = 0; i < patterns.length; i++) {
+ if (patterns[i].matcher(className).matches()) {
+ return true;
+ }
+ }
+
+ int dollarIdx = className.indexOf('$');
+ if (dollarIdx != -1) {
+ // This is an inner class, if the outer class matches, we also consider this a match
+ return matchesAny(className.substring(0, dollarIdx), patterns);
+ }
+
+ return false;
+ }
+
/**
* Process the "includes" arrays.
* <p/>
* This updates the in_out_found map.
*/
- Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses)
- throws LogAbortException {
+ private static void findIncludes(@NotNull Log log, @NotNull Pattern[] includePatterns,
+ @NotNull String[] deriveFrom, @NotNull Map<String, ClassReader> zipClasses,
+ @NotNull Consumer<Entry<String, ClassReader>> newInclude) throws FileNotFoundException {
TreeMap<String, ClassReader> found = new TreeMap<>();
- mLog.debug("Find classes to include.");
+ log.debug("Find classes to include.");
- for (String s : mIncludeGlobs) {
- findGlobs(s, zipClasses, found);
- }
- for (String s : mDeriveFrom) {
- findClassesDerivingFrom(s, zipClasses, found);
+ zipClasses.entrySet().stream()
+ .filter(entry -> matchesAny(entry.getKey(), includePatterns))
+ .forEach(entry -> found.put(entry.getKey(), entry.getValue()));
+
+ for (String entry : deriveFrom) {
+ findClassesDerivingFrom(entry, zipClasses, found);
}
- return found;
+ found.entrySet().forEach(newInclude);
}
@@ -208,47 +296,19 @@
* If found, insert it in the in_out_found map.
* Returns the class reader object.
*/
- ClassReader findClass(String className, Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inOutFound) throws LogAbortException {
+ static ClassReader findClass(String className, Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutFound) throws FileNotFoundException {
ClassReader classReader = zipClasses.get(className);
if (classReader == null) {
- throw new LogAbortException("Class %s not found by ASM in %s",
- className, mOsSourceJar);
+ throw new FileNotFoundException(String.format("Class %s not found by ASM", className));
}
inOutFound.put(className, classReader);
return classReader;
}
- /**
- * Insert in the inOutFound map all classes found in zipClasses that match the
- * given glob pattern.
- * <p/>
- * The glob pattern is not a regexp. It only accepts the "*" keyword to mean
- * "anything but a period". The "." and "$" characters match themselves.
- * The "**" keyword means everything including ".".
- * <p/>
- * Examples:
- * <ul>
- * <li>com.foo.* matches all classes in the package com.foo but NOT sub-packages.
- * <li>com.foo*.*$Event matches all internal Event classes in a com.foo*.* class.
- * </ul>
- */
- void findGlobs(String globPattern, Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inOutFound) throws LogAbortException {
- Pattern regexp = getPatternFromGlob(globPattern);
-
- for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
- String class_name = entry.getKey();
- if (regexp.matcher(class_name).matches() &&
- !mExcludedClasses.contains(getOuterClassName(class_name))) {
- findClass(class_name, zipClasses, inOutFound);
- }
- }
- }
-
- Pattern getPatternFromGlob(String globPattern) {
+ static Pattern getPatternFromGlob(String globPattern) {
// transforms the glob pattern in a regexp:
// - escape "." with "\."
// - replace "*" by "[^.]*"
@@ -271,11 +331,8 @@
* determine if they are derived from the given FQCN super class name.
* Inserts the super class and all the class objects found in the map.
*/
- void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inOutFound) throws LogAbortException {
- if (mExcludedClasses.contains(getOuterClassName(super_name))) {
- return;
- }
+ static void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutFound) throws FileNotFoundException {
findClass(super_name, zipClasses, inOutFound);
for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
@@ -314,16 +371,20 @@
* Finds all dependencies for all classes in keepClasses which are also
* listed in zipClasses. Returns a map of all the dependencies found.
*/
- Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses,
- Map<String, ClassReader> inOutKeepClasses) {
+ void findDeps(Log log,
+ Map<String, ClassReader> zipClasses,
+ Map<String, ClassReader> inOutKeepClasses,
+ Consumer<Entry<String, ClassReader>> newKeep,
+ Consumer<Entry<String, ClassReader>> newDep) {
+ TreeMap<String, ClassReader> keep = new TreeMap<>(inOutKeepClasses);
TreeMap<String, ClassReader> deps = new TreeMap<>();
TreeMap<String, ClassReader> new_deps = new TreeMap<>();
TreeMap<String, ClassReader> new_keep = new TreeMap<>();
TreeMap<String, ClassReader> temp = new TreeMap<>();
DependencyVisitor visitor = getVisitor(zipClasses,
- inOutKeepClasses, new_keep,
+ keep, new_keep,
deps, new_deps);
for (ClassReader cr : inOutKeepClasses.values()) {
@@ -332,15 +393,17 @@
}
while (new_deps.size() > 0 || new_keep.size() > 0) {
+ new_deps.entrySet().forEach(newDep);
+ new_keep.entrySet().forEach(newKeep);
+ keep.putAll(new_keep);
deps.putAll(new_deps);
- inOutKeepClasses.putAll(new_keep);
temp.clear();
temp.putAll(new_deps);
temp.putAll(new_keep);
new_deps.clear();
new_keep.clear();
- mLog.debug("Found %1$d to keep, %2$d dependencies.",
+ log.debug("Found %1$d to keep, %2$d dependencies.",
inOutKeepClasses.size(), deps.size());
for (ClassReader cr : temp.values()) {
@@ -348,19 +411,6 @@
cr.accept(visitor, 0 /* flags */);
}
}
-
- mLog.info("Found %1$d classes to keep, %2$d class dependencies.",
- inOutKeepClasses.size(), deps.size());
-
- return deps;
- }
-
- private String getOuterClassName(String className) {
- int pos = className.indexOf('$');
- if (pos > 0) {
- return className.substring(0, pos);
- }
- return className;
}
// ----------------------------------
@@ -425,8 +475,7 @@
if (mInKeep.containsKey(className) ||
mOutKeep.containsKey(className) ||
mInDeps.containsKey(className) ||
- mOutDeps.containsKey(className) ||
- mExcludedClasses.contains(getOuterClassName(className))) {
+ mOutDeps.containsKey(className)) {
return;
}
@@ -483,7 +532,8 @@
/**
* Considers this {@link Type}. For arrays, the element type is considered.
- * If the type is an object, it's internal name is considered.
+ * If the type is an object, its internal name is considered. If it is a method type,
+ * iterate through the argument and return types.
*/
public void considerType(Type t) {
if (t != null) {
@@ -493,6 +543,12 @@
if (t.getSort() == Type.OBJECT) {
considerName(t.getInternalName());
}
+ if (t.getSort() == Type.METHOD) {
+ for (Type type : t.getArgumentTypes()) {
+ considerType(type);
+ }
+ considerType(t.getReturnType());
+ }
}
}
diff --git a/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 395bfb8..35fc584 100644
--- a/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -16,17 +16,18 @@
package com.android.tools.layoutlib.create;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.create.AsmAnalyzer.Result;
+
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.ByteArrayOutputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ListIterator;
@@ -34,8 +35,6 @@
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
-import java.util.jar.JarEntry;
-import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -47,8 +46,6 @@
/** Output logger. */
private final Log mLog;
- /** The path of the destination JAR to create. */
- private final String mOsDestJar;
/** List of classes to inject in the final JAR from _this_ archive. */
private final Class<?>[] mInjectClasses;
/** All classes to output as-is, except if they have native methods. */
@@ -87,12 +84,10 @@
* Creates a new generator that can generate the output JAR with the stubbed classes.
*
* @param log Output logger.
- * @param osDestJar The path of the destination JAR to create.
* @param createInfo Creation parameters. Must not be null.
*/
- public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
+ public AsmGenerator(Log log, ICreateInfo createInfo) {
mLog = log;
- mOsDestJar = osDestJar;
ArrayList<Class<?>> injectedClasses =
new ArrayList<>(Arrays.asList(createInfo.getInjectedClasses()));
// Search for and add anonymous inner classes also.
@@ -229,7 +224,7 @@
* Utility that returns the internal ASM class name from a fully qualified binary class
* name. E.g. it returns android/view/View from android.view.View.
*/
- String binaryToInternalClassName(String className) {
+ private String binaryToInternalClassName(String className) {
if (className == null) {
return null;
} else {
@@ -237,27 +232,8 @@
}
}
- /** Sets the map of classes to output as-is, except if they have native methods */
- public void setKeep(Map<String, ClassReader> keep) {
- mKeep = keep;
- }
-
- /** Sets the map of dependencies that must be completely stubbed */
- public void setDeps(Map<String, ClassReader> deps) {
- mDeps = deps;
- }
-
- /** Sets the map of files to output as-is. */
- public void setCopyFiles(Map<String, InputStream> copyFiles) {
- mCopyFiles = copyFiles;
- }
-
- public void setRewriteMethodCallClasses(Set<String> rewriteMethodCallClasses) {
- mReplaceMethodCallsClasses = rewriteMethodCallClasses;
- }
-
/** Generates the final JAR */
- public void generate() throws IOException {
+ public Map<String, byte[]> generate() throws IOException {
TreeMap<String, byte[]> all = new TreeMap<>();
for (Class<?> clazz : mInjectClasses) {
@@ -296,28 +272,7 @@
mLog.info("# keep classes: %d", mKeep.size());
mLog.info("# renamed : %d", mRenameCount);
- createJar(new FileOutputStream(mOsDestJar), all);
- mLog.info("Created JAR file %s", mOsDestJar);
- }
-
- /**
- * Writes the JAR file.
- *
- * @param outStream The file output stream were to write the JAR.
- * @param all The map of all classes to output.
- * @throws IOException if an I/O error has occurred
- */
- void createJar(FileOutputStream outStream, Map<String,byte[]> all) throws IOException {
- JarOutputStream jar = new JarOutputStream(outStream);
- for (Entry<String, byte[]> entry : all.entrySet()) {
- String name = entry.getKey();
- JarEntry jar_entry = new JarEntry(name);
- jar.putNextEntry(jar_entry);
- jar.write(entry.getValue());
- jar.closeEntry();
- }
- jar.flush();
- jar.close();
+ return all;
}
/**
@@ -391,8 +346,11 @@
if (mInjectedMethodsMap.keySet().contains(binaryNewName)) {
cv = new InjectMethodsAdapter(cv, mInjectedMethodsMap.get(binaryNewName));
}
- cv = new TransformClassAdapter(mLog, Collections.emptySet(), mDeleteReturns.get(className),
- newName, cv, stubNativesOnly);
+ cv = StubClassAdapter.builder(mLog, cv)
+ .withDeleteReturns(mDeleteReturns.get(className))
+ .withNewClassName(newName)
+ .useOnlyStubNative(stubNativesOnly)
+ .build();
Set<String> delegateMethods = mDelegateMethods.get(className);
if (delegateMethods != null && !delegateMethods.isEmpty()) {
@@ -412,6 +370,11 @@
if (!mPromotedClasses.isEmpty()) {
cv = new PromoteClassClassAdapter(cv, mPromotedClasses);
}
+
+ // Make sure no class file has a version above 52 (corresponding to Java 8),
+ // so that layoutlib can be run with JDK 8.
+ cv = new ChangeFileVersionAdapter(mLog, 52, cv);
+
cr.accept(cv, 0);
return cw.toByteArray();
@@ -461,4 +424,16 @@
return buffer.toByteArray();
}
+ /**
+ * Sets the inputs for the generator phase.
+ */
+ public void setAnalysisResult(@NotNull Result analysisResult) {
+ // Map of classes to output as-is, except if they have native methods
+ mKeep = analysisResult.getFound();
+ // Map of dependencies that must be completely stubbed
+ mDeps = analysisResult.getDeps();
+ // Map of files to output as-is.
+ mCopyFiles = analysisResult.getFilesFound();
+ mReplaceMethodCallsClasses = analysisResult.getReplaceMethodCallClasses();
+ }
}
diff --git a/create/src/com/android/tools/layoutlib/create/ChangeFileVersionAdapter.java b/create/src/com/android/tools/layoutlib/create/ChangeFileVersionAdapter.java
new file mode 100644
index 0000000..3b66367
--- /dev/null
+++ b/create/src/com/android/tools/layoutlib/create/ChangeFileVersionAdapter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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 org.objectweb.asm.ClassVisitor;
+
+/**
+ * Class adapter that modifies the file version of classes to be
+ * below a certain maximum version.
+ */
+public class ChangeFileVersionAdapter extends ClassVisitor {
+ private final Log mLog;
+ private final int mMaxVersion;
+
+ public ChangeFileVersionAdapter(Log logger, int maxVersion, ClassVisitor cv) {
+ super(Main.ASM_VERSION, cv);
+ mLog = logger;
+ mMaxVersion = maxVersion;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName,
+ String[] interfaces) {
+ int classFileVersion = version;
+ if (classFileVersion > mMaxVersion) {
+ classFileVersion = mMaxVersion;
+ mLog.debug("Class %s has had its file version changed from %d to %d", name, version,
+ classFileVersion);
+ }
+ super.visit(classFileVersion, access, name, signature, superName, interfaces);
+ }
+}
diff --git a/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index e46e67f..02766e2 100644
--- a/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -67,7 +67,7 @@
}
@Override
- public Set<String> getExcludedClasses() {
+ public String[] getExcludedClasses() {
String[] refactoredClasses = getJavaPkgClasses();
int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length;
Set<String> excludedClasses = new HashSet<>(count);
@@ -75,7 +75,7 @@
excludedClasses.add(refactoredClasses[i]);
}
excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES));
- return excludedClasses;
+ return excludedClasses.toArray(new String[0]);
}
@Override
@@ -118,6 +118,7 @@
public final static String[] DELEGATE_METHODS = new String[] {
"android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;",
"android.content.res.Resources#getAnimation",
+ "android.content.res.Resources#getAttributeSetSourceResId",
"android.content.res.Resources#getBoolean",
"android.content.res.Resources#getColor",
"android.content.res.Resources#getColorStateList",
@@ -153,9 +154,12 @@
"android.content.res.Resources$Theme#resolveAttribute",
"android.content.res.Resources$Theme#resolveAttributes",
"android.content.res.AssetManager#open",
- "android.content.res.AssetManager#newTheme",
- "android.content.res.AssetManager#deleteTheme",
+ "android.content.res.AssetManager#nativeCreate",
+ "android.content.res.AssetManager#nativeDestroy",
+ "android.content.res.AssetManager#nativeThemeCreate",
+ "android.content.res.AssetManager#nativeThemeDestroy",
"android.content.res.AssetManager#getAssignedPackageIdentifiers",
+ "android.content.res.AssetManager#nativeCreateIdmapsForStaticOverlaysTargetingAndroid",
"android.content.res.TypedArray#getValueAt",
"android.content.res.TypedArray#obtain",
"android.graphics.BitmapFactory#finishDecode",
@@ -165,9 +169,9 @@
"android.graphics.drawable.GradientDrawable#buildRing",
"android.graphics.drawable.AdaptiveIconDrawable#<init>",
"android.graphics.FontFamily#addFont",
- "android.graphics.Typeface#buildSystemFallback",
"android.graphics.Typeface#create",
- "android.graphics.Typeface#createFontFamily",
+ "android.graphics.Typeface$Builder#createAssetUid",
+ "android.graphics.fonts.SystemFonts#buildSystemFallback",
"android.os.Binder#getNativeBBinderHolder",
"android.os.Binder#getNativeFinalizer",
"android.os.Handler#sendMessageAtTime",
@@ -181,6 +185,7 @@
"android.view.Display#updateDisplayInfoLocked",
"android.view.Display#getWindowManager",
"android.view.HandlerActionQueue#postDelayed",
+ "android.view.LayoutInflater#initPrecompiledViews",
"android.view.LayoutInflater#rInflate",
"android.view.LayoutInflater#parseInclude",
"android.view.View#draw",
@@ -190,44 +195,54 @@
"android.view.View#isInEditMode",
"android.view.ViewRootImpl#isInTouchMode",
"android.view.WindowManagerGlobal#getWindowManagerService",
- "android.view.inputmethod.InputMethodManager#getInstance",
+ "android.view.inputmethod.InputMethodManager#isInEditMode",
"android.view.MenuInflater#registerMenu",
- "android.view.RenderNode#getMatrix",
- "android.view.RenderNode#nCreate",
- "android.view.RenderNode#nGetNativeFinalizer",
- "android.view.RenderNode#nSetElevation",
- "android.view.RenderNode#nGetElevation",
- "android.view.RenderNode#nSetTranslationX",
- "android.view.RenderNode#nGetTranslationX",
- "android.view.RenderNode#nSetTranslationY",
- "android.view.RenderNode#nGetTranslationY",
- "android.view.RenderNode#nSetTranslationZ",
- "android.view.RenderNode#nGetTranslationZ",
- "android.view.RenderNode#nSetRotation",
- "android.view.RenderNode#nGetRotation",
- "android.view.RenderNode#nSetLeft",
- "android.view.RenderNode#nSetTop",
- "android.view.RenderNode#nSetRight",
- "android.view.RenderNode#nSetBottom",
- "android.view.RenderNode#nSetLeftTopRightBottom",
- "android.view.RenderNode#nSetPivotX",
- "android.view.RenderNode#nGetPivotX",
- "android.view.RenderNode#nSetPivotY",
- "android.view.RenderNode#nGetPivotY",
- "android.view.RenderNode#nSetScaleX",
- "android.view.RenderNode#nGetScaleX",
- "android.view.RenderNode#nSetScaleY",
- "android.view.RenderNode#nGetScaleY",
- "android.view.RenderNode#nIsPivotExplicitlySet",
+ "android.graphics.RenderNode#getMatrix",
+ "android.graphics.RenderNode#nCreate",
+ "android.graphics.RenderNode#nGetNativeFinalizer",
+ "android.graphics.RenderNode#nSetElevation",
+ "android.graphics.RenderNode#nGetElevation",
+ "android.graphics.RenderNode#nSetTranslationX",
+ "android.graphics.RenderNode#nGetTranslationX",
+ "android.graphics.RenderNode#nSetTranslationY",
+ "android.graphics.RenderNode#nGetTranslationY",
+ "android.graphics.RenderNode#nSetTranslationZ",
+ "android.graphics.RenderNode#nGetTranslationZ",
+ "android.graphics.RenderNode#nSetRotation",
+ "android.graphics.RenderNode#nGetRotation",
+ "android.graphics.RenderNode#nSetLeft",
+ "android.graphics.RenderNode#nSetTop",
+ "android.graphics.RenderNode#nSetRight",
+ "android.graphics.RenderNode#nSetBottom",
+ "android.graphics.RenderNode#nSetLeftTopRightBottom",
+ "android.graphics.RenderNode#nSetPivotX",
+ "android.graphics.RenderNode#nGetPivotX",
+ "android.graphics.RenderNode#nSetPivotY",
+ "android.graphics.RenderNode#nGetPivotY",
+ "android.graphics.RenderNode#nSetScaleX",
+ "android.graphics.RenderNode#nGetScaleX",
+ "android.graphics.RenderNode#nSetScaleY",
+ "android.graphics.RenderNode#nGetScaleY",
+ "android.graphics.RenderNode#nIsPivotExplicitlySet",
+ "android.provider.DeviceConfig#getBoolean",
+ "android.provider.DeviceConfig#getFloat",
+ "android.provider.DeviceConfig#getInt",
+ "android.provider.DeviceConfig#getLong",
+ "android.provider.DeviceConfig#getString",
"android.view.PointerIcon#loadResource",
+ "android.view.PointerIcon#registerDisplayListener",
+ "android.view.SurfaceControl#nativeCreateTransaction",
+ "android.view.SurfaceControl#nativeGetNativeTransactionFinalizer",
"android.view.ViewGroup#drawChild",
"com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
"com.android.internal.util.XmlUtils#convertValueToInt",
+ "dalvik.system.VMRuntime#getNotifyNativeInterval",
"dalvik.system.VMRuntime#newUnpaddedArray",
"libcore.io.MemoryMappedFile#mmapRO",
"libcore.io.MemoryMappedFile#close",
"libcore.io.MemoryMappedFile#bigEndianIterator",
"libcore.util.NativeAllocationRegistry#applyFreeFunction",
+ "libcore.util.NativeAllocationRegistry#registerNativeAllocation",
};
/**
@@ -239,10 +254,13 @@
"android.graphics.Bitmap",
"android.graphics.BitmapFactory",
"android.graphics.BitmapShader",
+ "android.graphics.BlendModeColorFilter",
"android.graphics.BlurMaskFilter",
"android.graphics.Canvas",
+ "android.graphics.Color",
"android.graphics.ColorFilter",
"android.graphics.ColorMatrixColorFilter",
+ "android.graphics.ColorSpace$Rgb",
"android.graphics.ComposePathEffect",
"android.graphics.ComposeShader",
"android.graphics.CornerPathEffect",
@@ -271,10 +289,13 @@
"android.graphics.Typeface",
"android.graphics.drawable.AnimatedVectorDrawable",
"android.graphics.drawable.VectorDrawable",
+ "android.graphics.fonts.Font$Builder",
+ "android.graphics.fonts.FontFamily$Builder",
+ "android.graphics.text.MeasuredText",
+ "android.graphics.text.MeasuredText$Builder",
+ "android.graphics.text.LineBreaker",
"android.os.SystemClock",
"android.os.SystemProperties",
- "android.text.MeasuredParagraph",
- "android.text.StaticLayout",
"android.util.PathParser",
"android.view.Display",
"com.android.internal.util.VirtualRefBasePtr",
@@ -321,7 +342,9 @@
private final static String[] EXCLUDED_CLASSES =
new String[] {
"android.preference.PreferenceActivity",
+ "java.**",
"org.kxml2.io.KXmlParser",
+ "sun.**",
};
/**
@@ -332,8 +355,17 @@
"android.graphics.drawable.VectorDrawable#mVectorState",
"android.view.Choreographer#mLastFrameTimeNanos",
"android.graphics.FontFamily#mBuilderPtr",
+ "android.graphics.Typeface#DEFAULT_FAMILY",
"android.graphics.Typeface#sDynamicTypefaceCache",
"android.graphics.drawable.AdaptiveIconDrawable#sMask",
+ "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",
+ "libcore.util.NativeAllocationRegistry#freeFunction",
+ "libcore.util.NativeAllocationRegistry#size",
};
/**
@@ -341,6 +373,8 @@
* if possible.
*/
private final static String[] PROMOTED_CLASSES = new String[] {
+ "libcore.util.NativeAllocationRegistry$CleanerRunner",
+ "libcore.util.NativeAllocationRegistry$CleanerThunk",
};
/**
diff --git a/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
index eca1c07..42d5727 100644
--- a/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
+++ b/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -77,7 +77,7 @@
*/
String[] getJavaPkgClasses();
- Set<String> getExcludedClasses();
+ String[] getExcludedClasses();
/**
* Returns a list of fields which should be promoted to public visibility. The array values
diff --git a/create/src/com/android/tools/layoutlib/create/JarUtil.java b/create/src/com/android/tools/layoutlib/create/JarUtil.java
new file mode 100644
index 0000000..2836642
--- /dev/null
+++ b/create/src/com/android/tools/layoutlib/create/JarUtil.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.NotNull;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Function;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+public class JarUtil {
+ private JarUtil() {
+ }
+
+ /**
+ * Writes the JAR file.
+ *
+ * @param outStream The file output stream were to write the JAR.
+ * @param all The map of all classes to output.
+ * @param transform Transform to apply to the class files
+ *
+ * @throws IOException if an I/O error has occurred
+ */
+ public static void createJar(@NotNull FileOutputStream outStream,
+ @NotNull Map<String, byte[]> all, @NotNull Function<byte[], byte[]> transform)
+ throws IOException {
+ JarOutputStream jar = new JarOutputStream(outStream);
+ for (Entry<String, byte[]> entry : all.entrySet()) {
+ String name = entry.getKey();
+ JarEntry jar_entry = new JarEntry(name);
+ jar.putNextEntry(jar_entry);
+ if (name.endsWith(".class")) {
+ jar.write(transform.apply(entry.getValue()));
+ } else {
+ // This is just a file,
+ jar.write(entry.getValue());
+ }
+ jar.closeEntry();
+ }
+ jar.flush();
+ jar.close();
+ }
+
+ /**
+ * Writes the JAR file.
+ *
+ * @param outStream The file output stream were to write the JAR.
+ * @param all The map of all classes to output.
+ *
+ * @throws IOException if an I/O error has occurred
+ */
+ public static void createJar(@NotNull FileOutputStream outStream,
+ @NotNull Map<String, byte[]> all) throws IOException {
+ createJar(outStream, all, Function.identity());
+ }
+}
diff --git a/create/src/com/android/tools/layoutlib/create/Main.java b/create/src/com/android/tools/layoutlib/create/Main.java
index 74eed83..4636a1c 100644
--- a/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/create/src/com/android/tools/layoutlib/create/Main.java
@@ -16,13 +16,22 @@
package com.android.tools.layoutlib.create;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
+import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.google.common.io.Files;
/**
@@ -49,14 +58,15 @@
*/
public class Main {
- public static class Options {
- public boolean listAllDeps = false;
- public boolean listOnlyMissingDeps = false;
+ private static class Options {
+ private boolean listAllDeps = false;
+ private boolean listOnlyMissingDeps = false;
+ private boolean createStubLib = false;
}
public static final int ASM_VERSION = Opcodes.ASM6;
- public static final Options sOptions = new Options();
+ private static final Options sOptions = new Options();
public static void main(String[] args) {
@@ -66,7 +76,7 @@
String[] osDestJar = { null };
if (!processArgs(log, args, osJarPath, osDestJar)) {
- log.error("Usage: layoutlib_create [-v] output.jar input.jar ...");
+ log.error("Usage: layoutlib_create [-v] [--create-stub] output.jar input.jar ...");
log.error("Usage: layoutlib_create [-v] [--list-deps|--missing-deps] input.jar ...");
System.exit(1);
}
@@ -90,10 +100,9 @@
try {
CreateInfo info = new CreateInfo();
- Set<String> excludeClasses = info.getExcludedClasses();
- AsmGenerator agen = new AsmGenerator(log, osDestJar, info);
+ AsmGenerator agen = new AsmGenerator(log, info);
- AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
+ AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath,
new String[] { // derived from
"android.view.View",
"android.app.Fragment"
@@ -126,13 +135,33 @@
"com.android.internal.transition.EpicenterTranslateClipReveal",
"com.android.internal.graphics.drawable.AnimationScaleListDrawable",
},
- excludeClasses,
+ info.getExcludedClasses(),
new String[] {
"com/android/i18n/phonenumbers/data/*",
"android/icu/impl/data/**"
});
- aa.analyze();
- agen.generate();
+ agen.setAnalysisResult(aa.analyze());
+
+ Map<String, byte[]> outputClasses = agen.generate();
+ JarUtil.createJar(new FileOutputStream(osDestJar), outputClasses);
+ log.info("Created JAR file %s", osDestJar);
+
+ if (sOptions.createStubLib) {
+ File osDestJarFile = new File(osDestJar);
+ String extension = Files.getFileExtension(osDestJarFile.getName());
+ if (!extension.isEmpty()) {
+ extension = '.' + extension;
+ }
+ String stubDestJarFile = osDestJarFile.getParent() + File.separatorChar +
+ Files.getNameWithoutExtension(osDestJarFile.getName()) + "-stubs" +
+ extension;
+
+ Map<String, byte[]> toStubClasses = outputClasses.entrySet().stream().filter(entry -> entry.getKey().startsWith("android/")).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+ JarUtil.createJar(new FileOutputStream(stubDestJarFile), toStubClasses,
+ input -> StubClassAdapter.stubClass(log, input));
+ log.info("Created stub JAR file %s", stubDestJarFile);
+ }
+
// Throw an error if any class failed to get renamed by the generator
//
@@ -159,8 +188,6 @@
return 0;
} catch (IOException e) {
log.exception(e, "Failed to load jar");
- } catch (LogAbortException e) {
- e.error(log);
}
return 1;
@@ -201,6 +228,8 @@
} else if (s.equals("--missing-deps")) {
sOptions.listOnlyMissingDeps = true;
needs_dest = false;
+ } else if (s.equals("--create-stub")) {
+ sOptions.createStubLib = true;
} else if (!s.startsWith("-")) {
if (needs_dest && osDestJar[0] == null) {
osDestJar[0] = s;
diff --git a/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index bf94415..2967b05 100644
--- a/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -73,33 +73,10 @@
}
});
- // Case 2: java.util.Locale.toLanguageTag() and java.util.Locale.getScript()
- METHOD_REPLACERS.add(new MethodReplacer() {
-
- private final String LOCALE_TO_STRING =
- Type.getMethodDescriptor(STRING, Type.getType(Locale.class));
-
- @Override
- public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
- return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) &&
- ("toLanguageTag".equals(name) || "getScript".equals(name));
- }
-
- @Override
- public void replace(MethodInformation mi) {
- mi.opcode = Opcodes.INVOKESTATIC;
- mi.owner = ANDROID_LOCALE_CLASS;
- mi.desc = LOCALE_TO_STRING;
- }
- });
-
- // Case 3: java.util.Locale.adjustLanguageCode() or java.util.Locale.forLanguageTag() or
- // java.util.Locale.getDefault()
+ // Case 2: java.util.Locale.adjustLanguageCode() or java.util.Locale.getDefault()
METHOD_REPLACERS.add(new MethodReplacer() {
private final String STRING_TO_STRING = Type.getMethodDescriptor(STRING, STRING);
- private final String STRING_TO_LOCALE = Type.getMethodDescriptor(
- Type.getType(Locale.class), STRING);
private final String VOID_TO_LOCALE =
Type.getMethodDescriptor(Type.getType(Locale.class));
@@ -107,7 +84,6 @@
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
return JAVA_LOCALE_CLASS.equals(owner) &&
("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) ||
- "forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE) ||
"getDefault".equals(name) && desc.equals(VOID_TO_LOCALE));
}
@@ -117,7 +93,7 @@
}
});
- // Case 4: java.lang.System.log?()
+ // Case 3: java.lang.System.log?()
METHOD_REPLACERS.add(new MethodReplacer() {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
@@ -134,7 +110,7 @@
}
});
- // Case 5: java.lang.System time calls
+ // Case 4: java.lang.System time calls
METHOD_REPLACERS.add(new MethodReplacer() {
@Override
public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
@@ -160,7 +136,7 @@
}
});
- // Case 6: java.util.LinkedHashMap.eldest()
+ // Case 5: java.util.LinkedHashMap.eldest()
METHOD_REPLACERS.add(new MethodReplacer() {
private final String VOID_TO_MAP_ENTRY =
@@ -183,7 +159,7 @@
}
});
- // Case 7: android.content.Context.getClassLoader() in LayoutInflater
+ // Case 6: 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/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/create/src/com/android/tools/layoutlib/create/StubCallMethodAdapter.java
similarity index 98%
rename from create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
rename to create/src/com/android/tools/layoutlib/create/StubCallMethodAdapter.java
index 4ba7237..f4cfc1d 100644
--- a/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
+++ b/create/src/com/android/tools/layoutlib/create/StubCallMethodAdapter.java
@@ -27,7 +27,7 @@
* This method adapter rewrites a method by discarding the original code and generating
* a stub depending on the return type. Original annotations are passed along unchanged.
*/
-class StubMethodAdapter extends MethodVisitor {
+class StubCallMethodAdapter extends MethodVisitor {
private static final String CONSTRUCTOR = "<init>";
private static final String CLASS_INIT = "<clinit>";
@@ -48,7 +48,7 @@
private final boolean mIsStatic;
private final boolean mIsNative;
- public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
+ public StubCallMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
String invokeSignature, boolean isStatic, boolean isNative) {
super(Main.ASM_VERSION);
mParentVisitor = mv;
diff --git a/create/src/com/android/tools/layoutlib/create/StubClassAdapter.java b/create/src/com/android/tools/layoutlib/create/StubClassAdapter.java
new file mode 100644
index 0000000..e1d2df2
--- /dev/null
+++ b/create/src/com/android/tools/layoutlib/create/StubClassAdapter.java
@@ -0,0 +1,292 @@
+/*
+ * 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.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+import com.android.tools.layoutlib.annotations.VisibleForTesting;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Class adapter that can stub some or all of the methods of the class.
+ */
+class StubClassAdapter extends ClassVisitor {
+ public interface MethodVisitorFactory {
+ @NotNull
+ MethodVisitor create(@NotNull MethodVisitor mv,
+ @NotNull String methodName,
+ @NotNull Type returnType,
+ @NotNull String invokeSignature,
+ boolean isStatic, boolean isNative);
+ }
+
+ public static class Builder {
+ private Log mLogger;
+ private Set<String> mDeleteReturns;
+ private String mClassName;
+ private ClassVisitor mCv;
+ private boolean mStubNativesOnly;
+ private boolean mRemoveStaticInitializers;
+ private boolean mRemovePrivates;
+ private MethodVisitorFactory mMethodVisitorFactory;
+
+ private Builder(@NotNull Log log, @NotNull ClassVisitor classVisitor) {
+ mLogger = log;
+ mCv = classVisitor;
+ }
+
+ @NotNull
+ public Builder withDeleteReturns(@Nullable Set<String> deleteReturns) {
+ mDeleteReturns = deleteReturns;
+ return this;
+ }
+
+ @NotNull
+ public Builder withNewClassName(@Nullable String className) {
+ mClassName = className;
+ return this;
+ }
+
+ public Builder useOnlyStubNative(boolean stubNativesOnly) {
+ mStubNativesOnly = stubNativesOnly;
+ return this;
+ }
+
+ @NotNull
+ public Builder withMethodVisitorFactory(@Nullable MethodVisitorFactory factory) {
+ mMethodVisitorFactory = factory;
+ return this;
+ }
+
+ @NotNull
+ public Builder removePrivates() {
+ mRemovePrivates = true;
+ return this;
+ }
+
+ @NotNull
+ public Builder removeStaticInitializers() {
+ mRemoveStaticInitializers = true;
+ return this;
+ }
+
+ public StubClassAdapter build() {
+ return new StubClassAdapter(mLogger,
+ mDeleteReturns != null ? mDeleteReturns : Collections.emptySet(),
+ mClassName,
+ mCv,
+ mMethodVisitorFactory != null ? mMethodVisitorFactory : StubCallMethodAdapter::new,
+ mStubNativesOnly,
+ mRemovePrivates,
+ mRemoveStaticInitializers);
+ }
+ }
+
+ /** True if all methods should be stubbed, false if only native ones must be stubbed. */
+ private final boolean mStubAll;
+ /** True if the class is an interface. */
+ private boolean mIsInterface;
+ private final String mClassName;
+ private final Log mLog;
+ private final Set<String> mDeleteReturns;
+ private final MethodVisitorFactory mMethodVisitorFactory;
+ private final boolean mRemovePrivates;
+ private final boolean mRemoveStaticInitalizers;
+
+
+ @NotNull
+ public static StubClassAdapter.Builder builder(@NotNull Log log,
+ @NotNull ClassVisitor classVisitor) {
+ return new Builder(log, classVisitor);
+ }
+ /**
+ * Creates a new class adapter that will stub some or all methods.
+ * @param deleteReturns list of types that trigger the deletion of methods returning them.
+ * @param className Optional new name for the class being modified
+ * @param cv The parent class writer visitor
+ * @param stubNativesOnly True if only native methods should be stubbed. False if all
+ * @param removePrivates If true, all private methods and fields will be removed
+ * @param removeStaticInitializers If true, static initializers will be removed
+ */
+ private StubClassAdapter(@NotNull Log logger,
+ @NotNull Set<String> deleteReturns, @Nullable String className,
+ @NotNull ClassVisitor cv, @NotNull MethodVisitorFactory methodVisitorFactory,
+ boolean stubNativesOnly, boolean removePrivates, boolean removeStaticInitializers) {
+ super(Main.ASM_VERSION, cv);
+ mLog = logger;
+ mClassName = className;
+ mStubAll = !stubNativesOnly;
+ mIsInterface = false;
+ mDeleteReturns = deleteReturns;
+ mMethodVisitorFactory = methodVisitorFactory;
+ mRemovePrivates = removePrivates;
+ mRemoveStaticInitalizers = removeStaticInitializers;
+ }
+
+ /**
+ * Utility method that receives a class in serialized form and returns the stubbed version.
+ */
+ @VisibleForTesting
+ @NotNull
+ static byte[] stubClass(@NotNull Log log, @NotNull byte[] bytes,
+ @Nullable String newName) {
+ ClassReader classReader = new ClassReader(bytes);
+ ClassWriter classWriter = new ClassWriter(0);
+
+ // We replace every method with a Stub exception throw and remove all private
+ // methods and static initializers since we only care about the interfaces.
+ ClassVisitor cv = StubClassAdapter.builder(log, classWriter)
+ .withNewClassName(newName)
+ .withMethodVisitorFactory(StubExceptionMethodAdapter::new)
+ .removePrivates()
+ .removeStaticInitializers()
+ .build();
+
+ classReader.accept(cv, 0);
+
+ return classWriter.toByteArray();
+ }
+
+ /**
+ * Utility method that receives a class in serialized form and returns the stubbed version.
+ */
+ @NotNull
+ public static byte[] stubClass(@NotNull Log log, @NotNull byte[] bytes) {
+ return stubClass(log, bytes, null);
+ }
+
+ /* Visits the class header. */
+ @Override
+ public void visit(int version, int access, String name,
+ String signature, String superName, String[] interfaces) {
+
+ if (mClassName != null) {
+ // This class might be being renamed.
+ name = mClassName;
+ }
+
+ // remove final
+ access = access & ~Opcodes.ACC_FINAL;
+ // note: leave abstract classes as such
+ // don't try to implement stub for interfaces
+
+ mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ /* Visits the header of an inner class. */
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ // remove final
+ access = access & ~Opcodes.ACC_FINAL;
+ // note: leave abstract classes as such
+ // don't try to implement stub for interfaces
+
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+
+ /* Visits a method. */
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ if (mRemovePrivates && (access & Opcodes.ACC_PRIVATE) != 0) {
+ return null;
+ }
+
+ if (mRemoveStaticInitalizers && "<clinit>".equals(name)) {
+ return null;
+ }
+
+ if (mDeleteReturns != null) {
+ Type t = Type.getReturnType(desc);
+ if (t.getSort() == Type.OBJECT) {
+ String returnType = t.getInternalName();
+ if (returnType != null) {
+ if (mDeleteReturns.contains(returnType)) {
+ return null;
+ }
+ }
+ }
+ }
+
+ String methodSignature = mClassName != null ?
+ mClassName.replace('/', '.') + "#" + name :
+ signature;
+
+ // remove final
+ access = access & ~Opcodes.ACC_FINAL;
+
+ // stub this method if they are all to be stubbed or if it is a native method
+ // and don't try to stub interfaces nor abstract non-native methods.
+ if (!mIsInterface &&
+ ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != Opcodes.ACC_ABSTRACT) &&
+ (mStubAll ||
+ (access & Opcodes.ACC_NATIVE) != 0)) {
+
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+
+ // remove abstract, final and native
+ access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
+
+ String invokeSignature = methodSignature + desc;
+ mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
+
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ return mMethodVisitorFactory.create(mw, name, returnType(desc), invokeSignature,
+ isStatic, isNative);
+
+ } else {
+ mLog.debug(" Keep: %s %s", name, desc);
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ if (mRemovePrivates && (access & Opcodes.ACC_PRIVATE) != 0) {
+ return null;
+ }
+
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ /**
+ * Extracts the return {@link Type} of this descriptor.
+ */
+ private static Type returnType(String desc) {
+ if (desc != null) {
+ try {
+ return Type.getReturnType(desc);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // ignore, not a valid type.
+ }
+ }
+ return null;
+ }
+}
diff --git a/create/src/com/android/tools/layoutlib/create/StubExceptionMethodAdapter.java b/create/src/com/android/tools/layoutlib/create/StubExceptionMethodAdapter.java
new file mode 100644
index 0000000..73a57a2
--- /dev/null
+++ b/create/src/com/android/tools/layoutlib/create/StubExceptionMethodAdapter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 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.NotNull;
+
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+
+/**
+ * {@link MethodVisitor} that replaces the method the implementation of the method with
+ * <code>
+ * throw new RuntimeException("Stub!");
+ * </code>
+ */
+public class StubExceptionMethodAdapter extends MethodVisitor {
+ private final MethodVisitor mParentVisitor;
+
+ public StubExceptionMethodAdapter(@NotNull MethodVisitor mv, @NotNull String methodName,
+ @NotNull Type returnType, @NotNull String invokeSignature, boolean isStatic,
+ boolean isNative) {
+ super(Main.ASM_VERSION, null);
+
+ mParentVisitor = mv;
+ }
+
+ @Override
+ public void visitCode() {
+ mParentVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException");
+ mParentVisitor.visitInsn(Opcodes.DUP);
+ mParentVisitor.visitLdcInsn("Stub!");
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException",
+ "<init>", "(Ljava" + "/lang/String;)V", false);
+ mParentVisitor.visitInsn(Opcodes.ATHROW);
+ mParentVisitor.visitMaxs(3, 1);
+ mParentVisitor.visitEnd();
+
+ }
+}
diff --git a/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
deleted file mode 100644
index a28ae69..0000000
--- a/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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 org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-import java.util.Set;
-
-/**
- * Class adapter that can stub some or all of the methods of the class.
- */
-class TransformClassAdapter extends ClassVisitor {
-
- /** True if all methods should be stubbed, false if only native ones must be stubbed. */
- private final boolean mStubAll;
- /** True if the class is an interface. */
- private boolean mIsInterface;
- private final String mClassName;
- private final Log mLog;
- private final Set<String> mStubMethods;
- private Set<String> mDeleteReturns;
-
- /**
- * Creates a new class adapter that will stub some or all methods.
- * @param stubMethods list of method signatures to always stub out
- * @param deleteReturns list of types that trigger the deletion of methods returning them.
- * @param className The name of the class being modified
- * @param cv The parent class writer visitor
- * @param stubNativesOnly True if only native methods should be stubbed. False if all
- * methods should be stubbed.
- */
- public TransformClassAdapter(Log logger, Set<String> stubMethods,
- Set<String> deleteReturns, String className, ClassVisitor cv,
- boolean stubNativesOnly) {
- super(Main.ASM_VERSION, cv);
- mLog = logger;
- mStubMethods = stubMethods;
- mClassName = className;
- mStubAll = !stubNativesOnly;
- mIsInterface = false;
- mDeleteReturns = deleteReturns;
- }
-
- /* Visits the class header. */
- @Override
- public void visit(int version, int access, String name,
- String signature, String superName, String[] interfaces) {
-
- // This class might be being renamed.
- name = mClassName;
-
- // remove final
- access = access & ~Opcodes.ACC_FINAL;
- // note: leave abstract classes as such
- // don't try to implement stub for interfaces
-
- mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
- super.visit(version, access, name, signature, superName, interfaces);
- }
-
- /* Visits the header of an inner class. */
- @Override
- public void visitInnerClass(String name, String outerName, String innerName, int access) {
- // remove final
- access = access & ~Opcodes.ACC_FINAL;
- // note: leave abstract classes as such
- // don't try to implement stub for interfaces
-
- super.visitInnerClass(name, outerName, innerName, access);
- }
-
- /* Visits a method. */
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc,
- String signature, String[] exceptions) {
-
- if (mDeleteReturns != null) {
- Type t = Type.getReturnType(desc);
- if (t.getSort() == Type.OBJECT) {
- String returnType = t.getInternalName();
- if (returnType != null) {
- if (mDeleteReturns.contains(returnType)) {
- return null;
- }
- }
- }
- }
-
- String methodSignature = mClassName.replace('/', '.') + "#" + name;
-
- // remove final
- access = access & ~Opcodes.ACC_FINAL;
-
- // stub this method if they are all to be stubbed or if it is a native method
- // and don't try to stub interfaces nor abstract non-native methods.
- if (!mIsInterface &&
- ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != Opcodes.ACC_ABSTRACT) &&
- (mStubAll ||
- (access & Opcodes.ACC_NATIVE) != 0) ||
- mStubMethods.contains(methodSignature)) {
-
- boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
- boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
-
- // remove abstract, final and native
- access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
-
- String invokeSignature = methodSignature + desc;
- mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
-
- MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
- return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
- isStatic, isNative);
-
- } else {
- mLog.debug(" Keep: %s %s", name, desc);
- return super.visitMethod(access, name, desc, signature, exceptions);
- }
- }
-
- /**
- * Extracts the return {@link Type} of this descriptor.
- */
- Type returnType(String desc) {
- if (desc != null) {
- try {
- return Type.getReturnType(desc);
- } catch (ArrayIndexOutOfBoundsException e) {
- // ignore, not a valid type.
- }
- }
- return null;
- }
-}
diff --git a/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index f86917a..21fc212 100644
--- a/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -18,8 +18,8 @@
package com.android.tools.layoutlib.create;
import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
+import com.android.tools.layoutlib.create.AsmAnalyzer.Result;
-import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
@@ -28,8 +28,8 @@
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.TreeMap;
import static org.junit.Assert.assertArrayEquals;
@@ -40,45 +40,50 @@
* Unit tests for some methods of {@link AsmAnalyzer}.
*/
public class AsmAnalyzerTest {
+ private static final List<String> MOCK_ANDROID_JAR;
+ private static final String[] DEFAULT_EXCLUDES = new String[]{"notjava.lang.JavaClass"};
+ private static final String[] DEFAULT_INCLUDE_FILES = new String[]{"mock_android/data/data*"};
- private MockLog mLog;
- private ArrayList<String> mOsJarPath;
- private AsmAnalyzer mAa;
+ static {
+ List<String> mockJar = new ArrayList<>();
+ URL url = AsmAnalyzerTest.class.getClassLoader().getResource("data/mock_android.jar");
+ assert url != null : "Unable to locate mock_android.jar";
+ mockJar.add(url.getFile());
+ MOCK_ANDROID_JAR = Collections.unmodifiableList(mockJar);
+ }
- @Before
- public void setUp() throws Exception {
- mLog = new MockLog();
- URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-
- mOsJarPath = new ArrayList<>();
- //noinspection ConstantConditions
- mOsJarPath.add(url.getFile());
-
- Set<String> excludeClasses = Collections.singleton("java.lang.JavaClass");
-
- String[] includeFiles = new String[]{"mock_android/data/data*"};
- mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, null /* deriveFrom */,
- null /* includeGlobs */, excludeClasses, includeFiles);
+ private static AsmAnalyzer getDefaultAnalyzer() {
+ MockLog log = new MockLog();
+ return new AsmAnalyzer(log, MOCK_ANDROID_JAR, null ,
+ null /* includeGlobs */, DEFAULT_EXCLUDES, DEFAULT_INCLUDE_FILES);
}
@Test
public void testParseZip() throws IOException {
-
Map<String, ClassReader> map = new TreeMap<>();
Map<String, InputStream> filesFound = new TreeMap<>();
- mAa.parseZip(mOsJarPath, map, filesFound);
+ getDefaultAnalyzer().parseZip(MOCK_ANDROID_JAR, map, filesFound);
assertArrayEquals(new String[] {
- "java.lang.JavaClass",
+ "mock_android.dummy.DummyClass",
"mock_android.dummy.InnerTest",
+ "mock_android.dummy.InnerTest$1",
"mock_android.dummy.InnerTest$DerivingClass",
"mock_android.dummy.InnerTest$MyGenerics1",
"mock_android.dummy.InnerTest$MyIntEnum",
"mock_android.dummy.InnerTest$MyStaticInnerClass",
"mock_android.dummy.InnerTest$NotStaticInner1",
"mock_android.dummy.InnerTest$NotStaticInner2",
+ "mock_android.dummy.subpackage.SubpackageClassA",
+ "mock_android.dummy.subpackage.SubpackageClassB",
+ "mock_android.dummy.subpackage.SubpackageClassC",
+ "mock_android.dummy.subpackage.SubpackageClassC$InnerClass",
+ "mock_android.dummy.subpackage.SubpackageClassC$StaticInnerClass",
+ "mock_android.dummy2.DummyClass",
+ "mock_android.dummy2.keep.DoNotRemove",
"mock_android.util.EmptyArray",
+ "mock_android.util.NotNeeded",
"mock_android.view.View",
"mock_android.view.ViewGroup",
"mock_android.view.ViewGroup$LayoutParams",
@@ -86,7 +91,8 @@
"mock_android.widget.LinearLayout",
"mock_android.widget.LinearLayout$LayoutParams",
"mock_android.widget.TableLayout",
- "mock_android.widget.TableLayout$LayoutParams"
+ "mock_android.widget.TableLayout$LayoutParams",
+ "notjava.lang.JavaClass",
},
map.keySet().toArray());
assertArrayEquals(new String[] {"mock_android/data/dataFile"},
@@ -94,15 +100,14 @@
}
@Test
- public void testFindClass() throws IOException, LogAbortException {
-
+ public void testFindClass() throws IOException {
Map<String, ClassReader> zipClasses = new TreeMap<>();
Map<String, InputStream> filesFound = new TreeMap<>();
- mAa.parseZip(mOsJarPath, zipClasses, filesFound);
+ getDefaultAnalyzer().parseZip(MOCK_ANDROID_JAR, zipClasses, filesFound);
TreeMap<String, ClassReader> found = new TreeMap<>();
- ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
+ ClassReader cr = AsmAnalyzer.findClass("mock_android.view.ViewGroup$LayoutParams",
zipClasses, found);
assertNotNull(cr);
@@ -113,78 +118,46 @@
}
@Test
- public void testFindGlobs() throws IOException, LogAbortException {
-
- Map<String, ClassReader> zipClasses = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
-
- mAa.parseZip(mOsJarPath, zipClasses, filesFound);
- TreeMap<String, ClassReader> found = new TreeMap<>();
-
- // this matches classes, a package match returns nothing
- found.clear();
- mAa.findGlobs("mock_android.view", zipClasses, found);
-
- assertArrayEquals(new String[] { },
- found.keySet().toArray());
-
- // a complex glob search. * is a search pattern that matches names, not dots
- mAa.findGlobs("mock_android.*.*Group$*Layout*", zipClasses, found);
-
+ public void testInclude() throws IOException {
+ AsmAnalyzer analyzer = new AsmAnalyzer(new MockLog(), MOCK_ANDROID_JAR, null,
+ new String[] {
+ "mock_android.util.EmptyArray", // Single class select
+ "mock_android.dummy.**", // Multi package select
+ "mock_android.dummy2.*", // Exclude subpackages select
+ },
+ DEFAULT_EXCLUDES,
+ DEFAULT_INCLUDE_FILES);
+ Result result = analyzer.analyze();
assertArrayEquals(new String[] {
- "mock_android.view.ViewGroup$LayoutParams",
- "mock_android.view.ViewGroup$MarginLayoutParams"
- },
- found.keySet().toArray());
-
- // a complex glob search. ** is a search pattern that matches names including dots
- mAa.findGlobs("mock_android.**Group*", zipClasses, found);
-
- assertArrayEquals(new String[] {
- "mock_android.view.ViewGroup",
- "mock_android.view.ViewGroup$LayoutParams",
- "mock_android.view.ViewGroup$MarginLayoutParams"
- },
- found.keySet().toArray());
-
- // matches a single class
- found.clear();
- mAa.findGlobs("mock_android.view.View", zipClasses, found);
-
- assertArrayEquals(new String[] {
- "mock_android.view.View"
- },
- found.keySet().toArray());
-
- // matches everyting inside the given package but not sub-packages
- found.clear();
- mAa.findGlobs("mock_android.view.*", zipClasses, found);
-
- assertArrayEquals(new String[] {
- "mock_android.view.View",
- "mock_android.view.ViewGroup",
- "mock_android.view.ViewGroup$LayoutParams",
- "mock_android.view.ViewGroup$MarginLayoutParams"
- },
- found.keySet().toArray());
-
- for (String key : found.keySet()) {
- ClassReader value = found.get(key);
- assertNotNull("No value for " + key, value);
- assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
- }
+ "mock_android.dummy.DummyClass",
+ "mock_android.dummy.InnerTest$MyIntEnum",
+ "mock_android.util.EmptyArray",
+ "mock_android.dummy.InnerTest$DerivingClass",
+ "mock_android.dummy2.DummyClass",
+ "mock_android.dummy.subpackage.SubpackageClassC$InnerClass",
+ "mock_android.dummy.InnerTest$MyGenerics1",
+ "mock_android.dummy.subpackage.SubpackageClassC$StaticInnerClass",
+ "mock_android.dummy.InnerTest$MyStaticInnerClass",
+ "mock_android.dummy.InnerTest$NotStaticInner1",
+ "mock_android.dummy.InnerTest$NotStaticInner2",
+ "mock_android.dummy.subpackage.SubpackageClassA",
+ "mock_android.dummy.InnerTest",
+ "mock_android.dummy.InnerTest$1",
+ "mock_android.dummy.subpackage.SubpackageClassC",
+ "mock_android.dummy.subpackage.SubpackageClassB",
+ },
+ result.getFound().keySet().toArray());
}
@Test
- public void testFindClassesDerivingFrom() throws LogAbortException, IOException {
-
+ public void testFindClassesDerivingFrom() throws IOException {
Map<String, ClassReader> zipClasses = new TreeMap<>();
Map<String, InputStream> filesFound = new TreeMap<>();
- mAa.parseZip(mOsJarPath, zipClasses, filesFound);
+ getDefaultAnalyzer().parseZip(MOCK_ANDROID_JAR, zipClasses, filesFound);
TreeMap<String, ClassReader> found = new TreeMap<>();
- mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
+ AsmAnalyzer.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
assertArrayEquals(new String[] {
"mock_android.view.View",
@@ -202,19 +175,18 @@
}
@Test
- public void testDependencyVisitor() throws IOException, LogAbortException {
-
+ public void testDependencyVisitor() throws IOException {
Map<String, ClassReader> zipClasses = new TreeMap<>();
Map<String, InputStream> filesFound = new TreeMap<>();
- mAa.parseZip(mOsJarPath, zipClasses, filesFound);
+ getDefaultAnalyzer().parseZip(MOCK_ANDROID_JAR, zipClasses, filesFound);
TreeMap<String, ClassReader> keep = new TreeMap<>();
TreeMap<String, ClassReader> new_keep = new TreeMap<>();
TreeMap<String, ClassReader> in_deps = new TreeMap<>();
TreeMap<String, ClassReader> out_deps = new TreeMap<>();
- ClassReader cr = mAa.findClass("mock_android.widget.LinearLayout", zipClasses, keep);
- DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
+ ClassReader cr = AsmAnalyzer.findClass("mock_android.widget.LinearLayout", zipClasses, keep);
+ DependencyVisitor visitor = getDefaultAnalyzer().getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
// get first level dependencies
cr.accept(visitor, 0 /* flags */);
@@ -253,6 +225,7 @@
assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
assertArrayEquals(new String[] {
"mock_android.widget.LinearLayout",
+ "notjava.lang.JavaClass",
}, keep.keySet().toArray());
}
}
diff --git a/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index e718fb9..9d7b26d 100644
--- a/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -29,6 +29,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
@@ -37,11 +38,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -61,7 +60,7 @@
private File mTempFile;
// ASM internal name for the the class in java package that should be refactored.
- private static final String JAVA_CLASS_NAME = "java/lang/JavaClass";
+ private static final String JAVA_CLASS_NAME = "notjava.lang.JavaClass";
@Before
public void setUp() throws Exception {
@@ -69,7 +68,6 @@
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
mOsJarPath = new ArrayList<>();
- //noinspection ConstantConditions
mOsJarPath.add(url.getFile());
mTempFile = File.createTempFile("mock", ".jar");
@@ -78,7 +76,7 @@
}
@After
- public void tearDown() throws Exception {
+ public void tearDown() {
if (mTempFile != null) {
//noinspection ResultOfMethodCallIgnored
mTempFile.delete();
@@ -87,7 +85,7 @@
}
@Test
- public void testClassRenaming() throws IOException, LogAbortException {
+ public void testClassRenaming() throws IOException {
ICreateInfo ci = new CreateInfoAdapter() {
@Override
@@ -95,21 +93,21 @@
// classes to rename (so that we can replace them)
return new String[] {
"mock_android.view.View", "mock_android.view._Original_View",
- "not.an.actual.ClassName", "anoter.fake.NewClassName",
+ "not.an.actual.ClassName", "another.fake.NewClassName",
};
}
};
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+ AsmGenerator agen = new AsmGenerator(mLog, ci);
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath,
null, // derived from
new String[] { // include classes
"**"
},
- Collections.emptySet() /* excluded classes */,
+ new String[]{} /* excluded classes */,
new String[]{} /* include files */);
- aa.analyze();
+ agen.setAnalysisResult(aa.analyze());
agen.generate();
Set<String> notRenamed = agen.getClassesNotRenamed();
@@ -118,7 +116,7 @@
}
@Test
- public void testJavaClassRefactoring() throws IOException, LogAbortException {
+ public void testJavaClassRefactoring() throws IOException {
ICreateInfo ci = new CreateInfoAdapter() {
@Override
public Class<?>[] getInjectedClasses() {
@@ -132,34 +130,33 @@
public String[] getJavaPkgClasses() {
// classes to refactor (so that we can replace them)
return new String[] {
- "java.lang.JavaClass", "com.android.tools.layoutlib.create.dataclass.JavaClass",
+ JAVA_CLASS_NAME, "com.android.tools.layoutlib.create.dataclass.JavaClass",
};
}
@Override
- public Set<String> getExcludedClasses() {
- return Collections.singleton("java.lang.JavaClass");
+ public String[] getExcludedClasses() {
+ return new String[]{JAVA_CLASS_NAME};
}
};
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+ AsmGenerator agen = new AsmGenerator(mLog, ci);
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath,
null, // derived from
new String[] { // include classes
"**"
},
- Collections.emptySet(),
+ new String[]{},
new String[] { /* include files */
"mock_android/data/data*"
});
- aa.analyze();
- agen.generate();
- Map<String, ClassReader> output = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
- parseZip(mOsDestJar, output, filesFound);
+ agen.setAnalysisResult(aa.analyze());
+ Map<String, byte[]> output = agen.generate();
RecordingClassVisitor cv = new RecordingClassVisitor();
- for (ClassReader cr: output.values()) {
+ for (Map.Entry<String, byte[]> entry: output.entrySet()) {
+ if (!entry.getKey().endsWith(".class")) continue;
+ ClassReader cr = new ClassReader(entry.getValue());
cr.accept(cv, 0);
}
assertTrue(cv.mVisitedClasses.contains(
@@ -167,11 +164,11 @@
assertFalse(cv.mVisitedClasses.contains(
JAVA_CLASS_NAME));
assertArrayEquals(new String[] {"mock_android/data/dataFile"},
- filesFound.keySet().toArray());
+ findFileNames(output));
}
@Test
- public void testClassRefactoring() throws IOException, LogAbortException {
+ public void testClassRefactoring() throws IOException {
ICreateInfo ci = new CreateInfoAdapter() {
@Override
public Class<?>[] getInjectedClasses() {
@@ -190,21 +187,20 @@
}
};
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+ AsmGenerator agen = new AsmGenerator(mLog, ci);
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath,
null, // derived from
new String[] { // include classes
"**"
},
- Collections.emptySet(),
+ new String[]{},
new String[] {});
- aa.analyze();
- agen.generate();
- Map<String, ClassReader> output = new TreeMap<>();
- parseZip(mOsDestJar, output, new TreeMap<>());
+ agen.setAnalysisResult(aa.analyze());
+ Map<String, byte[]> output = agen.generate();
RecordingClassVisitor cv = new RecordingClassVisitor();
- for (ClassReader cr: output.values()) {
+ for (byte[] classContent: output.values()) {
+ ClassReader cr = new ClassReader(classContent);
cr.accept(cv, 0);
}
assertTrue(cv.mVisitedClasses.contains(
@@ -214,43 +210,53 @@
}
@Test
- public void testClassExclusion() throws IOException, LogAbortException {
+ public void testClassExclusion() throws IOException {
ICreateInfo ci = new CreateInfoAdapter() {
@Override
- public Set<String> getExcludedClasses() {
- Set<String> set = new HashSet<>(2);
- set.add("mock_android.dummy.InnerTest");
- set.add("java.lang.JavaClass");
- return set;
+ public String[] getExcludedClasses() {
+ return new String[] {
+ "mock_android.dummy2.*",
+ "mock_android.dummy.**",
+ "mock_android.util.NotNeeded",
+ JAVA_CLASS_NAME
+ };
}
};
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
- Set<String> excludedClasses = ci.getExcludedClasses();
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ AsmGenerator agen = new AsmGenerator(mLog, ci);
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath,
null, // derived from
new String[] { // include classes
"**"
},
- excludedClasses,
+ ci.getExcludedClasses(),
new String[] { /* include files */
"mock_android/data/data*"
});
- aa.analyze();
- agen.generate();
- Map<String, ClassReader> output = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
- parseZip(mOsDestJar, output, filesFound);
- for (String s : output.keySet()) {
- assertFalse(excludedClasses.contains(s));
- }
+ agen.setAnalysisResult(aa.analyze());
+ Map<String, byte[]> output = agen.generate();
+ // Everything in .dummy.** should be filtered
+ // Only things is .dummy2.* should be filtered
+ assertArrayEquals(new String[] {
+ "mock_android.dummy2.keep.DoNotRemove",
+ "mock_android.util.EmptyArray",
+ "mock_android.view.View",
+ "mock_android.view.ViewGroup",
+ "mock_android.view.ViewGroup$LayoutParams",
+ "mock_android.view.ViewGroup$MarginLayoutParams",
+ "mock_android.widget.LinearLayout",
+ "mock_android.widget.LinearLayout$LayoutParams",
+ "mock_android.widget.TableLayout",
+ "mock_android.widget.TableLayout$LayoutParams"},
+ findClassNames(output)
+ );
assertArrayEquals(new String[] {"mock_android/data/dataFile"},
- filesFound.keySet().toArray());
+ findFileNames(output));
}
@Test
- public void testMethodInjection() throws IOException, LogAbortException,
- ClassNotFoundException, IllegalAccessException, InstantiationException,
+ public void testMethodInjection() throws IOException, ClassNotFoundException,
+ IllegalAccessException, InstantiationException,
NoSuchMethodException, InvocationTargetException {
ICreateInfo ci = new CreateInfoAdapter() {
@Override
@@ -260,8 +266,8 @@
}
};
- AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
- AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ AsmGenerator agen = new AsmGenerator(mLog, ci);
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath,
null, // derived from
new String[] { // include classes
"**"
@@ -270,11 +276,9 @@
new String[] { /* include files */
"mock_android/data/data*"
});
- aa.analyze();
- agen.generate();
- Map<String, ClassReader> output = new TreeMap<>();
- Map<String, InputStream> filesFound = new TreeMap<>();
- parseZip(mOsDestJar, output, filesFound);
+ agen.setAnalysisResult(aa.analyze());
+ JarUtil.createJar(new FileOutputStream(mOsDestJar), agen.generate());
+
final String modifiedClass = "mock_android.util.EmptyArray";
final String modifiedClassPath = modifiedClass.replace('.', '/').concat(".class");
ZipFile zipFile = new ZipFile(mOsDestJar);
@@ -310,27 +314,23 @@
return bos.toByteArray();
}
- private void parseZip(String jarPath,
- Map<String, ClassReader> classes,
- Map<String, InputStream> filesFound) throws IOException {
- ZipFile zip = new ZipFile(jarPath);
- Enumeration<? extends ZipEntry> entries = zip.entries();
- ZipEntry entry;
- while (entries.hasMoreElements()) {
- entry = entries.nextElement();
- if (entry.getName().endsWith(".class")) {
- ClassReader cr = new ClassReader(zip.getInputStream(entry));
- String className = classReaderToClassName(cr);
- classes.put(className, cr);
- } else {
- filesFound.put(entry.getName(), zip.getInputStream(entry));
- }
- }
-
+ private static String[] findClassNames(Map<String, byte[]> content) {
+ return content.entrySet().stream()
+ .filter(entry -> entry.getKey().endsWith(".class"))
+ .map(entry -> classReaderToClassName(new ClassReader(entry.getValue())))
+ .sorted()
+ .toArray(String[]::new);
}
- private String classReaderToClassName(ClassReader classReader) {
+ private static String[] findFileNames(Map<String, byte[]> content) {
+ return content.keySet().stream()
+ .filter(entry -> !entry.endsWith(".class"))
+ .sorted()
+ .toArray(String[]::new);
+ }
+
+ private static String classReaderToClassName(ClassReader classReader) {
if (classReader == null) {
return null;
} else {
diff --git a/create/tests/com/android/tools/layoutlib/create/CreateInfoAdapter.java b/create/tests/com/android/tools/layoutlib/create/CreateInfoAdapter.java
index ad7cb9a..675159a 100644
--- a/create/tests/com/android/tools/layoutlib/create/CreateInfoAdapter.java
+++ b/create/tests/com/android/tools/layoutlib/create/CreateInfoAdapter.java
@@ -59,8 +59,8 @@
}
@Override
- public Set<String> getExcludedClasses() {
- return Collections.emptySet();
+ public String[] getExcludedClasses() {
+ return EMPTY_STRING_ARRAY;
}
@Override
diff --git a/create/tests/com/android/tools/layoutlib/create/StubClassAdapterTest.java b/create/tests/com/android/tools/layoutlib/create/StubClassAdapterTest.java
new file mode 100644
index 0000000..5b3ef20
--- /dev/null
+++ b/create/tests/com/android/tools/layoutlib/create/StubClassAdapterTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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.create.dataclass.StubClass;
+
+import org.junit.Test;
+
+import java.io.IOException;
+
+import com.google.common.io.ByteStreams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class StubClassAdapterTest {
+ private static final String STUB_CLASS_NAME = StubClass.class.getName();
+
+ @Test
+ public void testStubbedClass()
+ throws ClassNotFoundException, IOException, IllegalAccessException,
+ InstantiationException {
+ // Always rename the class to avoid conflict with the original class.
+ byte[] classContent = ByteStreams.toByteArray(ClassLoader.getSystemResourceAsStream(
+ STUB_CLASS_NAME.replace('.', '/') + ".class"));
+
+ String newClassName = STUB_CLASS_NAME + '_';
+ classContent =
+ StubClassAdapter.stubClass(new Log(), classContent, newClassName.replace('.', '/'));
+ TestClassLoader myClassLoader = new TestClassLoader(newClassName, classContent);
+ Class<?> aClass = myClassLoader.loadClass(newClassName);
+ assertTrue("StubClass not loaded by the classloader. Likely a bug in the test.",
+ myClassLoader.wasClassLoaded(newClassName));
+ try {
+ aClass.newInstance();
+ fail("Method should throw a RuntimeException");
+ } catch (RuntimeException e) {
+ assertEquals("Stub!", e.getMessage());
+ }
+ }
+}
diff --git a/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java b/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java
index 3db3e23..fead4a6 100644
--- a/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java
+++ b/create/tests/com/android/tools/layoutlib/create/StubMethodAdapterTest.java
@@ -61,10 +61,10 @@
String newClassName = STUB_CLASS_NAME + '_';
new ClassReader(STUB_CLASS_NAME).accept(
new ClassAdapter(newClassName, writer, methodPredicate), 0);
- MyClassLoader myClassLoader = new MyClassLoader(newClassName, writer.toByteArray());
+ TestClassLoader myClassLoader = new TestClassLoader(newClassName, writer.toByteArray());
Class<?> aClass = myClassLoader.loadClass(newClassName);
assertTrue("StubClass not loaded by the classloader. Likely a bug in the test.",
- myClassLoader.findClassCalled);
+ myClassLoader.wasClassLoaded(newClassName));
Method method = aClass.getMethod(methodName);
Object o = aClass.newInstance();
assertion.accept((Boolean) method.invoke(o));
@@ -103,30 +103,11 @@
if (mMethodPredicate.test(name, descriptor)) {
String methodSignature = mClassName + "#" + name;
String invokeSignature = methodSignature + desc;
- return new StubMethodAdapter(originalMethod, name, descriptor.getReturnType(),
+ return new StubCallMethodAdapter(originalMethod, name, descriptor.getReturnType(),
invokeSignature, isStatic, isNative);
}
return originalMethod;
}
}
- private static class MyClassLoader extends ClassLoader {
- private final String mName;
- private final byte[] mBytes;
- private boolean findClassCalled;
-
- private MyClassLoader(String name, byte[] bytes) {
- mName = name;
- mBytes = bytes;
- }
-
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- if (name.equals(mName)) {
- findClassCalled = true;
- return defineClass(name, mBytes, 0, mBytes.length);
- }
- return super.findClass(name);
- }
- }
}
diff --git a/create/tests/com/android/tools/layoutlib/create/TestClassLoader.java b/create/tests/com/android/tools/layoutlib/create/TestClassLoader.java
new file mode 100644
index 0000000..a869141
--- /dev/null
+++ b/create/tests/com/android/tools/layoutlib/create/TestClassLoader.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 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 java.util.HashSet;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+
+class TestClassLoader extends ClassLoader {
+ private final Map<String, byte[]> mClassDefinitions;
+ private final HashSet<String> mLoadedClasses = new HashSet<>();
+
+ private TestClassLoader(Map<String, byte[]> classDefinitions) {
+ mClassDefinitions = classDefinitions;
+ }
+
+ TestClassLoader(String name, byte[] bytes) {
+ this(ImmutableMap.of(name, bytes));
+ }
+
+ public boolean wasClassLoaded(String name) {
+ return mLoadedClasses.contains(name);
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ byte[] classContent = mClassDefinitions.get(name);
+ if (classContent != null) {
+ mLoadedClasses.add(name);
+ return defineClass(name, classContent, 0, classContent.length);
+ }
+ return super.findClass(name);
+ }
+}
diff --git a/create/tests/data/mock_android.jar b/create/tests/data/mock_android.jar
index c6ca3c4..580e6f1 100644
--- a/create/tests/data/mock_android.jar
+++ b/create/tests/data/mock_android.jar
Binary files differ
diff --git a/create/tests/mock_data/Android.mk b/create/tests/mock_data/Android.mk
new file mode 100644
index 0000000..7a735f3
--- /dev/null
+++ b/create/tests/mock_data/Android.mk
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2018 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under,.)
+LOCAL_JAVA_RESOURCE_DIRS := .
+
+LOCAL_MODULE := mock_android
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
\ No newline at end of file
diff --git a/create/tests/mock_data/java/lang/JavaClass.java b/create/tests/mock_data/mock_android/dummy/DummyClass.java
similarity index 62%
copy from create/tests/mock_data/java/lang/JavaClass.java
copy to create/tests/mock_data/mock_android/dummy/DummyClass.java
index 59612e9..de3b0e0 100644
--- a/create/tests/mock_data/java/lang/JavaClass.java
+++ b/create/tests/mock_data/mock_android/dummy/DummyClass.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * 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.eclipse.org/org/documents/epl-v10.php
+ * 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,
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package java.lang;
+package mock_android.dummy;
-public class JavaClass {
+public class DummyClass {
- public static String test = "test";
-}
+}
\ No newline at end of file
diff --git a/create/tests/mock_data/java/lang/JavaClass.java b/create/tests/mock_data/mock_android/dummy/subpackage/SubpackageClassA.java
similarity index 62%
copy from create/tests/mock_data/java/lang/JavaClass.java
copy to create/tests/mock_data/mock_android/dummy/subpackage/SubpackageClassA.java
index 59612e9..eaf1302 100644
--- a/create/tests/mock_data/java/lang/JavaClass.java
+++ b/create/tests/mock_data/mock_android/dummy/subpackage/SubpackageClassA.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * 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.eclipse.org/org/documents/epl-v10.php
+ * 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,
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package java.lang;
+package mock_android.dummy.subpackage;
-public class JavaClass {
+public class SubpackageClassA {
- public static String test = "test";
-}
+}
\ No newline at end of file
diff --git a/create/tests/mock_data/java/lang/JavaClass.java b/create/tests/mock_data/mock_android/dummy/subpackage/SubpackageClassB.java
similarity index 62%
copy from create/tests/mock_data/java/lang/JavaClass.java
copy to create/tests/mock_data/mock_android/dummy/subpackage/SubpackageClassB.java
index 59612e9..7f67db1 100644
--- a/create/tests/mock_data/java/lang/JavaClass.java
+++ b/create/tests/mock_data/mock_android/dummy/subpackage/SubpackageClassB.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * 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.eclipse.org/org/documents/epl-v10.php
+ * 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,
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package java.lang;
+package mock_android.dummy.subpackage;
-public class JavaClass {
+public class SubpackageClassB {
- public static String test = "test";
-}
+}
\ No newline at end of file
diff --git a/create/tests/mock_data/mock_android/dummy/subpackage/SubpackageClassC.java b/create/tests/mock_data/mock_android/dummy/subpackage/SubpackageClassC.java
new file mode 100644
index 0000000..e456432
--- /dev/null
+++ b/create/tests/mock_data/mock_android/dummy/subpackage/SubpackageClassC.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 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 mock_android.dummy.subpackage;
+
+public class SubpackageClassC {
+ public static class StaticInnerClass {
+
+ }
+
+ public class InnerClass {
+
+ }
+}
\ No newline at end of file
diff --git a/create/tests/mock_data/java/lang/JavaClass.java b/create/tests/mock_data/mock_android/dummy2/DummyClass.java
similarity index 62%
copy from create/tests/mock_data/java/lang/JavaClass.java
copy to create/tests/mock_data/mock_android/dummy2/DummyClass.java
index 59612e9..9f7cfde 100644
--- a/create/tests/mock_data/java/lang/JavaClass.java
+++ b/create/tests/mock_data/mock_android/dummy2/DummyClass.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * 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.eclipse.org/org/documents/epl-v10.php
+ * 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,
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package java.lang;
+package mock_android.dummy2;
-public class JavaClass {
+public class DummyClass {
- public static String test = "test";
-}
+}
\ No newline at end of file
diff --git a/create/tests/mock_data/java/lang/JavaClass.java b/create/tests/mock_data/mock_android/dummy2/keep/DoNotRemove.java
similarity index 62%
copy from create/tests/mock_data/java/lang/JavaClass.java
copy to create/tests/mock_data/mock_android/dummy2/keep/DoNotRemove.java
index 59612e9..8a232a6 100644
--- a/create/tests/mock_data/java/lang/JavaClass.java
+++ b/create/tests/mock_data/mock_android/dummy2/keep/DoNotRemove.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
*
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * 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.eclipse.org/org/documents/epl-v10.php
+ * 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,
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package java.lang;
+package mock_android.dummy2.keep;
-public class JavaClass {
+public class DoNotRemove {
- public static String test = "test";
-}
+}
\ No newline at end of file
diff --git a/create/tests/mock_data/mock_android/util/EmptyArray.java b/create/tests/mock_data/mock_android/util/EmptyArray.java
index aaeebf6..11f8c86 100644
--- a/create/tests/mock_data/mock_android/util/EmptyArray.java
+++ b/create/tests/mock_data/mock_android/util/EmptyArray.java
@@ -16,7 +16,7 @@
package mock_android.util;
-import java.lang.JavaClass;
+import notjava.lang.JavaClass;
public class EmptyArray {
diff --git a/create/tests/mock_data/mock_android/util/NotNeeded.java b/create/tests/mock_data/mock_android/util/NotNeeded.java
new file mode 100644
index 0000000..3239e76
--- /dev/null
+++ b/create/tests/mock_data/mock_android/util/NotNeeded.java
@@ -0,0 +1,4 @@
+package mock_android.util;
+
+public class NotNeeded {
+}
\ No newline at end of file
diff --git a/create/tests/mock_data/mock_android/view/View.java b/create/tests/mock_data/mock_android/view/View.java
index 84ec8a9..7116888 100644
--- a/create/tests/mock_data/mock_android/view/View.java
+++ b/create/tests/mock_data/mock_android/view/View.java
@@ -16,7 +16,7 @@
package mock_android.view;
-import java.lang.JavaClass;
+import notjava.lang.JavaClass;
public class View {
diff --git a/create/tests/mock_data/java/lang/JavaClass.java b/create/tests/mock_data/notjava/lang/JavaClass.java
similarity index 96%
rename from create/tests/mock_data/java/lang/JavaClass.java
rename to create/tests/mock_data/notjava/lang/JavaClass.java
index 59612e9..77cd6aa 100644
--- a/create/tests/mock_data/java/lang/JavaClass.java
+++ b/create/tests/mock_data/notjava/lang/JavaClass.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package java.lang;
+package notjava.lang;
public class JavaClass {
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/RemoteBridgeClient.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/RemoteBridgeClient.java
index 99143ad..8cd61f9 100644
--- a/remote/client/src/com/android/layoutlib/bridge/remote/client/RemoteBridgeClient.java
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/RemoteBridgeClient.java
@@ -83,10 +83,13 @@
}
@Override
- public boolean init(Map<String, String> platformProperties, File fontLocation,
- Map<String, Map<String, Integer>> enumValueMap, LayoutLog log) {
+ public boolean init(Map<String, String> platformProperties,
+ File fontLocation,
+ String icuDataPath,
+ Map<String, Map<String, Integer>> enumValueMap,
+ LayoutLog log) {
try {
- return mDelegate.init(platformProperties, fontLocation, enumValueMap,
+ return mDelegate.init(platformProperties, fontLocation, icuDataPath, enumValueMap,
RemoteLayoutLogAdapter.create(log));
} catch (RemoteException e) {
throw new RuntimeException(e);
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteActionBarCallbackAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteActionBarCallbackAdapter.java
index e1cfe15..9f3da0c 100644
--- a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteActionBarCallbackAdapter.java
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteActionBarCallbackAdapter.java
@@ -18,6 +18,7 @@
import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.layout.remote.api.RemoteActionBarCallback;
import com.android.tools.layoutlib.annotations.NotNull;
@@ -39,8 +40,8 @@
}
@Override
- public List<String> getMenuIdNames() {
- return mDelegate.getMenuIdNames();
+ public List<ResourceReference> getMenuIds() {
+ return mDelegate.getMenuIds();
}
@Override
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteILayoutPullParserAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteILayoutPullParserAdapter.java
index 451d93e..841d714 100644
--- a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteILayoutPullParserAdapter.java
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteILayoutPullParserAdapter.java
@@ -17,6 +17,7 @@
package com.android.layoutlib.bridge.remote.client.adapters;
import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.ResourceNamespace;
import com.android.layout.remote.api.RemoteILayoutPullParser;
import com.android.tools.layoutlib.annotations.NotNull;
@@ -36,7 +37,12 @@
}
@Override
- public Object getViewCookie() throws RemoteException {
+ public Object getViewCookie() {
return ((ILayoutPullParser) mDelegate).getViewCookie();
}
+
+ @Override
+ public ResourceNamespace getLayoutNamespace() {
+ return ((ILayoutPullParser) mDelegate).getLayoutNamespace();
+ }
}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
index b9b1a00..b4236e9 100644
--- a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -17,8 +17,8 @@
package com.android.layoutlib.bridge.remote.client.adapters;
import com.android.ide.common.rendering.api.AdapterBinding;
-import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute;
import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.LayoutlibCallback.ViewAttribute;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams.Key;
@@ -27,10 +27,8 @@
import com.android.layout.remote.api.RemoteLayoutlibCallback;
import com.android.layout.remote.api.RemoteParserFactory;
import com.android.layout.remote.api.RemoteXmlPullParser;
-import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.NotNull;
import com.android.tools.layoutlib.annotations.Nullable;
-import com.android.util.Pair;
import java.net.URISyntaxException;
import java.net.URL;
@@ -58,8 +56,7 @@
}
@Override
- public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
- throws Exception {
+ public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs) {
throw new UnsupportedOperationException("Not implemented yet");
}
@@ -69,20 +66,13 @@
}
@Override
- public RemoteResolveResult resolveResourceId(int id) {
- Pair<ResourceType, String> result = mDelegate.resolveResourceId(id);
- return result != null ? new RemoteResolveResult(result.getFirst(), result.getSecond()) :
- null;
- }
-
- @Override
- public String resolveResourceId(int[] id) {
+ public ResourceReference resolveResourceId(int id) {
return mDelegate.resolveResourceId(id);
}
@Override
- public Integer getResourceId(ResourceType type, String name) {
- return mDelegate.getResourceId(type, name);
+ public int getOrGenerateResourceId(ResourceReference resource) {
+ return mDelegate.getOrGenerateResourceId(resource);
}
@Override
@@ -122,15 +112,6 @@
return mDelegate.getFlag(key);
}
- @Override
- public RemoteParserFactory getParserFactory() {
- try {
- return RemoteParserFactoryAdapter.create(mDelegate.getParserFactory());
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
@Nullable
@Override
public Path findClassPath(String name) {
@@ -148,10 +129,29 @@
return null;
}
+
@Override
- public RemoteXmlPullParser getXmlFileParser(String fileName) {
+ public RemoteXmlPullParser createXmlParserForPsiFile(String fileName) {
try {
- return RemoteXmlPullParserAdapter.create(mDelegate.getXmlFileParser(fileName));
+ return RemoteXmlPullParserAdapter.create(mDelegate.createXmlParserForPsiFile(fileName));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public RemoteXmlPullParser createXmlParserForFile(String fileName) {
+ try {
+ return RemoteXmlPullParserAdapter.create(mDelegate.createXmlParserForFile(fileName));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public RemoteXmlPullParser createXmlParser() {
+ try {
+ return RemoteXmlPullParserAdapter.create(mDelegate.createXmlParser());
} catch (RemoteException e) {
throw new RuntimeException(e);
}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteParserFactoryAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteParserFactoryAdapter.java
deleted file mode 100644
index d1f0411..0000000
--- a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteParserFactoryAdapter.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2017 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.layoutlib.bridge.remote.client.adapters;
-
-import com.android.ide.common.rendering.api.ParserFactory;
-import com.android.layout.remote.api.RemoteParserFactory;
-import com.android.layout.remote.api.RemoteXmlPullParser;
-import com.android.tools.layoutlib.annotations.NotNull;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
-
-public class RemoteParserFactoryAdapter implements RemoteParserFactory {
-
- private final ParserFactory mDelegate;
-
- private RemoteParserFactoryAdapter(@NotNull ParserFactory delegate) {
- mDelegate = delegate;
- }
-
- public static RemoteParserFactory create(@NotNull ParserFactory factory)
- throws RemoteException {
- return (RemoteParserFactory) UnicastRemoteObject.exportObject(
- new RemoteParserFactoryAdapter(factory), 0);
- }
-
- @Override
- public RemoteXmlPullParser createParser(String debugName) throws RemoteException {
- try {
- return RemoteXmlPullParserAdapter.create(mDelegate.createParser(debugName));
- } catch (XmlPullParserException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderParamsAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderParamsAdapter.java
index a3950d7..b9f4e9e 100644
--- a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderParamsAdapter.java
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderParamsAdapter.java
@@ -18,6 +18,7 @@
import com.android.ide.common.rendering.api.IImageFactory;
import com.android.ide.common.rendering.api.RenderParams;
+import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.Key;
import com.android.layout.remote.api.RemoteAssetRepository;
@@ -125,7 +126,7 @@
}
@Override
- public String getAppIcon() {
+ public ResourceValue getAppIcon() {
return mDelegate.getAppIcon();
}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderResourcesAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderResourcesAdapter.java
index 9ae58a9..b477eea 100644
--- a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderResourcesAdapter.java
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderResourcesAdapter.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.layoutlib.bridge.remote.client.adapters;
import com.android.ide.common.rendering.api.RenderResources;
@@ -21,14 +20,17 @@
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.layout.remote.api.RemoteRenderResources;
-import com.android.resources.ResourceType;
+import com.android.layout.remote.api.RemoteResourceValue;
import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;
+import java.util.stream.Collectors;
public class RemoteRenderResourcesAdapter implements RemoteRenderResources {
+
private final RenderResources mDelegate;
private RemoteRenderResourcesAdapter(@NotNull RenderResources delegate) {
@@ -42,13 +44,13 @@
}
@Override
- public StyleResourceValue getDefaultTheme() {
- return mDelegate.getDefaultTheme();
+ public RemoteResourceValue<StyleResourceValue> getDefaultTheme() {
+ return RemoteResourceValue.fromResourceValue(mDelegate.getDefaultTheme());
}
@Override
- public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
- mDelegate.applyStyle(theme, useAsPrimary);
+ public void applyStyle(RemoteResourceValue<StyleResourceValue> theme, boolean useAsPrimary) {
+ mDelegate.applyStyle(theme.toResourceValue(), useAsPrimary);
}
@Override
@@ -57,68 +59,58 @@
}
@Override
- public List<StyleResourceValue> getAllThemes() {
- return mDelegate.getAllThemes();
+ public List<RemoteResourceValue<StyleResourceValue>> getAllThemes() {
+ return mDelegate.getAllThemes().stream().map(
+ RemoteResourceValue::fromResourceValue).collect(Collectors.toList());
}
@Override
- public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
- return mDelegate.getTheme(name, frameworkTheme);
+ @Nullable
+ public RemoteResourceValue<ResourceValue> getResolvedResource(
+ @NotNull ResourceReference reference) {
+ return RemoteResourceValue.fromResourceValue(mDelegate.getResolvedResource(reference));
}
@Override
- public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
- return mDelegate.themeIsParentOf(parentTheme, childTheme);
+ public RemoteResourceValue<ResourceValue> findItemInTheme(ResourceReference attr) {
+ return RemoteResourceValue.fromResourceValue(mDelegate.findItemInTheme(attr));
}
@Override
- public ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName) {
- return mDelegate.getFrameworkResource(resourceType, resourceName);
+ public RemoteResourceValue<ResourceValue> findItemInStyle(
+ RemoteResourceValue<StyleResourceValue> style, ResourceReference attr) {
+ return RemoteResourceValue.fromResourceValue(
+ mDelegate.findItemInStyle(style.toResourceValue(), attr));
}
@Override
- public ResourceValue getProjectResource(ResourceType resourceType, String resourceName) {
- return mDelegate.getProjectResource(resourceType, resourceName);
+ public RemoteResourceValue<ResourceValue> resolveValue(
+ RemoteResourceValue<ResourceValue> value) {
+ return RemoteResourceValue.fromResourceValue(
+ mDelegate.resolveResValue(value.toResourceValue()));
}
@Override
- public ResourceValue findItemInTheme(ResourceReference attr) {
- return mDelegate.findItemInTheme(attr);
+ public RemoteResourceValue<StyleResourceValue> getParent(
+ RemoteResourceValue<StyleResourceValue> style) {
+ return RemoteResourceValue.fromResourceValue(mDelegate.getParent(style.toResourceValue()));
}
@Override
- public ResourceValue findItemInStyle(StyleResourceValue style, ResourceReference attr) {
- return mDelegate.findItemInStyle(style, attr);
+ @Nullable
+ public RemoteResourceValue<StyleResourceValue> getStyle(@NotNull ResourceReference reference) {
+ return RemoteResourceValue.fromResourceValue(mDelegate.getStyle(reference));
}
@Override
- public ResourceValue resolveValue(ResourceValue value) {
- return mDelegate.resolveResValue(value);
+ public RemoteResourceValue<ResourceValue> dereference(
+ RemoteResourceValue<ResourceValue> resourceValue) {
+ return RemoteResourceValue.fromResourceValue(
+ mDelegate.dereference(resourceValue.toResourceValue()));
}
@Override
- public ResourceValue resolveValue(ResourceType type, String name, String value,
- boolean isFrameworkValue) {
- return mDelegate.resolveValue(type, name, value, isFrameworkValue);
- }
-
- @Override
- public StyleResourceValue getParent(StyleResourceValue style) {
- return mDelegate.getParent(style);
- }
-
- @Override
- public StyleResourceValue getStyle(String styleName, boolean isFramework) {
- return mDelegate.getStyle(styleName, isFramework);
- }
-
- @Override
- public ResourceValue dereference(ResourceValue resourceValue) {
- return mDelegate.dereference(resourceValue);
- }
-
- @Override
- public ResourceValue getUnresolvedResource(ResourceReference reference) {
- return mDelegate.getUnresolvedResource(reference);
+ public RemoteResourceValue<ResourceValue> getUnresolvedResource(ResourceReference reference) {
+ return RemoteResourceValue.fromResourceValue(mDelegate.getUnresolvedResource(reference));
}
}
diff --git a/remote/common/remote common.iml b/remote/common/remote common.iml
index e56c17c..1562b6d 100644
--- a/remote/common/remote common.iml
+++ b/remote/common/remote common.iml
@@ -20,5 +20,6 @@
</orderEntry>
<orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
<orderEntry type="module" module-name="common" />
+ <orderEntry type="library" name="framework.jar" level="project" />
</component>
</module>
\ No newline at end of file
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteActionBarCallback.java b/remote/common/src/com/android/layout/remote/api/RemoteActionBarCallback.java
index 153a575..988fa98 100644
--- a/remote/common/src/com/android/layout/remote/api/RemoteActionBarCallback.java
+++ b/remote/common/src/com/android/layout/remote/api/RemoteActionBarCallback.java
@@ -13,35 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.android.layout.remote.api;
import com.android.ide.common.rendering.api.ActionBarCallback;
import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
+import com.android.ide.common.rendering.api.ResourceReference;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.List;
+import android.annotation.NonNull;
+
/**
- * Remote version of the {@link ActionBarCallback} class
+ * Remote version of the {@link ActionBarCallback} class.
*/
public interface RemoteActionBarCallback extends Remote {
-
- List<String> getMenuIdNames() throws RemoteException;
-
+ @NonNull
+ List<ResourceReference> getMenuIds() throws RemoteException;
boolean getSplitActionBarWhenNarrow() throws RemoteException;
-
int getNavigationMode() throws RemoteException;
-
String getSubTitle() throws RemoteException;
-
HomeButtonStyle getHomeButtonStyle() throws RemoteException;
-
boolean isOverflowPopupNeeded() throws RemoteException;
}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteBridge.java b/remote/common/src/com/android/layout/remote/api/RemoteBridge.java
index 8188198..74a02b3 100644
--- a/remote/common/src/com/android/layout/remote/api/RemoteBridge.java
+++ b/remote/common/src/com/android/layout/remote/api/RemoteBridge.java
@@ -62,6 +62,7 @@
*
* @param platformProperties The build properties for the platform.
* @param fontLocation the location of the fonts.
+ * @param icuDataPath the location of the ICU data used natively.
* @param enumValueMap map attrName ⇒ { map enumFlagName ⇒ Integer value }. This is typically
* read from attrs.xml in the SDK target.
* @param log a {@link LayoutLog} object. Can be null.
@@ -69,8 +70,8 @@
* @return true if success.
*/
boolean init(@NotNull Map<String, String> platformProperties, File fontLocation,
- @NotNull Map<String, Map<String, Integer>> enumValueMap, @Nullable RemoteLayoutLog log)
- throws RemoteException;
+ String icuDataPath, @NotNull Map<String, Map<String, Integer>> enumValueMap,
+ @Nullable RemoteLayoutLog log) throws RemoteException;
/**
* Prepares the layoutlib to be unloaded.
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java b/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java
index 585535b..9f8ef8b 100644
--- a/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java
+++ b/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java
@@ -17,6 +17,7 @@
package com.android.layout.remote.api;
import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.ResourceNamespace;
import java.rmi.RemoteException;
@@ -25,4 +26,6 @@
*/
public interface RemoteILayoutPullParser extends RemoteXmlPullParser {
Object getViewCookie() throws RemoteException;
+
+ ResourceNamespace getLayoutNamespace() throws RemoteException;
}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteLayoutlibCallback.java b/remote/common/src/com/android/layout/remote/api/RemoteLayoutlibCallback.java
index 0f315ca..94e5185 100644
--- a/remote/common/src/com/android/layout/remote/api/RemoteLayoutlibCallback.java
+++ b/remote/common/src/com/android/layout/remote/api/RemoteLayoutlibCallback.java
@@ -17,15 +17,14 @@
package com.android.layout.remote.api;
import com.android.ide.common.rendering.api.AdapterBinding;
-import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute;
import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.LayoutlibCallback.ViewAttribute;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams.Key;
-import com.android.resources.ResourceType;
-import com.android.util.Pair;
-import java.io.Serializable;
+import org.xmlpull.v1.XmlPullParser;
+
import java.nio.file.Path;
import java.rmi.Remote;
import java.rmi.RemoteException;
@@ -37,15 +36,13 @@
boolean supports(int ideFeature) throws RemoteException;
Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
- throws Exception, RemoteException;
+ throws Exception;
String getNamespace() throws RemoteException;
- RemoteResolveResult resolveResourceId(int id) throws RemoteException;
+ ResourceReference resolveResourceId(int id) throws RemoteException;
- String resolveResourceId(int[] id) throws RemoteException;
-
- Integer getResourceId(ResourceType type, String name) throws RemoteException;
+ int getOrGenerateResourceId(ResourceReference resource) throws RemoteException;
RemoteILayoutPullParser getParser(ResourceValue layoutResource) throws RemoteException;
@@ -61,23 +58,11 @@
<T> T getFlag(Key<T> key) throws RemoteException;
- RemoteParserFactory getParserFactory() throws RemoteException;
-
Path findClassPath(String name) throws RemoteException;
- RemoteXmlPullParser getXmlFileParser(String fileName) throws RemoteException;
+ RemoteXmlPullParser createXmlParserForPsiFile(String fileName) throws RemoteException;
- class RemoteResolveResult implements Serializable {
- private ResourceType type;
- private String value;
+ RemoteXmlPullParser createXmlParserForFile(String fileName) throws RemoteException;
- public RemoteResolveResult(ResourceType type, String value) {
- this.type = type;
- this.value = value;
- }
-
- public Pair<ResourceType, String> asPair() {
- return Pair.of(type, value);
- }
- }
+ RemoteXmlPullParser createXmlParser() throws RemoteException;
}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteNamespaceResolver.java b/remote/common/src/com/android/layout/remote/api/RemoteNamespaceResolver.java
new file mode 100644
index 0000000..2adf016
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteNamespaceResolver.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 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.layout.remote.api;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.io.Serializable;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+public interface RemoteNamespaceResolver extends Remote {
+ static final RemoteNamespaceResolver EMPTY_RESOLVER = new EmptyResolver();
+
+ /** Returns the full URI of an XML namespace for a given prefix, if defined. */
+ @Nullable
+ String remotePrefixToUri(@NotNull String namespacePrefix) throws RemoteException;
+
+ @Nullable
+ String remoteUriToPrefix(@NotNull String namespaceUri) throws RemoteException;
+
+ class EmptyResolver implements Serializable, RemoteNamespaceResolver {
+ private EmptyResolver() {
+ }
+
+ @Override
+ public String remotePrefixToUri(String namespacePrefix) {
+ return null;
+ }
+
+ @Override
+ public String remoteUriToPrefix(String namespaceUri) {
+ return null;
+ }
+ }
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java b/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java
index 31a35b2..4f187d1 100644
--- a/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java
+++ b/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java
@@ -16,7 +16,6 @@
package com.android.layout.remote.api;
-import com.android.ide.common.rendering.api.ParserFactory;
import com.android.tools.layoutlib.annotations.Nullable;
import java.rmi.Remote;
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java b/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java
index da2bd8e..5dff1fb 100644
--- a/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java
+++ b/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java
@@ -17,6 +17,7 @@
package com.android.layout.remote.api;
import com.android.ide.common.rendering.api.IImageFactory;
+import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams.Key;
import com.android.tools.layoutlib.annotations.Nullable;
@@ -49,7 +50,7 @@
IImageFactory getImageFactory() throws RemoteException;
- String getAppIcon() throws RemoteException;
+ ResourceValue getAppIcon() throws RemoteException;
String getAppLabel() throws RemoteException;
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteRenderResources.java b/remote/common/src/com/android/layout/remote/api/RemoteRenderResources.java
index f54d44d..530b19e 100644
--- a/remote/common/src/com/android/layout/remote/api/RemoteRenderResources.java
+++ b/remote/common/src/com/android/layout/remote/api/RemoteRenderResources.java
@@ -20,7 +20,8 @@
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
-import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
import java.rmi.Remote;
import java.rmi.RemoteException;
@@ -30,43 +31,31 @@
* Remote version of the {@link RenderResources} class
*/
public interface RemoteRenderResources extends Remote {
- StyleResourceValue getDefaultTheme() throws RemoteException;
+ RemoteResourceValue<StyleResourceValue> getDefaultTheme() throws RemoteException;
- void applyStyle(StyleResourceValue theme, boolean useAsPrimary) throws RemoteException;
+ void applyStyle(RemoteResourceValue<StyleResourceValue> theme, boolean useAsPrimary) throws RemoteException;
void clearStyles() throws RemoteException;
- List<StyleResourceValue> getAllThemes() throws RemoteException;
+ List<RemoteResourceValue<StyleResourceValue>> getAllThemes() throws RemoteException;
+ @Nullable
+ RemoteResourceValue<ResourceValue> getResolvedResource(@NotNull ResourceReference reference) throws RemoteException;
- StyleResourceValue getTheme(String name, boolean frameworkTheme) throws RemoteException;
+ RemoteResourceValue<ResourceValue> findItemInTheme(ResourceReference attr) throws RemoteException;
-
- boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme)
+ RemoteResourceValue<ResourceValue> findItemInStyle(RemoteResourceValue<StyleResourceValue> style,
+ ResourceReference attr)
throws RemoteException;
- ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName)
- throws RemoteException;
+ RemoteResourceValue<ResourceValue> resolveValue(RemoteResourceValue<ResourceValue> value) throws RemoteException;
- ResourceValue getProjectResource(ResourceType resourceType, String resourceName)
- throws RemoteException;
+ RemoteResourceValue<StyleResourceValue> getParent(RemoteResourceValue<StyleResourceValue> style) throws RemoteException;
+ @Nullable
+ RemoteResourceValue<StyleResourceValue> getStyle(@NotNull ResourceReference reference) throws RemoteException;
- ResourceValue findItemInTheme(ResourceReference attr) throws RemoteException;
+ RemoteResourceValue<ResourceValue> dereference(RemoteResourceValue<ResourceValue> resourceValue) throws RemoteException;
- ResourceValue findItemInStyle(StyleResourceValue style, ResourceReference attr)
- throws RemoteException;
-
- ResourceValue resolveValue(ResourceValue value) throws RemoteException;
-
- ResourceValue resolveValue(ResourceType type, String name, String value,
- boolean isFrameworkValue) throws RemoteException;
-
- StyleResourceValue getParent(StyleResourceValue style) throws RemoteException;
-
- StyleResourceValue getStyle(String styleName, boolean isFramework) throws RemoteException;
-
- ResourceValue dereference(ResourceValue resourceValue) throws RemoteException;
-
- ResourceValue getUnresolvedResource(ResourceReference reference) throws RemoteException;
+ RemoteResourceValue<ResourceValue> getUnresolvedResource(ResourceReference reference) throws RemoteException;
}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteResourceValue.java b/remote/common/src/com/android/layout/remote/api/RemoteResourceValue.java
new file mode 100644
index 0000000..fc19e4f
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteResourceValue.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.ResourceNamespace.Resolver;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.layout.remote.util.RemoteResolverAdapter;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.io.Serializable;
+import java.rmi.RemoteException;
+
+/**
+ * Wrapper for {@link ResourceValue} that can be transferred to a different VM.
+ *
+ * @param <T> the ResourceValue instance type
+ */
+public class RemoteResourceValue<T extends ResourceValue> implements Serializable {
+ private static final RemoteResourceValue<ResourceValue> NULL_INSTANCE =
+ new RemoteResourceValue<>(null, null);
+
+ private final T mResourceValue;
+ private final RemoteNamespaceResolver mRemoteResolver;
+
+ private RemoteResourceValue(@Nullable T resourceValue,
+ @Nullable RemoteNamespaceResolver remoteResolver) {
+ mResourceValue = resourceValue;
+ mRemoteResolver = remoteResolver;
+ }
+
+ /**
+ * Returns a RemoteResourceValue that wraps the given {@link ResourceValue} instance. The passed
+ * resource value can be null.
+ */
+ @NotNull
+ public static <T extends ResourceValue> RemoteResourceValue<T> fromResourceValue(
+ T resourceValue) {
+ if (resourceValue == null) {
+ //noinspection unchecked
+ return (RemoteResourceValue<T>) NULL_INSTANCE;
+ }
+ try {
+ return new RemoteResourceValue<>(resourceValue,
+ RemoteResolverAdapter.create(resourceValue.getNamespaceResolver()));
+
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Returns the {@link ResourceValue} wrapped by a remote wrapper
+ */
+ @NotNull
+ private static <T extends ResourceValue> T toResourceValue(
+ @NotNull RemoteResourceValue<T> remoteResourceValue) {
+ T remoteValue = remoteResourceValue.mResourceValue;
+ if (remoteValue == null) {
+ return null;
+ }
+
+ // The Resolver is not transferred in the ResourceValue (it's transient) so we use the
+ // information in the wrapper to reconstruct it.
+ RemoteNamespaceResolver remoteResolver = remoteResourceValue.mRemoteResolver;
+
+// TODO: Rethink this as setNamespaceResolver is not available in prebuilts any more
+// if (remoteResolver != null) {
+// remoteValue.setNamespaceResolver(new Resolver() {
+// @Override
+// public String prefixToUri(String namespacePrefix) {
+// try {
+// return remoteResolver.remotePrefixToUri(namespacePrefix);
+// } catch (RemoteException e) {
+// throw new RuntimeException(e);
+// }
+// }
+//
+// @Override
+// public String uriToPrefix(String namespaceUri) {
+// try {
+// return remoteResolver.remoteUriToPrefix(namespaceUri);
+// } catch (RemoteException e) {
+// throw new RuntimeException(e);
+// }
+// }
+// });
+// }
+// else {
+// remoteValue.setNamespaceResolver(Resolver.EMPTY_RESOLVER);
+// }
+
+ return remoteValue;
+ }
+
+ /**
+ * Returns the {@link ResourceValue} wrapped by this remote wrapper
+ */
+ @NotNull
+ public T toResourceValue() {
+ return RemoteResourceValue.toResourceValue(this);
+ }
+}
diff --git a/remote/common/src/com/android/layout/remote/util/RemoteResolverAdapter.java b/remote/common/src/com/android/layout/remote/util/RemoteResolverAdapter.java
new file mode 100644
index 0000000..f575b95
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/RemoteResolverAdapter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 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.layout.remote.util;
+
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceNamespace.Resolver;
+import com.android.layout.remote.api.RemoteNamespaceResolver;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteResolverAdapter implements RemoteNamespaceResolver {
+ private final Resolver mDelegate;
+
+ private RemoteResolverAdapter(Resolver delegate) {
+ mDelegate = delegate;
+ }
+
+ public static RemoteNamespaceResolver create(@NotNull ResourceNamespace.Resolver delegate)
+ throws RemoteException {
+ assert !(delegate instanceof RemoteResolverAdapter);
+
+ return (RemoteNamespaceResolver) UnicastRemoteObject.exportObject(
+ new RemoteResolverAdapter(delegate), 0);
+ }
+
+ @Override
+ public String remotePrefixToUri(String namespacePrefix) {
+ return mDelegate.prefixToUri(namespacePrefix);
+ }
+
+ @Override
+ public String remoteUriToPrefix(String namespaceUri) {
+ return mDelegate.uriToPrefix(namespaceUri);
+ }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/RemoteBridgeImpl.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/RemoteBridgeImpl.java
index 2593afc..0013218 100644
--- a/remote/server/src/com/android/layoutlib/bridge/remote/server/RemoteBridgeImpl.java
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/RemoteBridgeImpl.java
@@ -74,9 +74,9 @@
}
@Override
- public boolean init(Map<String, String> platformProperties, File fontLocation,
+ public boolean init(Map<String, String> platformProperties, File fontLocation, String icuDataPath,
Map<String, Map<String, Integer>> enumValueMap, RemoteLayoutLog log) {
- return mBridge.init(platformProperties, fontLocation, enumValueMap,
+ return mBridge.init(platformProperties, fontLocation, icuDataPath, enumValueMap,
log != null ? new RemoteLayoutLogAdapter(log) : null);
}
@@ -111,7 +111,7 @@
String projectKey = mCachedProjectKeys.putIfAbsent(remoteParams.getProjectKey(),
remoteParams.getProjectKey());
- // Unpack the remote params and convert it into the local SessionParams
+ // Unpack the remote params and convert it into the local SessionParams.
SessionParams params = new SessionParams(
new RemoteILayoutPullParserAdapter(remoteParams.getLayoutDescription()),
remoteParams.getRenderingMode(), projectKey,
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/ServerMain.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/ServerMain.java
index 09c27fd..4c23afc 100644
--- a/remote/server/src/com/android/layoutlib/bridge/remote/server/ServerMain.java
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/ServerMain.java
@@ -21,6 +21,7 @@
import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
@@ -36,6 +37,7 @@
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -65,6 +67,16 @@
}
}
+ private static Thread createOutputProcessor(String outputProcessorName,
+ InputStream inputStream,
+ Consumer<String> consumer) {
+ BufferedReader inputReader = new BufferedReader(new InputStreamReader(inputStream));
+ Thread thread = new Thread(() -> inputReader.lines().forEach(consumer));
+ thread.setName(outputProcessorName);
+ thread.start();
+ return thread;
+ }
+
/**
* This will start a new JVM and connect to the new JVM RMI registry.
* <p/>
@@ -110,30 +122,31 @@
.start();
BlockingQueue<String> outputQueue = new ArrayBlockingQueue<>(10);
- Thread outputThread = new Thread(() -> {
- BufferedReader inputStream = new BufferedReader(
- new InputStreamReader(process.getInputStream()));
- inputStream.lines()
- .forEach(outputQueue::offer);
-
- });
- outputThread.setName("output thread");
- outputThread.start();
+ Thread outputThread = createOutputProcessor("output", process.getInputStream(),
+ outputQueue::offer);
+ Thread errorThread = createOutputProcessor("error", process.getErrorStream(),
+ System.err::println);
Runnable killServer = () -> {
process.destroyForcibly();
outputThread.interrupt();
+ errorThread.interrupt();
try {
outputThread.join();
} catch (InterruptedException ignore) {
}
+
+ try {
+ errorThread.join();
+ } catch (InterruptedException ignore) {
+ }
};
// Try to read the "Running on port" line in 10 lines. If it's not there just fail.
for (int i = 0; i < 10; i++) {
- String line = outputQueue.poll(1000, TimeUnit.SECONDS);
+ String line = outputQueue.poll(5, TimeUnit.SECONDS);
- if (line.startsWith(RUNNING_SERVER_STR)) {
+ if (line != null && line.startsWith(RUNNING_SERVER_STR)) {
int runningPort = Integer.parseInt(line.substring(RUNNING_SERVER_STR.length()));
System.out.println("Running on port " + runningPort);
@@ -182,6 +195,14 @@
throw lastException;
}
+ /**
+ * Starts an RMI server that runs in the current JVM. Only for debugging.
+ */
+ public static ServerMain startLocalJvmServer() throws RemoteException {
+ System.err.println("Starting server in the local JVM");
+ return startServer(REGISTRY_BASE_PORT, 10);
+ }
+
public static void main(String[] args) throws RemoteException {
int basePort = args.length > 0 ? Integer.parseInt(args[0]) : REGISTRY_BASE_PORT;
int limit = args.length > 1 ? Integer.parseInt(args[1]) : 10;
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteActionBarCallbackAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteActionBarCallbackAdapter.java
index b5c040e..d87a94f 100644
--- a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteActionBarCallbackAdapter.java
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteActionBarCallbackAdapter.java
@@ -17,6 +17,7 @@
package com.android.layoutlib.bridge.remote.server.adapters;
import com.android.ide.common.rendering.api.ActionBarCallback;
+import com.android.ide.common.rendering.api.ResourceReference;
import com.android.layout.remote.api.RemoteActionBarCallback;
import com.android.tools.layoutlib.annotations.NotNull;
@@ -31,9 +32,9 @@
}
@Override
- public List<String> getMenuIdNames() {
+ public List<ResourceReference> getMenuIds() {
try {
- return mDelegate.getMenuIdNames();
+ return mDelegate.getMenuIds();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteILayoutPullParserAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteILayoutPullParserAdapter.java
index b717feb..3ca0176 100644
--- a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteILayoutPullParserAdapter.java
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteILayoutPullParserAdapter.java
@@ -17,6 +17,7 @@
package com.android.layoutlib.bridge.remote.server.adapters;
import com.android.ide.common.rendering.api.ILayoutPullParser;
+import com.android.ide.common.rendering.api.ResourceNamespace;
import com.android.layout.remote.api.RemoteILayoutPullParser;
import com.android.tools.layoutlib.annotations.NotNull;
@@ -38,9 +39,11 @@
}
@Override
- public ILayoutPullParser getParser(String layoutName) {
- throw new UnsupportedOperationException();
+ public ResourceNamespace getLayoutNamespace() {
+ try {
+ return ((RemoteILayoutPullParser) mDelegate).getLayoutNamespace();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
}
-
-
}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
index b685098..a6885ef 100644
--- a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -20,17 +20,13 @@
import com.android.ide.common.rendering.api.AdapterBinding;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutlibCallback;
-import com.android.ide.common.rendering.api.ParserFactory;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams.Key;
import com.android.layout.remote.api.RemoteLayoutlibCallback;
-import com.android.layout.remote.api.RemoteLayoutlibCallback.RemoteResolveResult;
import com.android.layoutlib.bridge.MockView;
-import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.NotNull;
import com.android.tools.layoutlib.annotations.Nullable;
-import com.android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
@@ -170,17 +166,7 @@
}
@Override
- public Pair<ResourceType, String> resolveResourceId(int id) {
- try {
- RemoteResolveResult result = mDelegate.resolveResourceId(id);
- return result != null ? result.asPair() : null;
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public String resolveResourceId(int[] id) {
+ public ResourceReference resolveResourceId(int id) {
try {
return mDelegate.resolveResourceId(id);
} catch (RemoteException e) {
@@ -189,20 +175,15 @@
}
@Override
- public Integer getResourceId(ResourceType type, String name) {
+ public int getOrGenerateResourceId(ResourceReference resource) {
try {
- return mDelegate.getResourceId(type, name);
+ return mDelegate.getOrGenerateResourceId(resource);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
@Override
- public ILayoutPullParser getParser(String layoutName) {
- return null;
- }
-
- @Override
public ILayoutPullParser getParser(ResourceValue layoutResource) {
try {
return new RemoteILayoutPullParserAdapter(mDelegate.getParser(layoutResource));
@@ -255,23 +236,32 @@
}
@Override
- public ParserFactory getParserFactory() {
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ return mPathClassLoader.loadClass(name);
+ }
+
+ @Override
+ public XmlPullParser createXmlParserForPsiFile(String fileName) {
try {
- return new RemoteParserFactoryAdapter(mDelegate.getParserFactory());
+ return new RemoteXmlPullParserAdapter(mDelegate.createXmlParserForPsiFile(fileName));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
@Override
- public Class<?> findClass(String name) throws ClassNotFoundException {
- return mPathClassLoader.loadClass(name);
+ public XmlPullParser createXmlParserForFile(String fileName) {
+ try {
+ return new RemoteXmlPullParserAdapter(mDelegate.createXmlParserForFile(fileName));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
}
@Override
- public XmlPullParser getXmlFileParser(String fileName) {
+ public XmlPullParser createXmlParser() {
try {
- return new RemoteXmlPullParserAdapter(mDelegate.getXmlFileParser(fileName));
+ return new RemoteXmlPullParserAdapter(mDelegate.createXmlParser());
} catch (RemoteException e) {
throw new RuntimeException(e);
}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteParserFactoryAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteParserFactoryAdapter.java
deleted file mode 100644
index f4ce421..0000000
--- a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteParserFactoryAdapter.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2017 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.layoutlib.bridge.remote.server.adapters;
-
-
-import com.android.ide.common.rendering.api.ParserFactory;
-import com.android.layout.remote.api.RemoteParserFactory;
-import com.android.tools.layoutlib.annotations.NotNull;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.rmi.RemoteException;
-
-class RemoteParserFactoryAdapter extends ParserFactory {
- private final RemoteParserFactory mDelegate;
-
- RemoteParserFactoryAdapter(@NotNull RemoteParserFactory remote) {
- mDelegate = remote;
- }
-
- @Override
- public XmlPullParser createParser(String debugName) throws XmlPullParserException {
- try {
- return new RemoteXmlPullParserAdapter(mDelegate.createParser(debugName));
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderResourcesAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderResourcesAdapter.java
index 3f261df..2e19bf7 100644
--- a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderResourcesAdapter.java
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderResourcesAdapter.java
@@ -22,11 +22,12 @@
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.layout.remote.api.RemoteRenderResources;
-import com.android.resources.ResourceType;
+import com.android.layout.remote.api.RemoteResourceValue;
import com.android.tools.layoutlib.annotations.NotNull;
import java.rmi.RemoteException;
import java.util.List;
+import java.util.stream.Collectors;
public class RemoteRenderResourcesAdapter extends RenderResources {
private final RemoteRenderResources mDelegate;
@@ -36,25 +37,14 @@
}
@Override
- public void setFrameworkResourceIdProvider(FrameworkResourceIdProvider provider) {
- // Ignored for remote operations.
- }
-
- @Override
public void setLogger(LayoutLog logger) {
// Ignored for remote operations.
}
- @SuppressWarnings("deprecation")
- @Override
- public StyleResourceValue getCurrentTheme() {
- throw new UnsupportedOperationException();
- }
-
@Override
public StyleResourceValue getDefaultTheme() {
try {
- return mDelegate.getDefaultTheme();
+ return mDelegate.getDefaultTheme().toResourceValue();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -63,7 +53,7 @@
@Override
public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
try {
- mDelegate.applyStyle(theme, useAsPrimary);
+ mDelegate.applyStyle(RemoteResourceValue.fromResourceValue(theme), useAsPrimary);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -81,43 +71,9 @@
@Override
public List<StyleResourceValue> getAllThemes() {
try {
- return mDelegate.getAllThemes();
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
- try {
- return mDelegate.getTheme(name, frameworkTheme);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
- try {
- return mDelegate.themeIsParentOf(parentTheme, childTheme);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName) {
- try {
- return mDelegate.getFrameworkResource(resourceType, resourceName);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public ResourceValue getProjectResource(ResourceType resourceType, String resourceName) {
- try {
- return mDelegate.getProjectResource(resourceType, resourceName);
+ return mDelegate.getAllThemes().stream()
+ .map(RemoteResourceValue::toResourceValue)
+ .collect(Collectors.toList());
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -126,7 +82,7 @@
@Override
public ResourceValue findItemInTheme(ResourceReference attr) {
try {
- return mDelegate.findItemInTheme(attr);
+ return mDelegate.findItemInTheme(attr).toResourceValue();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -135,7 +91,8 @@
@Override
public ResourceValue findItemInStyle(StyleResourceValue style, ResourceReference attr) {
try {
- return mDelegate.findItemInStyle(style, attr);
+ return mDelegate.findItemInStyle(RemoteResourceValue.fromResourceValue(style), attr)
+ .toResourceValue();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -144,7 +101,8 @@
@Override
public ResourceValue dereference(ResourceValue resourceValue) {
try {
- return mDelegate.dereference(resourceValue);
+ return mDelegate.dereference(RemoteResourceValue.fromResourceValue(resourceValue))
+ .toResourceValue();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -154,18 +112,7 @@
@Override
public ResourceValue getUnresolvedResource(ResourceReference reference) {
try {
- return mDelegate.getUnresolvedResource(reference);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public ResourceValue resolveValue(ResourceType type, String name, String value,
- boolean isFrameworkValue) {
- try {
- return mDelegate.resolveValue(type, name, value, isFrameworkValue);
+ return mDelegate.getUnresolvedResource(reference).toResourceValue();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -174,7 +121,8 @@
@Override
public ResourceValue resolveResValue(ResourceValue value) {
try {
- return mDelegate.resolveValue(value);
+ return mDelegate.resolveValue(RemoteResourceValue.fromResourceValue(value))
+ .toResourceValue();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -183,10 +131,19 @@
@Override
public StyleResourceValue getParent(StyleResourceValue style) {
try {
- return mDelegate.getParent(style);
+ return mDelegate.getParent(RemoteResourceValue.fromResourceValue(style))
+ .toResourceValue();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
+ @Override
+ public StyleResourceValue getStyle(ResourceReference reference) {
+ try {
+ return mDelegate.getStyle(reference).toResourceValue();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/remote/tests/out/failures/activity.png b/remote/tests/out/failures/activity.png
deleted file mode 100644
index 2920b7d..0000000
--- a/remote/tests/out/failures/activity.png
+++ /dev/null
Binary files differ
diff --git a/remote/tests/out/failures/delta-activity.png b/remote/tests/out/failures/delta-activity.png
deleted file mode 100644
index 32d9415..0000000
--- a/remote/tests/out/failures/delta-activity.png
+++ /dev/null
Binary files differ
diff --git a/remote/tests/out/failures/delta-remote_component_load.png b/remote/tests/out/failures/delta-remote_component_load.png
deleted file mode 100644
index ddb7f64..0000000
--- a/remote/tests/out/failures/delta-remote_component_load.png
+++ /dev/null
Binary files differ
diff --git a/remote/tests/out/failures/remote_component_load.png b/remote/tests/out/failures/remote_component_load.png
deleted file mode 100644
index 0ed85d1..0000000
--- a/remote/tests/out/failures/remote_component_load.png
+++ /dev/null
Binary files differ
diff --git a/remote/tests/src/RemoteBridgeTest.java b/remote/tests/src/RemoteBridgeTest.java
index e0b0610..6ad10cb 100644
--- a/remote/tests/src/RemoteBridgeTest.java
+++ b/remote/tests/src/RemoteBridgeTest.java
@@ -63,8 +63,11 @@
@Before
public void setupServer() throws IOException, NotBoundException, InterruptedException {
+ long startTime = System.currentTimeMillis();
mServerMain = ServerMain.forkAndStartServer(ServerMain.REGISTRY_BASE_PORT, 10);
mClient = RemoteBridgeClient.getRemoteBridge(mServerMain.getPort());
+ System.out.printf("Server started in %dms\n", System.currentTimeMillis() - startTime);
+ startTime = System.currentTimeMillis();
File data_dir = new File(PLATFORM_DIR, "data");
File res = new File(data_dir, "res");
@@ -72,8 +75,10 @@
File buildProp = new File(PLATFORM_DIR, "build.prop");
File attrs = new File(res, "values" + File.separator + "attrs.xml");
- mClient.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
+ mClient.init(ConfigGenerator.loadProperties(buildProp), fontLocation, null,
ConfigGenerator.getEnumMap(attrs), getLayoutLog());
+ System.out.printf("Remote client init took %dms\n",
+ System.currentTimeMillis() - startTime);
}
@After