Merge Android Pie into master
Bug: 112104996
Change-Id: I91f57f766e04b216f4c4c3ad48fb0e7a852fd7f1
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..b879e0e
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,76 @@
+<component name="ProjectCodeStyleConfiguration">
+ <code_scheme name="Project" version="173">
+ <option name="RIGHT_MARGIN" value="100" />
+ <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
+ <option name="WRAP_COMMENTS" value="true" />
+ <JavaCodeStyleSettings>
+ <option name="FIELD_NAME_PREFIX" value="m" />
+ <option name="STATIC_FIELD_NAME_PREFIX" value="s" />
+ <option name="CLASS_NAMES_IN_JAVADOC" value="3" />
+ <option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
+ <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
+ <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
+ <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
+ <value />
+ </option>
+ <option name="IMPORT_LAYOUT_TABLE">
+ <value>
+ <package name="com.android" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="org" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="android" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="java" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="" withSubpackages="true" static="false" />
+ <emptyLine />
+ <package name="" withSubpackages="true" static="true" />
+ </value>
+ </option>
+ <option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
+ <option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" />
+ <option name="JD_ADD_BLANK_AFTER_RETURN" value="true" />
+ <option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" />
+ </JavaCodeStyleSettings>
+ <XML>
+ <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
+ </XML>
+ <codeStyleSettings language="JAVA">
+ <option name="KEEP_LINE_BREAKS" value="false" />
+ <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+ <option name="CALL_PARAMETERS_WRAP" value="1" />
+ <option name="METHOD_PARAMETERS_WRAP" value="1" />
+ <option name="THROWS_LIST_WRAP" value="1" />
+ <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+ <option name="THROWS_KEYWORD_WRAP" value="1" />
+ <option name="BINARY_OPERATION_WRAP" value="1" />
+ <option name="TERNARY_OPERATION_WRAP" value="1" />
+ <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+ <option name="ASSIGNMENT_WRAP" value="1" />
+ <option name="ASSERT_STATEMENT_WRAP" value="1" />
+ <option name="IF_BRACE_FORCE" value="3" />
+ <option name="DOWHILE_BRACE_FORCE" value="3" />
+ <option name="WHILE_BRACE_FORCE" value="3" />
+ <option name="FOR_BRACE_FORCE" value="3" />
+ <option name="WRAP_LONG_LINES" value="true" />
+ <arrangement>
+ <groups>
+ <group>
+ <type>GETTERS_AND_SETTERS</type>
+ <order>KEEP</order>
+ </group>
+ <group>
+ <type>OVERRIDDEN_METHODS</type>
+ <order>KEEP</order>
+ </group>
+ </groups>
+ </arrangement>
+ </codeStyleSettings>
+ <codeStyleSettings language="XML">
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="4" />
+ </indentOptions>
+ </codeStyleSettings>
+ </code_scheme>
+</component>
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+ <state>
+ <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+ </state>
+</component>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 4654a5c..8d61f6c 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -6,6 +6,10 @@
<module fileurl="file://$PROJECT_DIR$/common/common.iml" filepath="$PROJECT_DIR$/common/common.iml" />
<module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" />
<module fileurl="file://$PROJECT_DIR$/legacy/legacy.iml" filepath="$PROJECT_DIR$/legacy/legacy.iml" />
+ <module fileurl="file://$PROJECT_DIR$/remote/client/remote client.iml" filepath="$PROJECT_DIR$/remote/client/remote client.iml" group="remote" />
+ <module fileurl="file://$PROJECT_DIR$/remote/common/remote common.iml" filepath="$PROJECT_DIR$/remote/common/remote common.iml" group="remote" />
+ <module fileurl="file://$PROJECT_DIR$/remote/server/remote server.iml" filepath="$PROJECT_DIR$/remote/server/remote server.iml" group="remote" />
+ <module fileurl="file://$PROJECT_DIR$/remote/tests/remote tests.iml" filepath="$PROJECT_DIR$/remote/tests/remote tests.iml" group="remote" />
<module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" />
</modules>
</component>
diff --git a/Android.mk b/Android.mk
index a9f09f4..e9aa138 100644
--- a/Android.mk
+++ b/Android.mk
@@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+ifeq (,$(PRODUCT_MINIMIZE_JAVA_DEBUG_INFO))
+
LOCAL_PATH := $(my-dir)
include $(CLEAR_VARS)
@@ -88,3 +90,5 @@
# Include the subdir makefiles.
#
include $(call all-makefiles-under,$(LOCAL_PATH))
+
+endif
diff --git a/bridge/.idea/bridge.iml b/bridge/.idea/bridge.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/bridge/.idea/bridge.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/bridge/.idea/compiler.xml b/bridge/.idea/compiler.xml
new file mode 100644
index 0000000..40ed937
--- /dev/null
+++ b/bridge/.idea/compiler.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CompilerConfiguration">
+ <wildcardResourcePatterns>
+ <entry name="!?*.java" />
+ <entry name="!?*.form" />
+ <entry name="!?*.class" />
+ <entry name="!?*.groovy" />
+ <entry name="!?*.scala" />
+ <entry name="!?*.flex" />
+ <entry name="!?*.kt" />
+ <entry name="!?*.clj" />
+ </wildcardResourcePatterns>
+ </component>
+</project>
\ No newline at end of file
diff --git a/bridge/.idea/encodings.xml b/bridge/.idea/encodings.xml
new file mode 100644
index 0000000..97626ba
--- /dev/null
+++ b/bridge/.idea/encodings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding">
+ <file url="PROJECT" charset="UTF-8" />
+ </component>
+</project>
\ No newline at end of file
diff --git a/bridge/.idea/misc.xml b/bridge/.idea/misc.xml
new file mode 100644
index 0000000..54f4c9f
--- /dev/null
+++ b/bridge/.idea/misc.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="MavenImportPreferences">
+ <option name="generalSettings">
+ <MavenGeneralSettings>
+ <option name="mavenHome" value="Bundled (Maven 3)" />
+ </MavenGeneralSettings>
+ </option>
+ </component>
+ <component name="ProjectDictionaryState">
+ <dictionary name="diegoperez" />
+ </component>
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" />
+</project>
\ No newline at end of file
diff --git a/bridge/.idea/modules.xml b/bridge/.idea/modules.xml
new file mode 100644
index 0000000..4683164
--- /dev/null
+++ b/bridge/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/bridge.iml" filepath="$PROJECT_DIR$/.idea/bridge.iml" />
+ </modules>
+ </component>
+</project>
\ No newline at end of file
diff --git a/bridge/.idea/workspace.xml b/bridge/.idea/workspace.xml
new file mode 100644
index 0000000..b48406a
--- /dev/null
+++ b/bridge/.idea/workspace.xml
@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ChangeListManager">
+ <list default="true" id="8a92e766-359b-4589-8d1f-ac1a44fb0c89" name="Default" comment="" />
+ <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
+ <option name="TRACKING_ENABLED" value="true" />
+ <option name="SHOW_DIALOG" value="false" />
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+ <option name="LAST_RESOLUTION" value="IGNORE" />
+ </component>
+ <component name="FileEditorManager">
+ <leaf />
+ </component>
+ <component name="GradleLocalSettings">
+ <option name="modificationStamps">
+ <map>
+ <entry key="$PROJECT_DIR$/../../../../nyc-dev/tools/base/layoutlib-api" value="1486045798848" />
+ </map>
+ </option>
+ <option name="externalProjectsViewState">
+ <projects_view />
+ </option>
+ </component>
+ <component name="ProjectFrameBounds">
+ <option name="x" value="2560" />
+ <option name="y" value="18" />
+ <option name="width" value="3840" />
+ <option name="height" value="2123" />
+ </component>
+ <component name="ProjectInspectionProfilesVisibleTreeState">
+ <entry key="Project Default">
+ <profile-state>
+ <expanded-state>
+ <State>
+ <id />
+ </State>
+ <State>
+ <id>Android</id>
+ </State>
+ <State>
+ <id>Android Lint</id>
+ </State>
+ </expanded-state>
+ <selected-state>
+ <State>
+ <id>Android</id>
+ </State>
+ </selected-state>
+ </profile-state>
+ </entry>
+ </component>
+ <component name="ProjectView">
+ <navigator currentView="ProjectPane" proportions="" version="1">
+ <flattenPackages />
+ <showMembers />
+ <showModules />
+ <showLibraryContents />
+ <hideEmptyPackages />
+ <abbreviatePackageNames />
+ <autoscrollToSource />
+ <autoscrollFromSource />
+ <sortByType />
+ <manualOrder />
+ <foldersAlwaysOnTop value="true" />
+ </navigator>
+ <panes>
+ <pane id="PackagesPane" />
+ <pane id="Scratches" />
+ <pane id="Scope" />
+ <pane id="ProjectPane">
+ <subPane>
+ <expand>
+ <path>
+ <item name="bridge" type="b2602c69:ProjectViewProjectNode" />
+ <item name="bridge" type="462c0819:PsiDirectoryNode" />
+ </path>
+ </expand>
+ <select />
+ </subPane>
+ </pane>
+ </panes>
+ </component>
+ <component name="PropertiesComponent">
+ <property name="GoToClass.includeLibraries" value="false" />
+ <property name="GoToClass.toSaveIncludeLibraries" value="false" />
+ <property name="GoToFile.includeJavaFiles" value="false" />
+ <property name="MemberChooser.sorted" value="false" />
+ <property name="MemberChooser.showClasses" value="true" />
+ <property name="MemberChooser.copyJavadoc" value="false" />
+ <property name="options.lastSelected" value="Errors" />
+ <property name="options.splitter.main.proportions" value="0.3" />
+ <property name="options.splitter.details.proportions" value="0.2" />
+ <property name="options.searchVisible" value="true" />
+ <property name="settings.editor.selected.configurable" value="editor.preferences.import" />
+ <property name="settings.editor.splitter.proportion" value="0.2" />
+ <property name="last_opened_file_path" value="$PROJECT_DIR$" />
+ </component>
+ <component name="RunDashboard">
+ <option name="ruleStates">
+ <list>
+ <RuleState>
+ <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
+ </RuleState>
+ <RuleState>
+ <option name="name" value="StatusDashboardGroupingRule" />
+ </RuleState>
+ </list>
+ </option>
+ </component>
+ <component name="RunManager">
+ <configuration default="true" type="Applet" factoryName="Applet">
+ <option name="WIDTH" value="400" />
+ <option name="HEIGHT" value="300" />
+ <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" />
+ <module />
+ </configuration>
+ <configuration default="true" type="Application" factoryName="Application">
+ <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="VM_PARAMETERS" />
+ <option name="PROGRAM_PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="ENABLE_SWING_INSPECTOR" value="false" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <module name="" />
+ <envs />
+ </configuration>
+ <configuration default="true" type="JUnit" factoryName="JUnit">
+ <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
+ <module name="" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="PACKAGE_NAME" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="METHOD_NAME" />
+ <option name="TEST_OBJECT" value="class" />
+ <option name="VM_PARAMETERS" value="-ea" />
+ <option name="PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <option name="TEST_SEARCH_SCOPE">
+ <value defaultName="moduleWithDependencies" />
+ </option>
+ <envs />
+ <patterns />
+ </configuration>
+ <configuration default="true" type="Remote" factoryName="Remote">
+ <option name="USE_SOCKET_TRANSPORT" value="true" />
+ <option name="SERVER_MODE" value="false" />
+ <option name="SHMEM_ADDRESS" value="javadebug" />
+ <option name="HOST" value="localhost" />
+ <option name="PORT" value="5005" />
+ </configuration>
+ <configuration default="true" type="TestNG" factoryName="TestNG">
+ <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
+ <module name="" />
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="SUITE_NAME" />
+ <option name="PACKAGE_NAME" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="METHOD_NAME" />
+ <option name="GROUP_NAME" />
+ <option name="TEST_OBJECT" value="CLASS" />
+ <option name="VM_PARAMETERS" value="-ea" />
+ <option name="PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ <option name="OUTPUT_DIRECTORY" />
+ <option name="ANNOTATION_TYPE" />
+ <option name="ENV_VARIABLES" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <option name="TEST_SEARCH_SCOPE">
+ <value defaultName="moduleWithDependencies" />
+ </option>
+ <option name="USE_DEFAULT_REPORTERS" value="false" />
+ <option name="PROPERTIES_FILE" />
+ <envs />
+ <properties />
+ <listeners />
+ </configuration>
+ <configuration default="true" type="#org.jetbrains.idea.devkit.run.PluginConfigurationType" factoryName="Plugin">
+ <module name="" />
+ <option name="VM_PARAMETERS" value="-Xmx512m -Xms256m -XX:MaxPermSize=250m -ea" />
+ <option name="PROGRAM_PARAMETERS" />
+ <predefined_log_file id="idea.log" enabled="true" />
+ </configuration>
+ <configuration name="<template>" type="WebApp" default="true" selected="false">
+ <Host>localhost</Host>
+ <Port>5050</Port>
+ </configuration>
+ </component>
+ <component name="ShelveChangesManager" show_recycled="false">
+ <option name="remove_strategy" value="false" />
+ </component>
+ <component name="TaskManager">
+ <task active="true" id="Default" summary="Default task">
+ <changelist id="8a92e766-359b-4589-8d1f-ac1a44fb0c89" name="Default" comment="" />
+ <created>1511883242491</created>
+ <option name="number" value="Default" />
+ <option name="presentableId" value="Default" />
+ <updated>1511883242491</updated>
+ </task>
+ <servers />
+ </component>
+ <component name="ToolWindowManager">
+ <frame x="2560" y="18" width="3840" height="2123" extended-state="0" />
+ <layout>
+ <window_info id="Palette" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+ <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32860184" sideWeight="0.49526584" order="6" side_tool="false" content_ui="tabs" />
+ <window_info id="Palette	" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+ <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.34767523" sideWeight="0.43609673" order="7" side_tool="true" content_ui="tabs" />
+ <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.34698126" sideWeight="0.5639033" order="2" side_tool="false" content_ui="tabs" />
+ <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.32338655" sideWeight="0.4821803" order="9" side_tool="false" content_ui="tabs" />
+ <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.35600278" sideWeight="0.49666223" order="10" side_tool="false" content_ui="tabs" />
+ <window_info id="Designer" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+ <window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.3227345" sideWeight="0.48438585" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="JProfiler" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4338588" sideWeight="0.47117063" order="12" side_tool="false" content_ui="tabs" />
+ <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.24960127" sideWeight="0.49757785" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="UI Designer" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+ <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.3650243" sideWeight="0.85578585" order="3" side_tool="false" content_ui="tabs" />
+ <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.16108453" sideWeight="0.51561415" order="7" side_tool="true" content_ui="tabs" />
+ <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+ <window_info id="Nl-Palette" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
+ <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="SLIDING" type="SLIDING" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+ <window_info id="Gerrit" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.333564" sideWeight="0.5" order="11" side_tool="false" content_ui="tabs" />
+ <window_info id="Maven Projects" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="8" side_tool="false" content_ui="tabs" />
+ <window_info id="Analyze Dataflow to" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32941177" sideWeight="0.49821427" order="13" side_tool="false" content_ui="tabs" />
+ <window_info id="Properties" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
+ <window_info id="Capture Tool" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+ <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.22288676" sideWeight="0.49111968" order="2" side_tool="false" content_ui="combo" />
+ <window_info id="Memory View" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.00996016" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+ <window_info id="Inspection Results" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.3293623" sideWeight="0.49248666" order="16" side_tool="false" content_ui="tabs" />
+ <window_info id="Dynamic Properties" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32988048" sideWeight="0.5" order="9" side_tool="false" content_ui="tabs" />
+ <window_info id="Messages" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.13046496" sideWeight="0.26683939" order="8" side_tool="false" content_ui="tabs" />
+ <window_info id="Image Layers" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
+ <window_info id="Capture Analysis" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+ <window_info id="Run Dashboard" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="15" side_tool="false" content_ui="tabs" />
+ <window_info id="Documentation" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="FLOATING" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="12" side_tool="false" content_ui="tabs" x="1292" y="82" width="2360" height="1327" />
+ <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+ <window_info id="Coverage" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25976095" sideWeight="0.5061444" order="11" side_tool="true" content_ui="tabs" />
+ <window_info id="Runtime Dashboard" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="14" side_tool="false" content_ui="tabs" />
+ <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.2984039" sideWeight="0.8713299" order="1" side_tool="false" content_ui="tabs" />
+ <window_info id="Theme Preview" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="10" side_tool="false" content_ui="tabs" />
+ </layout>
+ </component>
+ <component name="VcsContentAnnotationSettings">
+ <option name="myLimit" value="2678400000" />
+ </component>
+ <component name="XDebuggerManager">
+ <breakpoint-manager />
+ <watches-manager />
+ </component>
+ <component name="editorHistoryManager">
+ <entry file="file://$PROJECT_DIR$/bridge.iml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="0">
+ <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+ <folding />
+ </state>
+ </provider>
+ </entry>
+ </component>
+ <component name="masterDetails">
+ <states>
+ <state key="ProjectJDKs.UI">
+ <settings>
+ <last-edited>1.6</last-edited>
+ <splitter-proportions>
+ <option name="proportions">
+ <list>
+ <option value="0.2" />
+ </list>
+ </option>
+ </splitter-proportions>
+ </settings>
+ </state>
+ <state key="ScopeChooserConfigurable.UI">
+ <settings>
+ <splitter-proportions>
+ <option name="proportions">
+ <list>
+ <option value="0.23280424" />
+ </list>
+ </option>
+ </splitter-proportions>
+ </settings>
+ </state>
+ </states>
+ </component>
+</project>
\ No newline at end of file
diff --git a/bridge/Android.mk b/bridge/Android.mk
index 333e6bb..89a34a3 100644
--- a/bridge/Android.mk
+++ b/bridge/Android.mk
@@ -32,6 +32,8 @@
include $(BUILD_HOST_JAVA_LIBRARY)
+$(call dist-for-goals, layoutlib, $(LOCAL_INSTALLED_MODULE))
+
# Build all sub-directories
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/bridge/src/android/content/res/AssetManager_Delegate.java b/bridge/src/android/content/res/AssetManager_Delegate.java
index b4d5288..3b47ea7 100644
--- a/bridge/src/android/content/res/AssetManager_Delegate.java
+++ b/bridge/src/android/content/res/AssetManager_Delegate.java
@@ -20,6 +20,9 @@
import android.util.SparseArray;
+import java.io.IOException;
+import java.io.InputStream;
+
/**
* Delegate used to provide implementation of a select few native methods of {@link AssetManager}
* <p/>
@@ -30,6 +33,20 @@
public class AssetManager_Delegate {
@LayoutlibDelegate
+ public static InputStream open(AssetManager mgr, String fileName) throws IOException {
+ return mgr.open_Original(fileName);
+ }
+
+ @LayoutlibDelegate
+ public static InputStream open(AssetManager mgr, String fileName, int accessMode)
+ throws IOException {
+ if (!(mgr instanceof BridgeAssetManager)) {
+ return mgr.open_Original(fileName, accessMode);
+ }
+ return ((BridgeAssetManager) mgr).getAssetRepository().openAsset(fileName, accessMode);
+ }
+
+ @LayoutlibDelegate
/*package*/ static long newTheme(AssetManager manager) {
return Resources_Theme_Delegate.getDelegateManager()
.addNewDelegate(new Resources_Theme_Delegate());
diff --git a/bridge/src/android/content/res/BridgeAssetManager.java b/bridge/src/android/content/res/BridgeAssetManager.java
index 2691e56..a1a4a19 100644
--- a/bridge/src/android/content/res/BridgeAssetManager.java
+++ b/bridge/src/android/content/res/BridgeAssetManager.java
@@ -36,7 +36,6 @@
// Note that AssetManager() creates a system AssetManager and we override it
// with our BridgeAssetManager.
AssetManager.sSystem = new BridgeAssetManager();
- AssetManager.sSystem.makeStringBlocks(null);
}
return AssetManager.sSystem;
}
diff --git a/bridge/src/android/content/res/BridgeTypedArray.java b/bridge/src/android/content/res/BridgeTypedArray.java
index 5536c4f..9505993 100644
--- a/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/bridge/src/android/content/res/BridgeTypedArray.java
@@ -29,7 +29,6 @@
import com.android.resources.ResourceType;
import android.annotation.Nullable;
-import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.graphics.Typeface;
import android.graphics.Typeface_Accessor;
@@ -676,6 +675,13 @@
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]),
diff --git a/bridge/src/android/content/res/Resources_Delegate.java b/bridge/src/android/content/res/Resources_Delegate.java
index c1e9cd3..77ae90f 100644
--- a/bridge/src/android/content/res/Resources_Delegate.java
+++ b/bridge/src/android/content/res/Resources_Delegate.java
@@ -651,6 +651,26 @@
}
@LayoutlibDelegate
+ static float getFloat(Resources resources, int id) {
+ Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+ if (value != null) {
+ ResourceValue resValue = value.getSecond();
+
+ if (resValue != null) {
+ String v = resValue.getValue();
+ if (v != null) {
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException ignore) {
+ }
+ }
+ }
+ }
+ return 0;
+ }
+
+ @LayoutlibDelegate
static boolean getBoolean(Resources resources, int id) throws NotFoundException {
Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
@@ -866,31 +886,26 @@
}
@LayoutlibDelegate
+ static void getValueForDensity(Resources resources, int id, int density, TypedValue outValue,
+ boolean resolveRefs) throws NotFoundException {
+ getValue(resources, id, outValue, resolveRefs);
+ }
+
+ @LayoutlibDelegate
static XmlResourceParser getXml(Resources resources, int id) throws NotFoundException {
- Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+ Pair<String, ResourceValue> v = getResourceValue(resources, id, mPlatformResourceFlag);
- if (value != null) {
- String v = value.getSecond().getValue();
+ if (v != null) {
+ ResourceValue value = v.getSecond();
- if (v != null) {
- // check this is a file
- File f = new File(v);
- if (f.isFile()) {
- try {
- XmlPullParser parser = ParserFactory.create(f);
-
- return new BridgeXmlBlockParser(parser, getContext(resources),
- mPlatformResourceFlag[0]);
- } catch (XmlPullParserException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- } catch (FileNotFoundException e) {
- NotFoundException newE = new NotFoundException();
- newE.initCause(e);
- throw newE;
- }
- }
+ try {
+ 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*/);
+ // we'll return null below.
+ } catch (FileNotFoundException e) {
+ // this shouldn't happen since we check above.
}
}
diff --git a/bridge/src/android/content/res/Resources_Theme_Delegate.java b/bridge/src/android/content/res/Resources_Theme_Delegate.java
index f1e8fc2..8aa9216 100644
--- a/bridge/src/android/content/res/Resources_Theme_Delegate.java
+++ b/bridge/src/android/content/res/Resources_Theme_Delegate.java
@@ -56,7 +56,8 @@
Resources thisResources, Theme thisTheme,
int[] attrs) {
boolean changed = setupResources(thisTheme);
- BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+ 0, attrs);
ta.setTheme(thisTheme);
restoreResources(changed);
return ta;
@@ -68,8 +69,8 @@
int resid, int[] attrs)
throws NotFoundException {
boolean changed = setupResources(thisTheme);
- BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid,
- attrs);
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(
+ resid, attrs);
ta.setTheme(thisTheme);
restoreResources(changed);
return ta;
@@ -80,7 +81,7 @@
Resources thisResources, Theme thisTheme,
AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
boolean changed = setupResources(thisTheme);
- BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set,
+ BridgeTypedArray ta = RenderSessionImpl.getCurrentContext().internalObtainStyledAttributes(set,
attrs, defStyleAttr, defStyleRes);
ta.setTheme(thisTheme);
restoreResources(changed);
diff --git a/bridge/src/android/graphics/BaseCanvas_Delegate.java b/bridge/src/android/graphics/BaseCanvas_Delegate.java
index f1c63e6..9260099 100644
--- a/bridge/src/android/graphics/BaseCanvas_Delegate.java
+++ b/bridge/src/android/graphics/BaseCanvas_Delegate.java
@@ -477,40 +477,39 @@
@LayoutlibDelegate
/*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count,
- float startX, float startY, int flags, long paint, long typeface) {
+ float startX, float startY, int flags, long paint) {
drawText(nativeCanvas, text, index, count, startX, startY, flags,
- paint, typeface);
+ paint);
}
@LayoutlibDelegate
/*package*/ static void nDrawText(long nativeCanvas, String text,
- int start, int end, float x, float y, final int flags, long paint,
- long typeface) {
+ int start, int end, float x, float y, final int flags, long paint) {
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
- nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
+ nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint);
}
@LayoutlibDelegate
/*package*/ static void nDrawTextRun(long nativeCanvas, String text,
int start, int end, int contextStart, int contextEnd,
- float x, float y, boolean isRtl, long paint, long typeface) {
+ float x, float y, boolean isRtl, long paint) {
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
drawText(nativeCanvas, buffer, 0, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR,
- paint,
- typeface);
+ paint);
}
@LayoutlibDelegate
/*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 typeface) {
- drawText(nativeCanvas, text, start, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, paint, typeface);
+ float x, float y, boolean isRtl, long paint,
+ long nativeMeasuredText, int measuredTextOffset) {
+ drawText(nativeCanvas, text, start, count, x, y, isRtl ? Paint.BIDI_RTL : Paint.BIDI_LTR, paint);
}
@LayoutlibDelegate
@@ -519,7 +518,7 @@
int count, long path,
float hOffset,
float vOffset, int bidiFlags,
- long paint, long typeface) {
+ long paint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawTextOnPath is not supported.", null, null /*data*/);
@@ -530,8 +529,7 @@
String text, long path,
float hOffset,
float vOffset,
- int bidiFlags, long paint,
- long typeface) {
+ int bidiFlags, long paint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawTextOnPath is not supported.", null, null /*data*/);
@@ -576,16 +574,13 @@
private static void drawText(long nativeCanvas, final char[] text, final int index,
final int count, final float startX, final float startY, final int bidiFlags,
- long paint, final long typeface) {
+ long paint) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
(graphics, paintDelegate) -> {
// WARNING: the logic in this method is similar to Paint_Delegate.measureText.
// Any change to this method should be reflected in Paint.measureText
- // assert that the typeface passed is actually the one stored in paint.
- assert (typeface == paintDelegate.mNativeTypeface);
-
// Paint.TextAlign indicates how the text is positioned relative to X.
// LEFT is the default and there's nothing to do.
float x = startX;
diff --git a/bridge/src/android/graphics/BidiRenderer.java b/bridge/src/android/graphics/BidiRenderer.java
index 63691c3..9a102c1 100644
--- a/bridge/src/android/graphics/BidiRenderer.java
+++ b/bridge/src/android/graphics/BidiRenderer.java
@@ -33,6 +33,7 @@
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
@@ -42,7 +43,10 @@
*/
@SuppressWarnings("deprecation")
public class BidiRenderer {
- private static String JAVA_VENDOR = System.getProperty("java.vendor");
+ private static final String JETBRAINS_VENDOR_ID = "JetBrains s.r.o";
+ private static final String JAVA_VENDOR = System.getProperty("java.vendor");
+ /** When scaleX is bigger than this, we need to apply the workaround for http://b.android.com/211659 */
+ private static final double SCALEX_WORKAROUND_LIMIT = 9;
private static class ScriptRun {
private final int start;
@@ -62,6 +66,8 @@
// Bounds of the text drawn so far.
private RectF mBounds;
private float mBaseline;
+ private final Bidi mBidi = new Bidi();
+
/**
* @param graphics May be null.
@@ -97,10 +103,10 @@
*/
public RectF renderText(int start, int limit, int bidiFlags, float[] advances,
int advancesIndex, boolean draw) {
- Bidi bidi = new Bidi(mText, start, null, 0, limit - start, getIcuFlags(bidiFlags));
- mText = bidi.getText();
- for (int i = 0; i < bidi.countRuns(); i++) {
- BidiRun visualRun = bidi.getVisualRun(i);
+ mBidi.setPara(Arrays.copyOfRange(mText, start, limit), (byte)getIcuFlags(bidiFlags), null);
+ mText = mBidi.getText();
+ for (int i = 0; i < mBidi.countRuns(); i++) {
+ BidiRun visualRun = mBidi.getVisualRun(i);
boolean isRtl = visualRun.getDirection() == Bidi.RTL;
renderText(visualRun.getStart(), visualRun.getLimit(), isRtl, advances,
advancesIndex, draw);
@@ -197,8 +203,7 @@
private static void logFontWarning() {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
- "Some fonts could not be loaded. The rendering may not be perfect. " +
- "Try running the IDE with JRE 7.", null, null);
+ "Some fonts could not be loaded. The rendering may not be perfect.", null, null);
}
/**
@@ -207,28 +212,52 @@
*/
private void render(int start, int limit, Font font, int flag, float[] advances,
int advancesIndex, boolean draw) {
+ FontRenderContext frc = mGraphics != null ? mGraphics.getFontRenderContext() :
+ Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext();
- FontRenderContext frc;
- if (mGraphics != null) {
- frc = mGraphics.getFontRenderContext();
- } else {
- frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext();
+ boolean frcIsAntialiased = frc.isAntiAliased();
+ boolean useAntialiasing = mPaint.isAntiAliased();
- // Metrics obtained this way don't have anti-aliasing set. So,
- // we create a new FontRenderContext with anti-aliasing set.
+ if (frcIsAntialiased) {
+ if (!useAntialiasing) {
+ // The context has antialiasing enabled but the paint does not. We need to
+ // disable it
+ frc = new FontRenderContext(font.getTransform(), false,
+ frc.usesFractionalMetrics());
+ } else {
+ // In this case both the paint and the context antialising match but we need
+ // to check for a bug in the JDK
+ // Workaround for http://b.android.com/211659 (disable antialiasing)
+ if (font.isTransformed()) {
+ AffineTransform transform = font.getTransform();
+ if (transform.getScaleX() >= SCALEX_WORKAROUND_LIMIT &&
+ JETBRAINS_VENDOR_ID.equals(JAVA_VENDOR)) {
+ frc = new FontRenderContext(transform, false, frc.usesFractionalMetrics());
+ }
+ }
+ }
+ } else if (useAntialiasing) {
+ // The context does not have antialiasing enabled but the paint does. We need to
+ // enable it unless we need to avoid the JDK bug
+
AffineTransform transform = font.getTransform();
- if (mPaint.isAntiAliased() &&
- // Workaround for http://b.android.com/211659
- (transform.getScaleX() <= 9.9 ||
- !"JetBrains s.r.o".equals(JAVA_VENDOR))) {
- frc = new FontRenderContext(transform, true, frc.usesFractionalMetrics());
+ // Workaround for http://b.android.com/211659 (disable antialiasing)
+ if (transform.getScaleX() < SCALEX_WORKAROUND_LIMIT ||
+ !JETBRAINS_VENDOR_ID.equals(JAVA_VENDOR)) {
+ frc = new FontRenderContext(font.getTransform(), true, frc.usesFractionalMetrics());
}
}
+
GlyphVector gv = font.layoutGlyphVector(frc, mText, start, limit, flag);
int ng = gv.getNumGlyphs();
int[] ci = gv.getGlyphCharIndices(0, ng, null);
if (advances != null) {
for (int i = 0; i < ng; i++) {
+ if (mText[ci[i]] == '\uFEFF') {
+ // Workaround for bug in JetBrains JDK
+ // where the character \uFEFF is associated a glyph with non-zero width
+ continue;
+ }
int adv_idx = advancesIndex + ci[i];
advances[adv_idx] += gv.getGlyphMetrics(i).getAdvanceX();
}
@@ -285,15 +314,17 @@
@NonNull
private static Font getScriptFont(char[] text, int start, int limit, List<FontInfo> fonts) {
for (FontInfo fontInfo : fonts) {
- if (fontInfo.mFont == null) {
- logFontWarning();
- continue;
- }
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;
}
diff --git a/bridge/src/android/graphics/BitmapFactory_Delegate.java b/bridge/src/android/graphics/BitmapFactory_Delegate.java
index 8bd2a7a..ee099e1 100644
--- a/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -101,20 +101,26 @@
@LayoutlibDelegate
/*package*/ static Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
Rect padding, Options opts) {
- opts.inBitmap = null;
+ if (opts != null) {
+ opts.inBitmap = null;
+ }
return null;
}
@LayoutlibDelegate
/*package*/ static Bitmap nativeDecodeAsset(long asset, Rect padding, Options opts) {
- opts.inBitmap = null;
+ if (opts != null) {
+ opts.inBitmap = null;
+ }
return null;
}
@LayoutlibDelegate
/*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset,
int length, Options opts) {
- opts.inBitmap = null;
+ if (opts != null) {
+ opts.inBitmap = null;
+ }
return null;
}
diff --git a/bridge/src/android/graphics/BitmapShader_Delegate.java b/bridge/src/android/graphics/BitmapShader_Delegate.java
index 4914a48..891b07f 100644
--- a/bridge/src/android/graphics/BitmapShader_Delegate.java
+++ b/bridge/src/android/graphics/BitmapShader_Delegate.java
@@ -31,7 +31,9 @@
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
+import java.awt.image.SampleModel;
/**
* Delegate implementing the native methods of android.graphics.BitmapShader
@@ -189,9 +191,9 @@
}
}
- image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
- return image.getRaster();
+ DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+ SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+ return Raster.createWritableRaster(colorModel, dataBuffer, null);
}
}
diff --git a/bridge/src/android/graphics/Bitmap_Delegate.java b/bridge/src/android/graphics/Bitmap_Delegate.java
index 0064537..6c72cb2 100644
--- a/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -608,7 +608,8 @@
if (delegate == null) {
return 0;
}
- return nativeRowBytes(nativeBitmap) * delegate.mImage.getHeight();
+ int size = nativeRowBytes(nativeBitmap) * delegate.mImage.getHeight();
+ return size < 0 ? Integer.MAX_VALUE : size;
}
diff --git a/bridge/src/android/graphics/Canvas_Delegate.java b/bridge/src/android/graphics/Canvas_Delegate.java
index 47216ee..a23244b 100644
--- a/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/bridge/src/android/graphics/Canvas_Delegate.java
@@ -141,9 +141,6 @@
}
@LayoutlibDelegate
- public static void nSetHighContrastText(long nativeCanvas, boolean highContrastText){}
-
- @LayoutlibDelegate
public static int nGetWidth(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
@@ -473,6 +470,11 @@
return sFinalizer;
}
+ @LayoutlibDelegate
+ /*package*/ static void nSetCompatibilityVersion(int apiLevel) {
+ // Unsupported by layoutlib, do nothing
+ }
+
private Canvas_Delegate(Bitmap_Delegate bitmap) {
super(bitmap);
}
diff --git a/bridge/src/android/graphics/FontFamily_Delegate.java b/bridge/src/android/graphics/FontFamily_Delegate.java
index d8e049a..1ad1f8f 100644
--- a/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -27,7 +27,6 @@
import android.content.res.AssetManager;
import android.content.res.BridgeAssetManager;
import android.graphics.fonts.FontVariationAxis;
-import android.text.FontConfig;
import java.awt.Font;
import java.awt.FontFormatException;
@@ -42,6 +41,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
@@ -97,6 +97,28 @@
Font mFont;
int mWeight;
boolean mIsItalic;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ FontInfo fontInfo = (FontInfo) o;
+ return mWeight == fontInfo.mWeight && mIsItalic == fontInfo.mIsItalic;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mWeight, mIsItalic);
+ }
+
+ @Override
+ public String toString() {
+ return "FontInfo{" + "mWeight=" + mWeight + ", mIsItalic=" + mIsItalic + '}';
+ }
}
// ---- delegate manager ----
@@ -111,7 +133,10 @@
// ---- delegate data ----
- private List<FontInfo> mFonts = new ArrayList<FontInfo>();
+
+ // 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.
@@ -147,7 +172,7 @@
File allFonts = new File(fontLocation, FN_ALL_FONTS_LIST);
// Current number of fonts is 103. Use the next round number to leave scope for more fonts
// in the future.
- Set<String> allFontsList = new HashSet<String>(128);
+ Set<String> allFontsList = new HashSet<>(128);
Scanner scanner = null;
try {
scanner = new Scanner(allFonts);
@@ -179,23 +204,38 @@
FontInfo desiredStyle = new FontInfo();
desiredStyle.mWeight = desiredWeight;
desiredStyle.mIsItalic = isItalic;
- FontInfo bestFont = null;
- int bestMatch = Integer.MAX_VALUE;
- //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
- for (int i = 0, n = mFonts.size(); i < n; i++) {
- FontInfo font = mFonts.get(i);
- int match = computeMatch(font, desiredStyle);
- if (match < bestMatch) {
- bestMatch = match;
- bestFont = font;
- }
+
+ 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;
+
+ //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
+ for (FontInfo font : mFonts.keySet()) {
+ int match = computeMatch(font, desiredStyle);
+ if (match < bestMatch) {
+ bestMatch = match;
+ bestFont = font;
+ }
+ }
+
+ // This would mean that we already had the font so it should be in the set
+ assert bestMatch != 0;
+ }
+
if (bestFont == null) {
return null;
}
- if (bestMatch == 0) {
- return bestFont.mFont;
- }
+
+
// Derive the font as required and add it to the list of Fonts.
deriveFont(bestFont, desiredStyle);
addFont(desiredStyle);
@@ -411,14 +451,6 @@
sManager.removeJavaReferenceFor(builderPtr);
}
- /**
- * @see FontFamily#allowUnsupportedFont
- */
- @LayoutlibDelegate
- /*package*/ static void nAllowUnsupportedFont(long builderPtr) {
- // Do nothing here as this is used for Minikin fonts
- }
-
// ---- private helper methods ----
private void init() {
@@ -461,19 +493,7 @@
}
private boolean addFont(@NonNull FontInfo fontInfo) {
- int weight = fontInfo.mWeight;
- boolean isItalic = fontInfo.mIsItalic;
- // The list is usually just two fonts big. So iterating over all isn't as bad as it looks.
- // It's biggest for roboto where the size is 12.
- //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
- for (int i = 0, n = mFonts.size(); i < n; i++) {
- FontInfo font = mFonts.get(i);
- if (font.mWeight == weight && font.mIsItalic == isItalic) {
- return false;
- }
- }
- mFonts.add(fontInfo);
- return true;
+ return mFonts.putIfAbsent(fontInfo, fontInfo.mFont) == null;
}
/**
@@ -496,28 +516,32 @@
* its style
* @return outFont
*/
- @NonNull
- private FontInfo deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
+ private void deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) {
int desiredWeight = outFont.mWeight;
int srcWeight = srcFont.mWeight;
assert srcFont.mFont != null;
Font derivedFont = srcFont.mFont;
+ int derivedStyle = 0;
// Embolden the font if required.
if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) {
- derivedFont = derivedFont.deriveFont(Font.BOLD);
+ derivedStyle |= Font.BOLD;
srcWeight += BOLD_FONT_WEIGHT_DELTA;
}
// Italicize the font if required.
if (outFont.mIsItalic && !srcFont.mIsItalic) {
- derivedFont = derivedFont.deriveFont(Font.ITALIC);
+ derivedStyle |= Font.ITALIC;
} else if (outFont.mIsItalic != srcFont.mIsItalic) {
// The desired font is plain, but the src font is italics. We can't convert it back. So
// we update the value to reflect the true style of the font we're deriving.
outFont.mIsItalic = srcFont.mIsItalic;
}
+
+ if (derivedStyle != 0) {
+ derivedFont = derivedFont.deriveFont(derivedStyle);
+ }
+
outFont.mFont = derivedFont;
outFont.mWeight = srcWeight;
// No need to update mIsItalics, as it's already been handled above.
- return outFont;
}
}
diff --git a/bridge/src/android/graphics/ImageDecoder.java b/bridge/src/android/graphics/ImageDecoder.java
new file mode 100644
index 0000000..506eab5
--- /dev/null
+++ b/bridge/src/android/graphics/ImageDecoder.java
@@ -0,0 +1,704 @@
+/*
+ * 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 android.graphics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.res.AssetManager.AssetInputStream;
+import android.content.res.Resources;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.util.DisplayMetrics;
+import android.util.Size;
+import android.util.TypedValue;
+
+import java.nio.ByteBuffer;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ArrayIndexOutOfBoundsException;
+import java.lang.AutoCloseable;
+import java.lang.NullPointerException;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
+ */
+public final class ImageDecoder implements AutoCloseable {
+
+ /**
+ * Source of the encoded image data.
+ */
+ public static abstract class Source {
+ private Source() {}
+
+ /* @hide */
+ @Nullable
+ Resources getResources() { return null; }
+
+ /* @hide */
+ int getDensity() { return Bitmap.DENSITY_NONE; }
+
+ /* @hide */
+ int computeDstDensity() {
+ Resources res = getResources();
+ if (res == null) {
+ return Bitmap.getDefaultDensity();
+ }
+
+ return res.getDisplayMetrics().densityDpi;
+ }
+
+ /* @hide */
+ @NonNull
+ abstract ImageDecoder createImageDecoder() throws IOException;
+ };
+
+ private static class ByteArraySource extends Source {
+ ByteArraySource(@NonNull byte[] data, int offset, int length) {
+ mData = data;
+ mOffset = offset;
+ mLength = length;
+ };
+ private final byte[] mData;
+ private final int mOffset;
+ private final int mLength;
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return new ImageDecoder();
+ }
+ }
+
+ private static class ByteBufferSource extends Source {
+ ByteBufferSource(@NonNull ByteBuffer buffer) {
+ mBuffer = buffer;
+ }
+ private final ByteBuffer mBuffer;
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return new ImageDecoder();
+ }
+ }
+
+ private static class ContentResolverSource extends Source {
+ ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri) {
+ mResolver = resolver;
+ mUri = uri;
+ }
+
+ private final ContentResolver mResolver;
+ private final Uri mUri;
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return new ImageDecoder();
+ }
+ }
+
+ /**
+ * For backwards compatibility, this does *not* close the InputStream.
+ */
+ private static class InputStreamSource extends Source {
+ InputStreamSource(Resources res, InputStream is, int inputDensity) {
+ if (is == null) {
+ throw new IllegalArgumentException("The InputStream cannot be null");
+ }
+ mResources = res;
+ mInputStream = is;
+ mInputDensity = res != null ? inputDensity : Bitmap.DENSITY_NONE;
+ }
+
+ final Resources mResources;
+ InputStream mInputStream;
+ final int mInputDensity;
+
+ @Override
+ public Resources getResources() { return mResources; }
+
+ @Override
+ public int getDensity() { return mInputDensity; }
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return new ImageDecoder();
+ }
+ }
+
+ /**
+ * Takes ownership of the AssetInputStream.
+ *
+ * @hide
+ */
+ public static class AssetInputStreamSource extends Source {
+ public AssetInputStreamSource(@NonNull AssetInputStream ais,
+ @NonNull Resources res, @NonNull TypedValue value) {
+ mAssetInputStream = ais;
+ mResources = res;
+
+ if (value.density == TypedValue.DENSITY_DEFAULT) {
+ mDensity = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (value.density != TypedValue.DENSITY_NONE) {
+ mDensity = value.density;
+ } else {
+ mDensity = Bitmap.DENSITY_NONE;
+ }
+ }
+
+ private AssetInputStream mAssetInputStream;
+ private final Resources mResources;
+ private final int mDensity;
+
+ @Override
+ public Resources getResources() { return mResources; }
+
+ @Override
+ public int getDensity() {
+ return mDensity;
+ }
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return new ImageDecoder();
+ }
+ }
+
+ private static class ResourceSource extends Source {
+ ResourceSource(@NonNull Resources res, int resId) {
+ mResources = res;
+ mResId = resId;
+ mResDensity = Bitmap.DENSITY_NONE;
+ }
+
+ final Resources mResources;
+ final int mResId;
+ int mResDensity;
+
+ @Override
+ public Resources getResources() { return mResources; }
+
+ @Override
+ public int getDensity() { return mResDensity; }
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return new ImageDecoder();
+ }
+ }
+
+ private static class FileSource extends Source {
+ FileSource(@NonNull File file) {
+ mFile = file;
+ }
+
+ private final File mFile;
+
+ @Override
+ public ImageDecoder createImageDecoder() throws IOException {
+ return new ImageDecoder();
+ }
+ }
+
+ /**
+ * Contains information about the encoded image.
+ */
+ public static class ImageInfo {
+ private ImageDecoder mDecoder;
+
+ private ImageInfo(@NonNull ImageDecoder decoder) {
+ mDecoder = decoder;
+ }
+
+ /**
+ * Size of the image, without scaling or cropping.
+ */
+ @NonNull
+ public Size getSize() {
+ return new Size(0, 0);
+ }
+
+ /**
+ * The mimeType of the image.
+ */
+ @NonNull
+ public String getMimeType() {
+ return "";
+ }
+
+ /**
+ * Whether the image is animated.
+ *
+ * <p>Calling {@link #decodeDrawable} will return an
+ * {@link AnimatedImageDrawable}.</p>
+ */
+ public boolean isAnimated() {
+ return mDecoder.mAnimated;
+ }
+ };
+
+ /**
+ * Thrown if the provided data is incomplete.
+ */
+ public static class IncompleteException extends IOException {};
+
+ /**
+ * Optional listener supplied to {@link #decodeDrawable} or
+ * {@link #decodeBitmap}.
+ */
+ public interface OnHeaderDecodedListener {
+ /**
+ * Called when the header is decoded and the size is known.
+ *
+ * @param decoder allows changing the default settings of the decode.
+ * @param info Information about the encoded image.
+ * @param source that created the decoder.
+ */
+ void onHeaderDecoded(@NonNull ImageDecoder decoder,
+ @NonNull ImageInfo info, @NonNull Source source);
+
+ };
+
+ /**
+ * An Exception was thrown reading the {@link Source}.
+ */
+ public static final int ERROR_SOURCE_EXCEPTION = 1;
+
+ /**
+ * The encoded data was incomplete.
+ */
+ public static final int ERROR_SOURCE_INCOMPLETE = 2;
+
+ /**
+ * The encoded data contained an error.
+ */
+ public static final int ERROR_SOURCE_ERROR = 3;
+
+ @Retention(SOURCE)
+ public @interface Error {}
+
+ /**
+ * Optional listener supplied to the ImageDecoder.
+ *
+ * Without this listener, errors will throw {@link java.io.IOException}.
+ */
+ public interface OnPartialImageListener {
+ /**
+ * Called when there is only a partial image to display.
+ *
+ * If decoding is interrupted after having decoded a partial image,
+ * this listener lets the client know that and allows them to
+ * optionally finish the rest of the decode/creation process to create
+ * a partial {@link Drawable}/{@link Bitmap}.
+ *
+ * @param error indicating what interrupted the decode.
+ * @param source that had the error.
+ * @return True to create and return a {@link Drawable}/{@link Bitmap}
+ * with partial data. False (which is the default) to abort the
+ * decode and throw {@link java.io.IOException}.
+ */
+ boolean onPartialImage(@Error int error, @NonNull Source source);
+ }
+
+ private boolean mAnimated;
+ private Rect mOutPaddingRect;
+
+ public ImageDecoder() {
+ mAnimated = true; // This is too avoid throwing an exception in AnimatedImageDrawable
+ }
+
+ /**
+ * Create a new {@link Source} from an asset.
+ * @hide
+ *
+ * @param res the {@link Resources} object containing the image data.
+ * @param resId resource ID of the image data.
+ * // FIXME: Can be an @DrawableRes?
+ * @return a new Source object, which can be passed to
+ * {@link #decodeDrawable} or {@link #decodeBitmap}.
+ */
+ @NonNull
+ public static Source createSource(@NonNull Resources res, int resId)
+ {
+ return new ResourceSource(res, resId);
+ }
+
+ /**
+ * Create a new {@link Source} from a {@link android.net.Uri}.
+ *
+ * @param cr to retrieve from.
+ * @param uri of the image file.
+ * @return a new Source object, which can be passed to
+ * {@link #decodeDrawable} or {@link #decodeBitmap}.
+ */
+ @NonNull
+ public static Source createSource(@NonNull ContentResolver cr,
+ @NonNull Uri uri) {
+ return new ContentResolverSource(cr, uri);
+ }
+
+ /**
+ * Create a new {@link Source} from a byte array.
+ *
+ * @param data byte array of compressed image data.
+ * @param offset offset into data for where the decoder should begin
+ * parsing.
+ * @param length number of bytes, beginning at offset, to parse.
+ * @throws NullPointerException if data is null.
+ * @throws ArrayIndexOutOfBoundsException if offset and length are
+ * not within data.
+ * @hide
+ */
+ @NonNull
+ public static Source createSource(@NonNull byte[] data, int offset,
+ int length) throws ArrayIndexOutOfBoundsException {
+ if (offset < 0 || length < 0 || offset >= data.length ||
+ offset + length > data.length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "invalid offset/length!");
+ }
+ return new ByteArraySource(data, offset, length);
+ }
+
+ /**
+ * See {@link #createSource(byte[], int, int).
+ * @hide
+ */
+ @NonNull
+ public static Source createSource(@NonNull byte[] data) {
+ return createSource(data, 0, data.length);
+ }
+
+ /**
+ * Create a new {@link Source} from a {@link java.nio.ByteBuffer}.
+ *
+ * <p>The returned {@link Source} effectively takes ownership of the
+ * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after
+ * this call.</p>
+ *
+ * Decoding will start from {@link java.nio.ByteBuffer#position()}. The
+ * position after decoding is undefined.
+ */
+ @NonNull
+ public static Source createSource(@NonNull ByteBuffer buffer) {
+ return new ByteBufferSource(buffer);
+ }
+
+ /**
+ * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable)
+ * @hide
+ */
+ public static Source createSource(Resources res, InputStream is) {
+ return new InputStreamSource(res, is, Bitmap.getDefaultDensity());
+ }
+
+ /**
+ * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable)
+ * @hide
+ */
+ public static Source createSource(Resources res, InputStream is, int density) {
+ return new InputStreamSource(res, is, density);
+ }
+
+ /**
+ * Create a new {@link Source} from a {@link java.io.File}.
+ */
+ @NonNull
+ public static Source createSource(@NonNull File file) {
+ return new FileSource(file);
+ }
+
+ /**
+ * Return the width and height of a given sample size.
+ *
+ * <p>This takes an input that functions like
+ * {@link BitmapFactory.Options#inSampleSize}. It returns a width and
+ * height that can be acheived by sampling the encoded image. Other widths
+ * and heights may be supported, but will require an additional (internal)
+ * scaling step. Such internal scaling is *not* supported with
+ * {@link #setRequireUnpremultiplied} set to {@code true}.</p>
+ *
+ * @param sampleSize Sampling rate of the encoded image.
+ * @return {@link android.util.Size} of the width and height after
+ * sampling.
+ */
+ @NonNull
+ public Size getSampledSize(int sampleSize) {
+ return new Size(0, 0);
+ }
+
+ // Modifiers
+ /**
+ * Resize the output to have the following size.
+ *
+ * @param width must be greater than 0.
+ * @param height must be greater than 0.
+ */
+ public void setResize(int width, int height) {
+ }
+
+ /**
+ * Resize based on a sample size.
+ *
+ * <p>This has the same effect as passing the result of
+ * {@link #getSampledSize} to {@link #setResize(int, int)}.</p>
+ *
+ * @param sampleSize Sampling rate of the encoded image.
+ */
+ public void setResize(int sampleSize) {
+ }
+
+ // These need to stay in sync with ImageDecoder.cpp's Allocator enum.
+ /**
+ * Use the default allocation for the pixel memory.
+ *
+ * Will typically result in a {@link Bitmap.Config#HARDWARE}
+ * allocation, but may be software for small images. In addition, this will
+ * switch to software when HARDWARE is incompatible, e.g.
+ * {@link #setMutable}, {@link #setAsAlphaMask}.
+ */
+ public static final int ALLOCATOR_DEFAULT = 0;
+
+ /**
+ * Use a software allocation for the pixel memory.
+ *
+ * Useful for drawing to a software {@link Canvas} or for
+ * accessing the pixels on the final output.
+ */
+ public static final int ALLOCATOR_SOFTWARE = 1;
+
+ /**
+ * Use shared memory for the pixel memory.
+ *
+ * Useful for sharing across processes.
+ */
+ public static final int ALLOCATOR_SHARED_MEMORY = 2;
+
+ /**
+ * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}.
+ *
+ * When this is combined with incompatible options, like
+ * {@link #setMutable} or {@link #setAsAlphaMask}, {@link #decodeDrawable}
+ * / {@link #decodeBitmap} will throw an
+ * {@link java.lang.IllegalStateException}.
+ */
+ public static final int ALLOCATOR_HARDWARE = 3;
+
+ /** @hide **/
+ @Retention(SOURCE)
+ public @interface Allocator {};
+
+ /**
+ * Choose the backing for the pixel memory.
+ *
+ * This is ignored for animated drawables.
+ *
+ * @param allocator Type of allocator to use.
+ */
+ public ImageDecoder setAllocator(@Allocator int allocator) {
+ return this;
+ }
+
+ /**
+ * Specify whether the {@link Bitmap} should have unpremultiplied pixels.
+ *
+ * By default, ImageDecoder will create a {@link Bitmap} with
+ * premultiplied pixels, which is required for drawing with the
+ * {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
+ * this method with a value of {@code true} will result in
+ * {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied
+ * pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with
+ * {@link #decodeDrawable}; attempting to decode an unpremultiplied
+ * {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
+ */
+ public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) {
+ return this;
+ }
+
+ /**
+ * Modify the image after decoding and scaling.
+ *
+ * <p>This allows adding effects prior to returning a {@link Drawable} or
+ * {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
+ * this is the only way to process the image after decoding.</p>
+ *
+ * <p>If set on a nine-patch image, the nine-patch data is ignored.</p>
+ *
+ * <p>For an animated image, the drawing commands drawn on the
+ * {@link Canvas} will be recorded immediately and then applied to each
+ * frame.</p>
+ */
+ public ImageDecoder setPostProcessor(@Nullable PostProcessor p) {
+ return this;
+ }
+
+ /**
+ * Set (replace) the {@link OnPartialImageListener} on this object.
+ *
+ * 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;
+ }
+
+ /**
+ * Crop the output to {@code subset} of the (possibly) scaled image.
+ *
+ * <p>{@code subset} must be contained within the size set by
+ * {@link #setResize} or the bounds of the image if setResize was not
+ * called. Otherwise an {@link IllegalStateException} will be thrown by
+ * {@link #decodeDrawable}/{@link #decodeBitmap}.</p>
+ *
+ * <p>NOT intended as a replacement for
+ * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
+ * but merely crops the output.</p>
+ */
+ public ImageDecoder setCrop(@Nullable Rect subset) {
+ return this;
+ }
+
+ /**
+ * Set a Rect for retrieving nine patch padding.
+ *
+ * If the image is a nine patch, this Rect will be set to the padding
+ * rectangle during decode. Otherwise it will not be modified.
+ *
+ * @hide
+ */
+ public ImageDecoder setOutPaddingRect(@NonNull Rect outPadding) {
+ mOutPaddingRect = outPadding;
+ return this;
+ }
+
+ /**
+ * Specify whether the {@link Bitmap} should be mutable.
+ *
+ * <p>By default, a {@link Bitmap} created will be immutable, but that can
+ * be changed with this call.</p>
+ *
+ * <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE},
+ * because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable.
+ * Attempting to combine them will throw an
+ * {@link java.lang.IllegalStateException}.</p>
+ *
+ * <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable},
+ * which would require retrieving the Bitmap from the returned Drawable in
+ * order to modify. Attempting to decode a mutable {@link Drawable} will
+ * throw an {@link java.lang.IllegalStateException}.</p>
+ */
+ public ImageDecoder setMutable(boolean mutable) {
+ return this;
+ }
+
+ /**
+ * Specify whether to potentially save RAM at the expense of quality.
+ *
+ * Setting this to {@code true} may result in a {@link Bitmap} with a
+ * denser {@link Bitmap.Config}, depending on the image. For example, for
+ * an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config}
+ * with no alpha information.
+ */
+ public ImageDecoder setPreferRamOverQuality(boolean preferRamOverQuality) {
+ return this;
+ }
+
+ /**
+ * Specify whether to potentially treat the output as an alpha mask.
+ *
+ * <p>If this is set to {@code true} and the image is encoded in a format
+ * with only one channel, treat that channel as alpha. Otherwise this call has
+ * no effect.</p>
+ *
+ * <p>setAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to
+ * combine them will result in {@link #decodeDrawable}/
+ * {@link #decodeBitmap} throwing an
+ * {@link java.lang.IllegalStateException}.</p>
+ */
+ public ImageDecoder setAsAlphaMask(boolean asAlphaMask) {
+ return this;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ /**
+ * Create a {@link Drawable} from a {@code Source}.
+ *
+ * @param src representing the encoded image.
+ * @param listener for learning the {@link ImageInfo} and changing any
+ * default settings on the {@code ImageDecoder}. If not {@code null},
+ * this will be called on the same thread as {@code decodeDrawable}
+ * before that method returns.
+ * @return Drawable for displaying the image.
+ * @throws IOException if {@code src} is not found, is an unsupported
+ * format, or cannot be decoded for any reason.
+ */
+ @NonNull
+ public static Drawable decodeDrawable(@NonNull Source src,
+ @Nullable OnHeaderDecodedListener listener) throws IOException {
+ Bitmap bitmap = decodeBitmap(src, listener);
+ return new BitmapDrawable(src.getResources(), bitmap);
+ }
+
+ /**
+ * See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}.
+ */
+ @NonNull
+ public static Drawable decodeDrawable(@NonNull Source src)
+ throws IOException {
+ return decodeDrawable(src, null);
+ }
+
+ /**
+ * Create a {@link Bitmap} from a {@code Source}.
+ *
+ * @param src representing the encoded image.
+ * @param listener for learning the {@link ImageInfo} and changing any
+ * default settings on the {@code ImageDecoder}. If not {@code null},
+ * this will be called on the same thread as {@code decodeBitmap}
+ * before that method returns.
+ * @return Bitmap containing the image.
+ * @throws IOException if {@code src} is not found, is an unsupported
+ * format, or cannot be decoded for any reason.
+ */
+ @NonNull
+ public static Bitmap decodeBitmap(@NonNull Source src,
+ @Nullable OnHeaderDecodedListener listener) throws IOException {
+ TypedValue value = new TypedValue();
+ value.density = src.getDensity();
+ ImageDecoder decoder = src.createImageDecoder();
+ if (listener != null) {
+ listener.onHeaderDecoded(decoder, new ImageInfo(decoder), src);
+ }
+ return BitmapFactory.decodeResourceStream(src.getResources(), value,
+ ((InputStreamSource) src).mInputStream, decoder.mOutPaddingRect, null);
+ }
+
+ /**
+ * See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}.
+ */
+ @NonNull
+ public static Bitmap decodeBitmap(@NonNull Source src) throws IOException {
+ return decodeBitmap(src, null);
+ }
+}
diff --git a/bridge/src/android/graphics/LinearGradient_Delegate.java b/bridge/src/android/graphics/LinearGradient_Delegate.java
index cd4393a..477705c 100644
--- a/bridge/src/android/graphics/LinearGradient_Delegate.java
+++ b/bridge/src/android/graphics/LinearGradient_Delegate.java
@@ -24,6 +24,9 @@
import android.graphics.Shader.TileMode;
import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
/**
* Delegate implementing the native methods of android.graphics.LinearGradient
@@ -174,10 +177,6 @@
@Override
public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
- java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
- mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
- mColorModel.isAlphaPremultiplied(), null);
-
int[] data = new int[w*h];
int index = 0;
@@ -199,9 +198,9 @@
}
}
- image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
- return image.getRaster();
+ DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+ SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+ return Raster.createWritableRaster(colorModel, dataBuffer, null);
}
}
diff --git a/bridge/src/android/graphics/Matrix_Delegate.java b/bridge/src/android/graphics/Matrix_Delegate.java
index 354f919..5ae181d 100644
--- a/bridge/src/android/graphics/Matrix_Delegate.java
+++ b/bridge/src/android/graphics/Matrix_Delegate.java
@@ -118,8 +118,7 @@
return true;
}
- public static float[] makeValues(AffineTransform matrix) {
- float[] values = new float[MATRIX_SIZE];
+ private static float[] setValues(AffineTransform matrix, float[] values) {
values[0] = (float) matrix.getScaleX();
values[1] = (float) matrix.getShearX();
values[2] = (float) matrix.getTranslateX();
@@ -133,6 +132,10 @@
return values;
}
+ public static float[] makeValues(AffineTransform matrix) {
+ return setValues(matrix, new float[MATRIX_SIZE]);
+ }
+
public static Matrix_Delegate make(AffineTransform matrix) {
return new Matrix_Delegate(makeValues(matrix));
}
@@ -616,12 +619,7 @@
try {
AffineTransform affineTransform = d.getAffineTransform();
AffineTransform inverseTransform = affineTransform.createInverse();
- inv_mtx.mValues[0] = (float)inverseTransform.getScaleX();
- inv_mtx.mValues[1] = (float)inverseTransform.getShearX();
- inv_mtx.mValues[2] = (float)inverseTransform.getTranslateX();
- inv_mtx.mValues[3] = (float)inverseTransform.getScaleX();
- inv_mtx.mValues[4] = (float)inverseTransform.getShearY();
- inv_mtx.mValues[5] = (float)inverseTransform.getTranslateY();
+ setValues(inverseTransform, inv_mtx.mValues);
return true;
} catch (NoninvertibleTransformException e) {
diff --git a/bridge/src/android/graphics/NinePatch_Delegate.java b/bridge/src/android/graphics/NinePatch_Delegate.java
index 43e5b0f..ce2c18b 100644
--- a/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -160,8 +160,12 @@
}
@LayoutlibDelegate
- /*package*/ static void nativeFinalize(long chunk) {
- sManager.removeJavaReferenceFor(chunk);
+ /*package*/ static void nativeFinalize(long nativeNinePatch) {
+ NinePatch_Delegate delegate = sManager.getDelegate(nativeNinePatch);
+ if (delegate != null && delegate.chunk != null) {
+ sChunkCache.remove(delegate.chunk);
+ }
+ sManager.removeJavaReferenceFor(nativeNinePatch);
}
diff --git a/bridge/src/android/graphics/Paint_Delegate.java b/bridge/src/android/graphics/Paint_Delegate.java
index 406157e..dae966d 100644
--- a/bridge/src/android/graphics/Paint_Delegate.java
+++ b/bridge/src/android/graphics/Paint_Delegate.java
@@ -34,10 +34,11 @@
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
import libcore.util.NativeAllocationRegistry_Delegate;
@@ -55,23 +56,32 @@
*
*/
public class Paint_Delegate {
+ private static final float DEFAULT_TEXT_SIZE = 20.f;
+ private static final float DEFAULT_TEXT_SCALE_X = 1.f;
+ private static final float DEFAULT_TEXT_SKEW_X = 0.f;
/**
* Class associating a {@link Font} and its {@link java.awt.FontMetrics}.
*/
/*package*/ static final class FontInfo {
- Font mFont;
- java.awt.FontMetrics mMetrics;
+ final Font mFont;
+ final java.awt.FontMetrics mMetrics;
+
+ FontInfo(@NonNull Font font, @NonNull java.awt.FontMetrics fontMetrics) {
+ this.mFont = font;
+ this.mMetrics = fontMetrics;
+ }
}
// ---- delegate manager ----
private static final DelegateManager<Paint_Delegate> sManager =
- new DelegateManager<Paint_Delegate>(Paint_Delegate.class);
+ new DelegateManager<>(Paint_Delegate.class);
private static long sFinalizer = -1;
// ---- delegate helper data ----
// This list can contain null elements.
+ @Nullable
private List<FontInfo> mFonts;
// ---- delegate data ----
@@ -100,11 +110,9 @@
private PathEffect_Delegate mPathEffect;
private MaskFilter_Delegate mMaskFilter;
+ @SuppressWarnings("FieldCanBeLocal") // Used to store the locale for future use
private Locale mLocale = Locale.getDefault();
- // Used only to assert invariants.
- public long mNativeTypeface;
-
// ---- Public Helper methods ----
@Nullable
@@ -115,7 +123,32 @@
/**
* Returns the list of {@link Font} objects.
*/
+ @NonNull
public List<FontInfo> getFonts() {
+ Typeface_Delegate typeface = mTypeface;
+ if (typeface == null) {
+ if (Typeface.sDefaultTypeface == null) {
+ return Collections.emptyList();
+ }
+
+ typeface = Typeface_Delegate.getDelegate(Typeface.sDefaultTypeface.native_instance);
+ }
+
+ if (mFonts != null) {
+ return mFonts;
+ }
+
+ // Apply an optional transformation for skew and scale
+ AffineTransform affineTransform = mTextScaleX != 1.0 || mTextSkewX != 0 ?
+ new AffineTransform(mTextScaleX, mTextSkewX, 0, 1, 0, 0) :
+ null;
+
+ List<FontInfo> infoList = StreamSupport.stream(typeface.getFonts(mFontVariant).spliterator
+ (), false)
+ .map(font -> getFontInfo(font, mTextSize, affineTransform))
+ .collect(Collectors.toList());
+ mFonts = Collections.unmodifiableList(infoList);
+
return mFonts;
}
@@ -187,6 +220,7 @@
if (mPathEffect.isSupported()) {
Stroke stroke = mPathEffect.getStroke(this);
assert stroke != null;
+ //noinspection ConstantConditions
if (stroke != null) {
return stroke;
}
@@ -483,7 +517,7 @@
if (delegate.mTextSize != textSize) {
delegate.mTextSize = textSize;
- delegate.updateFontObject();
+ delegate.invalidateFonts();
}
}
@@ -508,7 +542,7 @@
if (delegate.mTextScaleX != scaleX) {
delegate.mTextScaleX = scaleX;
- delegate.updateFontObject();
+ delegate.invalidateFonts();
}
}
@@ -533,20 +567,21 @@
if (delegate.mTextSkewX != skewX) {
delegate.mTextSkewX = skewX;
- delegate.updateFontObject();
+ delegate.invalidateFonts();
}
}
@LayoutlibDelegate
- /*package*/ static float nAscent(long nativePaint, long nativeTypeface) {
+ /*package*/ static float nAscent(long nativePaint) {
// get the delegate
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
- if (delegate.mFonts.size() > 0) {
- java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
+ List<FontInfo> fonts = delegate.getFonts();
+ if (fonts.size() > 0) {
+ java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
// Android expects negative ascent so we invert the value from Java.
return - javaMetrics.getAscent();
}
@@ -555,15 +590,16 @@
}
@LayoutlibDelegate
- /*package*/ static float nDescent(long nativePaint, long nativeTypeface) {
+ /*package*/ static float nDescent(long nativePaint) {
// get the delegate
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
- if (delegate.mFonts.size() > 0) {
- java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
+ List<FontInfo> fonts = delegate.getFonts();
+ if (fonts.size() > 0) {
+ java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
return javaMetrics.getDescent();
}
@@ -572,7 +608,7 @@
}
@LayoutlibDelegate
- /*package*/ static float nGetFontMetrics(long nativePaint, long nativeTypeface,
+ /*package*/ static float nGetFontMetrics(long nativePaint,
FontMetrics metrics) {
// get the delegate
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -584,16 +620,16 @@
}
@LayoutlibDelegate
- /*package*/ static int nGetFontMetricsInt(long nativePaint,
- long nativeTypeface, FontMetricsInt fmi) {
+ /*package*/ static int nGetFontMetricsInt(long nativePaint, FontMetricsInt fmi) {
// get the delegate
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return 0;
}
- if (delegate.mFonts.size() > 0) {
- java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
+ List<FontInfo> fonts = delegate.getFonts();
+ if (fonts.size() > 0) {
+ java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
if (fmi != null) {
// Android expects negative ascent so we invert the value from Java.
fmi.top = (int)(- javaMetrics.getMaxAscent() * 1.15);
@@ -610,7 +646,7 @@
}
@LayoutlibDelegate
- /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, char[] text,
+ /*package*/ static int nBreakText(long nativePaint, char[] text,
int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) {
// get the delegate
@@ -652,10 +688,9 @@
}
@LayoutlibDelegate
- /*package*/ static int nBreakText(long nativePaint, long nativeTypeface, String text,
- boolean measureForwards,
+ /*package*/ static int nBreakText(long nativePaint, String text, boolean measureForwards,
float maxWidth, int bidiFlags, float[] measuredWidth) {
- return nBreakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(),
+ return nBreakText(nativePaint, text.toCharArray(), 0, text.length(),
maxWidth, bidiFlags, measuredWidth);
}
@@ -871,20 +906,18 @@
}
@LayoutlibDelegate
- /*package*/ static long nSetTypeface(long native_object, long typeface) {
+ /*package*/ static void nSetTypeface(long native_object, long typeface) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(native_object);
if (delegate == null) {
- return 0;
+ return;
}
Typeface_Delegate typefaceDelegate = Typeface_Delegate.getDelegate(typeface);
- if (delegate.mTypeface != typefaceDelegate || delegate.mNativeTypeface != typeface) {
- delegate.mTypeface = Typeface_Delegate.getDelegate(typeface);
- delegate.mNativeTypeface = typeface;
- delegate.updateFontObject();
+ if (delegate.mTypeface != typefaceDelegate) {
+ delegate.mTypeface = typefaceDelegate;
+ delegate.invalidateFonts();
}
- return typeface;
}
@LayoutlibDelegate
@@ -922,14 +955,14 @@
}
@LayoutlibDelegate
- /*package*/ static void nSetTextLocalesByMinikinLangListId(long paintPtr,
+ /*package*/ static void nSetTextLocalesByMinikinLocaleListId(long paintPtr,
int mMinikinLangListId) {
// FIXME
}
@LayoutlibDelegate
- /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
- char[] text, int index, int count, int contextIndex, int contextCount,
+ /*package*/ static float nGetTextAdvances(long native_object, char[] text, int index,
+ int count, int contextIndex, int contextCount,
int bidiFlags, float[] advances, int advancesIndex) {
if (advances != null)
@@ -941,32 +974,25 @@
return 0.f;
}
- // native_typeface is passed here since Framework's old implementation did not have the
- // typeface object associated with the Paint. Since, we follow the new framework way,
- // we store the typeface with the paint and use it directly.
- assert (native_typeface == delegate.mNativeTypeface);
-
RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, bidiFlags);
return bounds.right - bounds.left;
}
@LayoutlibDelegate
- /*package*/ static float nGetTextAdvances(long native_object, long native_typeface,
- String text, int start, int end, int contextStart, int contextEnd,
- int bidiFlags, float[] advances, int advancesIndex) {
+ /*package*/ static float nGetTextAdvances(long native_object, String text, int start, int end,
+ int contextStart, int contextEnd, int bidiFlags, float[] advances, int advancesIndex) {
// FIXME: support contextStart and contextEnd
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
- return nGetTextAdvances(native_object, native_typeface, buffer, 0, count,
+ return nGetTextAdvances(native_object, buffer, 0, count,
contextStart, contextEnd - contextStart, bidiFlags, advances, advancesIndex);
}
@LayoutlibDelegate
- /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, long typefacePtr,
- char[] text, int contextStart, int contextLength, int flags, int offset,
- int cursorOpt) {
+ /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, char[] text,
+ int contextStart, int contextLength, int flags, int offset, int cursorOpt) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Paint.getTextRunCursor is not supported.", null, null /*data*/);
@@ -974,8 +1000,8 @@
}
@LayoutlibDelegate
- /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, long typefacePtr,
- String text, int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
+ /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, String text,
+ int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Paint.getTextRunCursor is not supported.", null, null /*data*/);
@@ -983,31 +1009,31 @@
}
@LayoutlibDelegate
- /*package*/ static void nGetTextPath(long native_object, long native_typeface,
- int bidiFlags, char[] text, int index, int count, float x, float y, long path) {
+ /*package*/ static void nGetTextPath(long native_object, int bidiFlags, char[] text,
+ int index, int count, float x, float y, long path) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Paint.getTextPath is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
- /*package*/ static void nGetTextPath(long native_object, long native_typeface,
- int bidiFlags, String text, int start, int end, float x, float y, long path) {
+ /*package*/ static void nGetTextPath(long native_object, int bidiFlags, String text, int start,
+ int end, float x, float y, long path) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Paint.getTextPath is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
- /*package*/ static void nGetStringBounds(long nativePaint, long native_typeface,
- String text, int start, int end, int bidiFlags, Rect bounds) {
- nGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start,
+ /*package*/ static void nGetStringBounds(long nativePaint, String text, int start, int end,
+ int bidiFlags, Rect bounds) {
+ nGetCharArrayBounds(nativePaint, text.toCharArray(), start,
end - start, bidiFlags, bounds);
}
@LayoutlibDelegate
- /*package*/ static void nGetCharArrayBounds(long nativePaint, long native_typeface,
- char[] text, int index, int count, int bidiFlags, Rect bounds) {
+ /*package*/ static void nGetCharArrayBounds(long nativePaint, char[] text, int index,
+ int count, int bidiFlags, Rect bounds) {
// get the delegate from the native int.
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
@@ -1015,9 +1041,6 @@
return;
}
- // assert that the typeface passed is actually the one that we had stored.
- assert (native_typeface == delegate.mNativeTypeface);
-
delegate.measureText(text, index, count, null, 0, bidiFlags).roundOut(bounds);
}
@@ -1095,8 +1118,7 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nHasGlyph(long nativePaint, long nativeTypeface, int bidiFlags,
- String string) {
+ /*package*/ static boolean nHasGlyph(long nativePaint, int bidiFlags, String string) {
Paint_Delegate delegate = sManager.getDelegate(nativePaint);
if (delegate == null) {
return false;
@@ -1109,11 +1131,9 @@
"Paint.hasGlyph() is not supported for ligatures.", null, null);
return false;
}
- assert nativeTypeface == delegate.mNativeTypeface;
- Typeface_Delegate typeface_delegate = Typeface_Delegate.getDelegate(nativeTypeface);
char c = string.charAt(0);
- for (Font font : typeface_delegate.getFonts(delegate.mFontVariant)) {
+ for (Font font : delegate.mTypeface.getFonts(delegate.mFontVariant)) {
if (font.canDisplay(c)) {
return true;
}
@@ -1123,14 +1143,14 @@
@LayoutlibDelegate
- /*package*/ static float nGetRunAdvance(long nativePaint, long nativeTypeface,
- @NonNull char[] text, int start, int end, int contextStart, int contextEnd,
+ /*package*/ static float nGetRunAdvance(long nativePaint, @NonNull char[] text, int start,
+ int end, int contextStart, int contextEnd,
boolean isRtl, int offset) {
int count = end - start;
float[] advances = new float[count];
int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
- nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
- contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
+ nGetTextAdvances(nativePaint, text, start, count, contextStart,
+ contextEnd - contextStart, bidiFlags, advances, 0);
int startOffset = offset - start; // offset from start.
float sum = 0;
for (int i = 0; i < startOffset; i++) {
@@ -1140,14 +1160,13 @@
}
@LayoutlibDelegate
- /*package*/ static int nGetOffsetForAdvance(long nativePaint, long nativeTypeface,
- char[] text, int start, int end, int contextStart, int contextEnd, boolean isRtl,
- float advance) {
+ /*package*/ static int nGetOffsetForAdvance(long nativePaint, char[] text, int start,
+ int end, int contextStart, int contextEnd, boolean isRtl, float advance) {
int count = end - start;
float[] advances = new float[count];
int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
- nGetTextAdvances(nativePaint, nativeTypeface, text, start, count,
- contextStart, contextEnd - contextStart, bidiFlags, advances, 0);
+ nGetTextAdvances(nativePaint, text, start, count, contextStart,
+ contextEnd - contextStart, bidiFlags, advances, 0);
float sum = 0;
int i;
for (i = 0; i < count && sum < advance; i++) {
@@ -1159,25 +1178,30 @@
}
@LayoutlibDelegate
- /*package*/ static float nGetUnderlinePosition(long paintPtr, long typefacePtr) {
+ /*package*/ static float nGetUnderlinePosition(long paintPtr) {
return (1.0f / 9.0f) * nGetTextSize(paintPtr);
}
@LayoutlibDelegate
- /*package*/ static float nGetUnderlineThickness(long paintPtr, long typefacePtr) {
+ /*package*/ static float nGetUnderlineThickness(long paintPtr) {
return (1.0f / 18.0f) * nGetTextSize(paintPtr);
}
@LayoutlibDelegate
- /*package*/ static float nGetStrikeThruPosition(long paintPtr, long typefacePtr) {
+ /*package*/ static float nGetStrikeThruPosition(long paintPtr) {
return (-79.0f / 252.0f) * nGetTextSize(paintPtr);
}
@LayoutlibDelegate
- /*package*/ static float nGetStrikeThruThickness(long paintPtr, long typefacePtr) {
+ /*package*/ static float nGetStrikeThruThickness(long paintPtr) {
return (1.0f / 18.0f) * nGetTextSize(paintPtr);
}
+ @LayoutlibDelegate
+ /*package*/ static boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr) {
+ return leftPaintPtr == rightPaintPtr;
+ }
+
// ---- Private delegate/helper methods ----
/*package*/ Paint_Delegate() {
@@ -1196,26 +1220,24 @@
mJoin = paint.mJoin;
mTextAlign = paint.mTextAlign;
- boolean needsFontUpdate = false;
- if (mTypeface != paint.mTypeface || mNativeTypeface != paint.mNativeTypeface) {
+ if (mTypeface != paint.mTypeface) {
mTypeface = paint.mTypeface;
- mNativeTypeface = paint.mNativeTypeface;
- needsFontUpdate = true;
+ invalidateFonts();
}
if (mTextSize != paint.mTextSize) {
mTextSize = paint.mTextSize;
- needsFontUpdate = true;
+ invalidateFonts();
}
if (mTextScaleX != paint.mTextScaleX) {
mTextScaleX = paint.mTextScaleX;
- needsFontUpdate = true;
+ invalidateFonts();
}
if (mTextSkewX != paint.mTextSkewX) {
mTextSkewX = paint.mTextSkewX;
- needsFontUpdate = true;
+ invalidateFonts();
}
mStrokeWidth = paint.mStrokeWidth;
@@ -1226,77 +1248,70 @@
mPathEffect = paint.mPathEffect;
mMaskFilter = paint.mMaskFilter;
mHintingMode = paint.mHintingMode;
-
- if (needsFontUpdate) {
- updateFontObject();
- }
}
private void reset() {
+ Typeface_Delegate defaultTypeface =
+ Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance);
+
mFlags = Paint.HIDDEN_DEFAULT_PAINT_FLAGS;
mColor = 0xFF000000;
mStyle = Paint.Style.FILL.nativeInt;
mCap = Paint.Cap.BUTT.nativeInt;
mJoin = Paint.Join.MITER.nativeInt;
mTextAlign = 0;
- mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance);
- mNativeTypeface = 0;
+
+ if (mTypeface != defaultTypeface) {
+ mTypeface = defaultTypeface;
+ invalidateFonts();
+ }
+
mStrokeWidth = 1.f;
mStrokeMiter = 4.f;
- mTextSize = 20.f;
- mTextScaleX = 1.f;
- mTextSkewX = 0.f;
+
+ if (mTextSize != DEFAULT_TEXT_SIZE) {
+ mTextSize = DEFAULT_TEXT_SIZE;
+ invalidateFonts();
+ }
+
+ if (mTextScaleX != DEFAULT_TEXT_SCALE_X) {
+ mTextScaleX = DEFAULT_TEXT_SCALE_X;
+ invalidateFonts();
+ }
+
+ if (mTextSkewX != DEFAULT_TEXT_SKEW_X) {
+ mTextSkewX = DEFAULT_TEXT_SKEW_X;
+ invalidateFonts();
+ }
+
mPorterDuffMode = Xfermode.DEFAULT;
mColorFilter = null;
mShader = null;
mPathEffect = null;
mMaskFilter = null;
- updateFontObject();
mHintingMode = Paint.HINTING_ON;
}
- /**
- * Update the {@link Font} object from the typeface, text size and scaling
- */
- @SuppressWarnings("deprecation")
- private void updateFontObject() {
- if (mTypeface != null) {
- // Get the fonts from the TypeFace object.
- List<Font> fonts = mTypeface.getFonts(mFontVariant);
+ private void invalidateFonts() {
+ mFonts = null;
+ }
- if (fonts.isEmpty()) {
- mFonts = Collections.emptyList();
- return;
- }
-
- // create new font objects as well as FontMetrics, based on the current text size
- // and skew info.
- int nFonts = fonts.size();
- ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(nFonts);
- //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
- for (int i = 0; i < nFonts; i++) {
- Font font = fonts.get(i);
- if (font == null) {
- // If the font is null, add null to infoList. When rendering the text, if this
- // null is reached, a warning will be logged.
- infoList.add(null);
- continue;
- }
- FontInfo info = new FontInfo();
- info.mFont = font.deriveFont(mTextSize);
- if (mTextScaleX != 1.0 || mTextSkewX != 0) {
- // TODO: support skew
- info.mFont = info.mFont.deriveFont(new AffineTransform(
- mTextScaleX, mTextSkewX, 0, 1, 0, 0));
- }
- // The metrics here don't have anti-aliasing set.
- info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);
-
- infoList.add(info);
- }
-
- mFonts = Collections.unmodifiableList(infoList);
+ @Nullable
+ private static FontInfo getFontInfo(@Nullable Font font, float textSize,
+ @Nullable AffineTransform transform) {
+ if (font == null) {
+ return null;
}
+
+ Font transformedFont = font.deriveFont(textSize);
+ if (transform != null) {
+ // TODO: support skew
+ transformedFont = transformedFont.deriveFont(transform);
+ }
+
+ // The metrics here don't have anti-aliasing set.
+ return new FontInfo(transformedFont,
+ Toolkit.getDefaultToolkit().getFontMetrics(transformedFont));
}
/*package*/ RectF measureText(char[] text, int index, int count, float[] advances,
@@ -1312,8 +1327,9 @@
}
private float getFontMetrics(FontMetrics metrics) {
- if (mFonts.size() > 0) {
- java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
+ List<FontInfo> fonts = getFonts();
+ if (fonts.size() > 0) {
+ java.awt.FontMetrics javaMetrics = fonts.get(0).mMetrics;
if (metrics != null) {
// Android expects negative ascent so we invert the value from Java.
metrics.top = - javaMetrics.getMaxAscent();
diff --git a/bridge/src/android/graphics/RadialGradient_Delegate.java b/bridge/src/android/graphics/RadialGradient_Delegate.java
index b5ba468..25521d2 100644
--- a/bridge/src/android/graphics/RadialGradient_Delegate.java
+++ b/bridge/src/android/graphics/RadialGradient_Delegate.java
@@ -24,6 +24,9 @@
import android.graphics.Shader.TileMode;
import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
/**
* Delegate implementing the native methods of android.graphics.RadialGradient
@@ -163,10 +166,6 @@
@Override
public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
- java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
- mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
- mColorModel.isAlphaPremultiplied(), null);
-
int[] data = new int[w*h];
// compute distance from each point to the center, and figure out the distance from
@@ -174,6 +173,7 @@
int index = 0;
float[] pt1 = new float[2];
float[] pt2 = new float[2];
+
for (int iy = 0 ; iy < h ; iy++) {
for (int ix = 0 ; ix < w ; ix++) {
// handle the canvas transform
@@ -182,21 +182,21 @@
mCanvasMatrix.transform(pt1, 0, pt2, 0, 1);
// handle the local matrix
- pt1[0] = pt2[0] - mX;
- pt1[1] = pt2[1] - mY;
+ pt1[0] = pt2[0];
+ pt1[1] = pt2[1];
mLocalMatrix.transform(pt1, 0, pt2, 0, 1);
- float _x = pt2[0];
- float _y = pt2[1];
+ float _x = pt2[0] - mX;
+ float _y = pt2[1] - mY;
float distance = (float) Math.hypot(_x, _y);
data[index++] = getGradientColor(distance / mRadius);
}
}
- image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
- return image.getRaster();
+ DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+ SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+ return Raster.createWritableRaster(colorModel, dataBuffer, null);
}
}
diff --git a/bridge/src/android/graphics/Shader_Delegate.java b/bridge/src/android/graphics/Shader_Delegate.java
index d88be47..eefa929 100644
--- a/bridge/src/android/graphics/Shader_Delegate.java
+++ b/bridge/src/android/graphics/Shader_Delegate.java
@@ -92,6 +92,10 @@
// ---- Private delegate/helper methods ----
protected Shader_Delegate(long nativeMatrix) {
+ setLocalMatrix(nativeMatrix);
+ }
+
+ public void setLocalMatrix(long nativeMatrix) {
mLocalMatrix = Matrix_Delegate.getDelegate(nativeMatrix);
}
diff --git a/bridge/src/android/graphics/SweepGradient_Delegate.java b/bridge/src/android/graphics/SweepGradient_Delegate.java
index 30152bc..6e8aca3 100644
--- a/bridge/src/android/graphics/SweepGradient_Delegate.java
+++ b/bridge/src/android/graphics/SweepGradient_Delegate.java
@@ -21,6 +21,10 @@
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import java.awt.image.DataBufferInt;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+
/**
* Delegate implementing the native methods of android.graphics.SweepGradient
*
@@ -156,9 +160,6 @@
@Override
public java.awt.image.Raster getRaster(int x, int y, int w, int h) {
- java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
- mColorModel, mColorModel.createCompatibleWritableRaster(w, h),
- mColorModel.isAlphaPremultiplied(), null);
int[] data = new int[w*h];
@@ -203,9 +204,9 @@
}
}
- image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/);
-
- return image.getRaster();
+ DataBufferInt dataBuffer = new DataBufferInt(data, data.length);
+ SampleModel colorModel = mColorModel.createCompatibleSampleModel(w, h);
+ return Raster.createWritableRaster(colorModel, dataBuffer, null);
}
}
diff --git a/bridge/src/android/graphics/Typeface_Delegate.java b/bridge/src/android/graphics/Typeface_Delegate.java
index a04a324..d793ade 100644
--- a/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/bridge/src/android/graphics/Typeface_Delegate.java
@@ -16,37 +16,57 @@
package android.graphics;
+import com.android.SdkConstants;
import com.android.ide.common.rendering.api.LayoutLog;
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;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.FontResourcesParser;
import android.graphics.FontFamily_Delegate.FontVariant;
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.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Spliterator;
+import java.util.Spliterators;
import static android.graphics.FontFamily_Delegate.getFontLocation;
/**
* Delegate implementing the native methods of android.graphics.Typeface
- *
- * Through the layoutlib_create tool, the original native methods of Typeface have been replaced
- * by calls to methods of the same name in this delegate class.
- *
+ * <p>
+ * Through the layoutlib_create tool, the original native methods of Typeface 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 Typeface class.
+ * native data into its own objects and mapping them to int that are sent back and forth between it
+ * and the original Typeface class.
*
* @see DelegateManager
- *
*/
public final class Typeface_Delegate {
@@ -54,87 +74,29 @@
// ---- delegate manager ----
private static final DelegateManager<Typeface_Delegate> sManager =
- new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class);
+ new DelegateManager<>(Typeface_Delegate.class);
// ---- delegate data ----
-
+ private static long sDefaultTypeface;
@NonNull
private final FontFamily_Delegate[] mFontFamilies; // the reference to FontFamily_Delegate.
/** @see Font#getStyle() */
private final int mStyle;
private final int mWeight;
-
- private static long sDefaultTypeface;
+ private SoftReference<EnumMap<FontVariant, List<Font>>> mFontsCache = new SoftReference<>(null);
// ---- Public Helper methods ----
- public static Typeface_Delegate getDelegate(long nativeTypeface) {
- return sManager.getDelegate(nativeTypeface);
+ public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) {
+ mFontFamilies = fontFamilies;
+ mStyle = style;
+ mWeight = weight;
}
- /**
- * Return a list of fonts that match the style and variant. The list is ordered according to
- * preference of fonts.
- *
- * The list may contain null when the font failed to load. If null is reached when trying to
- * render with this list of fonts, then a warning should be logged letting the user know that
- * some font failed to load.
- *
- * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or
- * {@link FontVariant#ELEGANT}
- */
- @NonNull
- public List<Font> getFonts(FontVariant variant) {
- assert variant != FontVariant.NONE;
-
- // Calculate the required weight based on style and weight of this typeface.
- int weight = mWeight + 50 +
- ((mStyle & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
- if (weight > 1000) {
- weight = 1000;
- } else if (weight < 100) {
- weight = 100;
- }
- final boolean isItalic = (mStyle & Font.ITALIC) != 0;
- List<Font> fonts = new ArrayList<Font>(mFontFamilies.length);
- for (int i = 0; i < mFontFamilies.length; i++) {
- FontFamily_Delegate ffd = mFontFamilies[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 < mFontFamilies.length - 1;
- FontFamily_Delegate ffd2 = mFontFamilies[++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;
+ public static Typeface_Delegate getDelegate(long nativeTypeface) {
+ return sManager.getDelegate(nativeTypeface);
}
/**
@@ -161,13 +123,13 @@
return 0;
}
- return sManager.addNewDelegate(new Typeface_Delegate(delegate.mFontFamilies, style,
- delegate.mWeight));
+ return sManager.addNewDelegate(
+ new Typeface_Delegate(delegate.mFontFamilies, style, delegate.mWeight));
}
@LayoutlibDelegate
- /*package*/ static long nativeCreateFromTypefaceWithExactStyle(long native_instance,
- int weight, boolean italic) {
+ /*package*/ static long nativeCreateFromTypefaceWithExactStyle(long native_instance, int weight,
+ boolean italic) {
Typeface_Delegate delegate = sManager.getDelegate(native_instance);
if (delegate == null) {
delegate = sManager.getDelegate(sDefaultTypeface);
@@ -178,7 +140,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));
+ return sManager.addNewDelegate(
+ new Typeface_Delegate(delegate.mFontFamilies, style, weight));
}
@LayoutlibDelegate
@@ -262,28 +225,250 @@
}
@LayoutlibDelegate
- /*package*/ static File getSystemFontConfigLocation() {
- return new File(getFontLocation());
+ /*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 makeFamilyFromParsed(FontConfig.Family family,
- Map<String, ByteBuffer> bufferForPath) {
- FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
- for (FontConfig.Font font : family.getFonts()) {
- String fullPathName = "/system/fonts/" + font.getFontName();
- FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, fullPathName,
- font.getWeight(), font.isItalic());
+ /*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
+ */
+ @Nullable
+ public static Typeface createFromDisk(@NonNull BridgeContext context, @NonNull String path,
+ boolean isFramework) {
+ // Check if this is an asset that we've already loaded dynamically
+ Typeface typeface = Typeface.findFromCache(context.getAssets(), path);
+ if (typeface != null) {
+ return typeface;
+ }
+
+ String lowerCaseValue = path.toLowerCase();
+ if (lowerCaseValue.endsWith(SdkConstants.DOT_XML)) {
+ // create a block parser for the file
+ Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
+ RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
+ XmlPullParser parser = null;
+ if (psiParserSupport != null && psiParserSupport) {
+ parser = context.getLayoutlibCallback().getXmlFileParser(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*/);
+ }
+ }
+ }
+
+ if (parser != null) {
+ BridgeXmlBlockParser blockParser =
+ new BridgeXmlBlockParser(parser, context, isFramework);
+ try {
+ FontResourcesParser.FamilyResourceEntry entry =
+ FontResourcesParser.parse(blockParser, context.getResources());
+ typeface = Typeface.createFromResources(entry, context.getAssets(), path);
+ } catch (XmlPullParserException | IOException e) {
+ Bridge.getLog().error(null, "Failed to parse file " + path, e, null /*data*/);
+ } finally {
+ blockParser.ensurePopped();
+ }
+ } else {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ String.format("File %s does not exist (or is not a file)", path),
+ null /*data*/);
+ }
+ } else {
+ typeface = Typeface.createFromResources(context.getAssets(), path, 0);
+ }
+
+ return typeface;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Typeface create(String familyName, int style) {
+ if (familyName != null && Files.exists(Paths.get(familyName))) {
+ // Workaround for b/64137851
+ // Support lib will call this method after failing to create the TypefaceCompat.
+ return Typeface_Delegate.createFromDisk(RenderAction.getCurrentContext(), familyName,
+ false);
+ }
+ return Typeface.create_Original(familyName, style);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Typeface create(Typeface family, int style) {
+ return Typeface.create_Original(family, style);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static Typeface create(Typeface family, int style, boolean isItalic) {
+ return Typeface.create_Original(family, style, isItalic);
+ }
+
// ---- Private delegate/helper methods ----
- public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) {
- mFontFamilies = fontFamilies;
- mStyle = style;
- mWeight = weight;
+ 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;
+ }
+ 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;
+ }
+
+ /**
+ * Return an Iterable of fonts that match the style and variant. The list is ordered
+ * according to preference of fonts.
+ * <p>
+ * The Iterator may contain null when the font failed to load. If null is reached when trying to
+ * render with this list of fonts, then a warning should be logged letting the user know that
+ * some font failed to load.
+ *
+ * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or {@link
+ * FontVariant#ELEGANT}
+ */
+ @NonNull
+ public Iterable<Font> getFonts(final FontVariant variant) {
+ assert variant != FontVariant.NONE;
+
+ return new FontsIterator(mFontFamilies, variant, mWeight, mStyle);
+ }
+
+ private static class FontsIterator implements Iterator<Font>, Iterable<Font> {
+ private final FontFamily_Delegate[] fontFamilies;
+ private final int weight;
+ private final boolean isItalic;
+ private final FontVariant variant;
+
+ private int index = 0;
+
+ private FontsIterator(@NonNull FontFamily_Delegate[] fontFamilies,
+ @NonNull FontVariant variant, int weight, int style) {
+ // Calculate the required weight based on style and weight of this typeface.
+ int boldExtraWeight =
+ ((style & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA);
+ this.weight = Math.min(Math.max(100, weight + 50 + boldExtraWeight), 1000);
+ this.isItalic = (style & Font.ITALIC) != 0;
+ this.fontFamilies = fontFamilies;
+ this.variant = variant;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return index < fontFamilies.length;
+ }
+
+ @Override
+ @Nullable
+ public Font next() {
+ FontFamily_Delegate ffd = fontFamilies[index++];
+ if (ffd == null || !ffd.isValid()) {
+ return null;
+ }
+
+ 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
+ // 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.
+ return null;
+ }
+
+ FontVariant ffdVariant = ffd.getVariant();
+ if (ffdVariant == FontVariant.NONE) {
+ 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 (ffdVariant == variant) {
+ return font;
+ }
+
+ 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
+ @Override
+ public Iterator<Font> iterator() {
+ return this;
+ }
+
+ @Override
+ public Spliterator<Font> spliterator() {
+ return Spliterators.spliterator(iterator(), fontFamilies.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 616784c..d9f8692 100644
--- a/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -35,7 +35,9 @@
import android.graphics.PathMeasure;
import android.graphics.Path_Delegate;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.Region.Op;
+import android.graphics.Shader_Delegate;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
@@ -133,6 +135,12 @@
}
@LayoutlibDelegate
+ static void nSetAntiAlias(long rendererPtr, boolean aa) {
+ VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
+ nativePathRenderer.setAntiAlias(aa);
+ }
+
+ @LayoutlibDelegate
static void nSetAllowCaching(long rendererPtr, boolean allowCaching) {
// ignored
}
@@ -143,6 +151,9 @@
VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
Canvas_Delegate.nSave(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
+ Canvas_Delegate.nClipRect(canvasWrapperPtr,
+ bounds.left, bounds.top, bounds.right, bounds.bottom,
+ Region.Op.INTERSECT.nativeInt);
Canvas_Delegate.nTranslate(canvasWrapperPtr, bounds.left, bounds.top);
if (needsMirroring) {
@@ -1055,6 +1066,7 @@
private Paint mStrokePaint;
private Paint mFillPaint;
private PathMeasure mPathMeasure;
+ private boolean mAntiAlias = true;
private VPathRenderer_Delegate(long rootGroupPtr) {
mRootGroupPtr = rootGroupPtr;
@@ -1164,7 +1176,7 @@
if (mFillPaint == null) {
mFillPaint = new Paint();
mFillPaint.setStyle(Style.FILL);
- mFillPaint.setAntiAlias(true);
+ mFillPaint.setAntiAlias(mAntiAlias);
}
final Paint fillPaint = mFillPaint;
@@ -1175,17 +1187,30 @@
// mFillPaint can not be null at this point so we will have a delegate
assert fillPaintDelegate != null;
fillPaintDelegate.setColorFilter(filterPtr);
+
+ Shader_Delegate shaderDelegate =
+ Shader_Delegate.getDelegate(fullPath.mFillGradient);
+ if (shaderDelegate != null) {
+ // 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);
+ }
+
fillPaintDelegate.setShader(fullPath.mFillGradient);
Path_Delegate.nSetFillType(mRenderPath.mNativePath, fullPath.mFillType);
BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
.getNativeInstance());
+ if (shaderDelegate != null) {
+ // Remove the local matrix
+ shaderDelegate.setLocalMatrix(0);
+ }
}
if (fullPath.mStrokeColor != Color.TRANSPARENT) {
if (mStrokePaint == null) {
mStrokePaint = new Paint();
mStrokePaint.setStyle(Style.STROKE);
- mStrokePaint.setAntiAlias(true);
+ mStrokePaint.setAntiAlias(mAntiAlias);
}
final Paint strokePaint = mStrokePaint;
@@ -1243,6 +1268,10 @@
return matrixScale;
}
+ private void setAntiAlias(boolean aa) {
+ mAntiAlias = aa;
+ }
+
@Override
public void setName(String name) {
}
diff --git a/bridge/src/android/os/Binder_Delegate.java b/bridge/src/android/os/Binder_Delegate.java
new file mode 100644
index 0000000..03596de
--- /dev/null
+++ b/bridge/src/android/os/Binder_Delegate.java
@@ -0,0 +1,54 @@
+/*
+ * 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 android.os;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate overriding selected methods of android.os.Binder
+ *
+ * Through the layoutlib_create tool, selected methods of Binder have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ *
+ */
+public class Binder_Delegate {
+
+ // ---- delegate manager ----
+ private static final DelegateManager<Binder_Delegate> sManager =
+ new DelegateManager<>(Binder_Delegate.class);
+ private static long sFinalizer = -1;
+
+ @LayoutlibDelegate
+ /*package*/ static long getNativeBBinderHolder() {
+ return sManager.addNewDelegate(new Binder_Delegate());
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long getNativeFinalizer() {
+ synchronized (Binder_Delegate.class) {
+ if (sFinalizer == -1) {
+ sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(
+ sManager::removeJavaReferenceFor);
+ }
+ }
+ return sFinalizer;
+ }
+}
diff --git a/bridge/src/android/preference/BridgePreferenceInflater.java b/bridge/src/android/preference/BridgePreferenceInflater.java
index cb56116..3665d86 100644
--- a/bridge/src/android/preference/BridgePreferenceInflater.java
+++ b/bridge/src/android/preference/BridgePreferenceInflater.java
@@ -49,7 +49,8 @@
preference = super.createItem(name, prefix, attrs);
} catch (ClassNotFoundException | InflateException exception) {
// name is probably not a valid preference type
- if ("android.support.v7.preference".equals(prefix) &&
+ if (("android.support.v7.preference".equals(prefix) ||
+ "androidx.preference".equals(prefix)) &&
"SwitchPreferenceCompat".equals(name)) {
preference = super.createItem("SwitchPreference", prefix, attrs);
}
diff --git a/bridge/src/android/text/AndroidBidi_Delegate.java b/bridge/src/android/text/AndroidBidi_Delegate.java
deleted file mode 100644
index 38171dc..0000000
--- a/bridge/src/android/text/AndroidBidi_Delegate.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2011 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.text;
-
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.icu.text.Bidi;
-
-/**
- * Delegate used to provide new implementation for the native methods of {@link AndroidBidi}
- *
- * Through the layoutlib_create tool, the original methods of AndroidBidi have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class AndroidBidi_Delegate {
-
- @LayoutlibDelegate
- /*package*/ static int runBidi(int dir, char[] chars, byte[] charInfo, int count,
- boolean haveInfo) {
-
- switch (dir) {
- case 0: // Layout.DIR_REQUEST_LTR
- dir = Bidi.LTR;
- break;
- case 1: // Layout.DIR_REQUEST_RTL
- dir = Bidi.RTL;
- break;
- case -1: // Layout.DIR_REQUEST_DEFAULT_RTL
- dir = Bidi.LEVEL_DEFAULT_RTL;
- break;
- case -2: // Layout.DIR_REQUEST_DEFAULT_LTR
- dir = Bidi.LEVEL_DEFAULT_LTR;
- break;
- default:
- // Invalid code. Log error, assume LEVEL_DEFAULT_LTR and continue.
- Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Invalid direction flag", null);
- dir = Bidi.LEVEL_DEFAULT_LTR;
- }
- Bidi bidi = new Bidi(chars, 0, null, 0, count, dir);
- if (charInfo != null) {
- for (int i = 0; i < count; ++i)
- charInfo[i] = bidi.getLevelAt(i);
- }
- return bidi.getParaLevel();
- }
-}
diff --git a/bridge/src/android/text/Hyphenator_Delegate.java b/bridge/src/android/text/Hyphenator_Delegate.java
deleted file mode 100644
index 499e58a..0000000
--- a/bridge/src/android/text/Hyphenator_Delegate.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 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.text;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-
-/**
- * Delegate that overrides implementation for certain methods in {@link android.text.Hyphenator}
- * <p/>
- * Through the layoutlib_create tool, selected methods of Hyphenator have been replaced
- * by calls to methods of the same name in this delegate class.
- */
-public class Hyphenator_Delegate {
-
- private static final DelegateManager<Hyphenator_Delegate> sDelegateManager = new
- DelegateManager<Hyphenator_Delegate>(Hyphenator_Delegate.class);
-
- @LayoutlibDelegate
- /*package*/ static File getSystemHyphenatorLocation() {
- // FIXME
- return null;
- }
-
- /*package*/ @SuppressWarnings("UnusedParameters") // TODO implement this.
- static long loadHyphenator(ByteBuffer buffer, int offset, int minPrefix, int minSuffix) {
- return sDelegateManager.addNewDelegate(new Hyphenator_Delegate());
- }
-}
diff --git a/bridge/src/android/text/MeasuredParagraph_Delegate.java b/bridge/src/android/text/MeasuredParagraph_Delegate.java
new file mode 100644
index 0000000..4694890
--- /dev/null
+++ b/bridge/src/android/text/MeasuredParagraph_Delegate.java
@@ -0,0 +1,191 @@
+/*
+ * 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 android.text;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.annotation.NonNull;
+import android.graphics.BidiRenderer;
+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 java.util.ArrayList;
+import java.util.Arrays;
+
+import libcore.util.NativeAllocationRegistry_Delegate;
+
+/**
+ * Delegate that provides implementation for native methods in {@link android.text.MeasuredParagraph}
+ * <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 {
+
+ // ---- 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;
+
+ private long mNativeBuilderPtr;
+
+ @LayoutlibDelegate
+ /*package*/ static long nInitBuilder() {
+ return sBuilderManager.addNewDelegate(new MeasuredParagraphBuilder());
+ }
+
+ /**
+ * Apply style to make native measured text.
+ *
+ * @param nativeBuilderPtr The native MeasuredParagraph 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.
+ * @param isRtl True if the text is RTL.
+ */
+ @LayoutlibDelegate
+ /*package*/ static void nAddStyleRun(long nativeBuilderPtr, long paintPtr, int start,
+ int end, boolean isRtl) {
+ MeasuredParagraphBuilder builder = sBuilderManager.getDelegate(nativeBuilderPtr);
+ if (builder == null) {
+ return;
+ }
+ builder.mRuns.add(new StyleRun(paintPtr, start, end, isRtl));
+ }
+
+ /**
+ * Apply ReplacementRun to make native measured text.
+ *
+ * @param nativeBuilderPtr The native MeasuredParagraph 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.
+ * @param width The width of the replacement.
+ */
+ @LayoutlibDelegate
+ /*package*/ static void nAddReplacementRun(long nativeBuilderPtr, long paintPtr, int start,
+ int end, float width) {
+ MeasuredParagraphBuilder builder = sBuilderManager.getDelegate(nativeBuilderPtr);
+ if (builder == null) {
+ return;
+ }
+ builder.mRuns.add(new ReplacementRun(start, end, width));
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static long nBuildNativeMeasuredParagraph(long nativeBuilderPtr,
+ @NonNull char[] text, boolean computeHyphenation, boolean computeLayout) {
+ MeasuredParagraph_Delegate delegate = new MeasuredParagraph_Delegate();
+ delegate.mNativeBuilderPtr = nativeBuilderPtr;
+ return sManager.addNewDelegate(delegate);
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nFreeBuilder(long nativeBuilderPtr) {
+ 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);
+ RectF bounds =
+ new BidiRenderer(null, paint, text).renderText(index, index + count, bidiFlags,
+ widths, 0, false);
+ 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;
+
+ private StyleRun(long nativePaint, int start, int end, boolean isRtl) {
+ super(start, end);
+ mNativePaint = nativePaint;
+ mIsRtl = isRtl;
+ }
+
+ @Override
+ void addTo(Builder builder) {
+ int bidiFlags = mIsRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+ measureText(mNativePaint, builder.mText, mStart, mEnd - mStart, builder.mWidths,
+ bidiFlags);
+ }
+ }
+
+ private static class ReplacementRun extends Run {
+ private final float mWidth;
+
+ private ReplacementRun(int start, int end, float width) {
+ super(start, end);
+ mWidth = width;
+ }
+
+ @Override
+ void addTo(Builder builder) {
+ builder.mWidths[mStart] = mWidth;
+ 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/text/StaticLayout_Delegate.java b/bridge/src/android/text/StaticLayout_Delegate.java
index 7c59f38..d7cb596 100644
--- a/bridge/src/android/text/StaticLayout_Delegate.java
+++ b/bridge/src/android/text/StaticLayout_Delegate.java
@@ -4,19 +4,15 @@
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.NonNull;
-import android.graphics.BidiRenderer;
-import android.graphics.Paint;
-import android.graphics.Paint_Delegate;
-import android.graphics.RectF;
+import android.annotation.Nullable;
import android.icu.text.BreakIterator;
-import android.icu.util.ULocale;
+import android.text.Layout.BreakStrategy;
+import android.text.Layout.HyphenationFrequency;
import android.text.Primitive.PrimitiveType;
import android.text.StaticLayout.LineBreaks;
-import java.nio.ByteBuffer;
import java.text.CharacterIterator;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import javax.swing.text.Segment;
@@ -40,107 +36,61 @@
new DelegateManager<Builder>(Builder.class);
@LayoutlibDelegate
- /*package*/ static long nNewBuilder() {
- return sBuilderManager.addNewDelegate(new Builder());
+ /*package*/ static long nInit(
+ @BreakStrategy int breakStrategy,
+ @HyphenationFrequency int hyphenationFrequency,
+ boolean isJustified,
+ @Nullable int[] indents,
+ @Nullable int[] leftPaddings,
+ @Nullable int[] rightPaddings) {
+ Builder builder = new Builder();
+ builder.mBreakStrategy = breakStrategy;
+ return sBuilderManager.addNewDelegate(builder);
}
@LayoutlibDelegate
- /*package*/ static void nFreeBuilder(long nativeBuilder) {
- sBuilderManager.removeJavaReferenceFor(nativeBuilder);
+ /*package*/ static void nFinish(long nativePtr) {
+ sBuilderManager.removeJavaReferenceFor(nativePtr);
}
@LayoutlibDelegate
- /*package*/ static void nFinishBuilder(long nativeBuilder) {
- }
+ /*package*/ static int nComputeLineBreaks(
+ /* non zero */ long nativePtr,
- @LayoutlibDelegate
- /*package*/ static long nLoadHyphenator(ByteBuffer buf, int offset, int minPrefix,
- int minSuffix) {
- return Hyphenator_Delegate.loadHyphenator(buf, offset, minPrefix, minSuffix);
- }
+ // Inputs
+ @NonNull char[] text,
+ long measuredTextPtr,
+ int length,
+ float firstWidth,
+ int firstWidthLineCount,
+ float restWidth,
+ @Nullable int[] variableTabStops,
+ int defaultTabStop,
+ int indentsOffset,
- @LayoutlibDelegate
- /*package*/ static void nSetLocales(long nativeBuilder, String locales,
- long[] nativeHyphenators) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- builder.mLocales = locales;
- builder.mNativeHyphenators = nativeHyphenators;
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetIndents(long nativeBuilder, int[] indents) {
- // TODO.
- }
-
- @LayoutlibDelegate
- /*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length,
- float firstWidth, int firstWidthLineCount, float restWidth,
- int[] variableTabStops, int defaultTabStop, int breakStrategy,
- int hyphenationFrequency, boolean isJustified) {
- // TODO: implement justified alignment
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+ // Outputs
+ @NonNull LineBreaks recycle,
+ int recycleLength,
+ @NonNull int[] recycleBreaks,
+ @NonNull float[] recycleWidths,
+ @NonNull float[] recycleAscents,
+ @NonNull float[] recycleDescents,
+ @NonNull int[] recycleFlags,
+ @NonNull float[] charWidths) {
+ Builder builder = sBuilderManager.getDelegate(nativePtr);
if (builder == null) {
- return;
+ return 0;
}
builder.mText = text;
builder.mWidths = new float[length];
builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
- }
- @LayoutlibDelegate
- /*package*/ static float nAddStyleRun(long nativeBuilder, long nativePaint, long nativeTypeface,
- int start, int end, boolean isRtl) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-
- int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
- return builder == null ? 0 :
- measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
- bidiFlags);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddMeasuredRun(long nativeBuilder, int start, int end, float[] widths) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- System.arraycopy(widths, start, builder.mWidths, start, end - start);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static void nAddReplacementRun(long nativeBuilder, int start, int end, float width) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder == null) {
- return;
- }
- builder.mWidths[start] = width;
- Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
- }
-
- @LayoutlibDelegate
- /*package*/ static void nGetWidths(long nativeBuilder, float[] floatsArray) {
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder != null) {
- System.arraycopy(builder.mWidths, 0, floatsArray, 0, builder.mWidths.length);
- }
- }
-
- @LayoutlibDelegate
- /*package*/ static int nComputeLineBreaks(long nativeBuilder,
- LineBreaks recycle, int[] recycleBreaks, float[] recycleWidths,
- int[] recycleFlags, int recycleLength) {
-
- Builder builder = sBuilderManager.getDelegate(nativeBuilder);
- if (builder == null) {
- return 0;
- }
+ MeasuredParagraph_Delegate.computeRuns(measuredTextPtr, builder);
// compute all possible breakpoints.
- int length = builder.mWidths.length;
- BreakIterator it = BreakIterator.getLineInstance(new ULocale(builder.mLocales));
+ BreakIterator it = BreakIterator.getLineInstance();
it.setText((CharacterIterator) new Segment(builder.mText, 0, length));
// average word length in english is 5. So, initialize the possible breaks with a guess.
@@ -171,6 +121,7 @@
builder.mTabStopCalculator);
}
builder.mLineBreaker.computeBreaks(recycle);
+ System.arraycopy(builder.mWidths, 0, charWidths, 0, builder.mWidths.length);
return recycle.breaks.length;
}
@@ -215,26 +166,28 @@
return primitives;
}
- private static float measureText(long nativePaint, char []text, int index, int count,
- float[] widths, int bidiFlags) {
- Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
- RectF bounds = new BidiRenderer(null, paint, text)
- .renderText(index, index + count, bidiFlags, widths, 0, false);
- return bounds.right - bounds.left;
- }
-
// TODO: Rename to LineBreakerRef and move everything other than LineBreaker to LineBreaker.
/**
* Java representation of the native Builder class.
*/
- private static class Builder {
- String mLocales;
+ public static class Builder {
char[] mText;
float[] mWidths;
- LineBreaker mLineBreaker;
- long[] mNativeHyphenators;
- int mBreakStrategy;
- LineWidth mLineWidth;
- TabStops mTabStopCalculator;
+ private LineBreaker mLineBreaker;
+ private int mBreakStrategy;
+ private LineWidth mLineWidth;
+ private TabStops mTabStopCalculator;
+ }
+
+ public abstract static class Run {
+ int mStart;
+ int mEnd;
+
+ Run(int start, int end) {
+ mStart = start;
+ mEnd = end;
+ }
+
+ abstract void addTo(Builder builder);
}
}
diff --git a/bridge/src/android/view/AttachInfo_Accessor.java b/bridge/src/android/view/AttachInfo_Accessor.java
index 4445a22..60c13c0 100644
--- a/bridge/src/android/view/AttachInfo_Accessor.java
+++ b/bridge/src/android/view/AttachInfo_Accessor.java
@@ -16,13 +16,12 @@
package android.view;
-import com.android.layoutlib.bridge.android.BridgeWindow;
-import com.android.layoutlib.bridge.android.BridgeWindowSession;
-
import android.content.Context;
import android.os.Handler;
import android.view.View.AttachInfo;
+import com.android.layoutlib.bridge.util.ReflectionUtils;
+
/**
* Class allowing access to package-protected methods/fields.
*/
@@ -33,8 +32,9 @@
WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
ViewRootImpl root = new ViewRootImpl(context, display);
- AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(),
- display, root, new Handler(), null, context);
+ AttachInfo info = new AttachInfo(ReflectionUtils.createProxy(IWindowSession.class),
+ ReflectionUtils.createProxy(IWindow.class), display, root, new Handler(), null,
+ context);
info.mHasWindowFocus = true;
info.mWindowVisibility = View.VISIBLE;
info.mInTouchMode = false; // this is so that we can display selections.
diff --git a/bridge/src/android/view/BridgeInflater.java b/bridge/src/android/view/BridgeInflater.java
index 58d8c52..84fd0ed 100644
--- a/bridge/src/android/view/BridgeInflater.java
+++ b/bridge/src/android/view/BridgeInflater.java
@@ -31,6 +31,8 @@
import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.util.ReflectionUtils;
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;
@@ -45,25 +47,13 @@
import android.widget.NumberPicker;
import java.io.File;
-import java.util.Arrays;
-import java.util.Collections;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
-import java.util.Set;
+import java.util.function.BiFunction;
-import static com.android.SdkConstants.AUTO_COMPLETE_TEXT_VIEW;
-import static com.android.SdkConstants.BUTTON;
-import static com.android.SdkConstants.CHECKED_TEXT_VIEW;
-import static com.android.SdkConstants.CHECK_BOX;
-import static com.android.SdkConstants.EDIT_TEXT;
-import static com.android.SdkConstants.IMAGE_BUTTON;
-import static com.android.SdkConstants.IMAGE_VIEW;
-import static com.android.SdkConstants.MULTI_AUTO_COMPLETE_TEXT_VIEW;
-import static com.android.SdkConstants.RADIO_BUTTON;
-import static com.android.SdkConstants.SEEK_BAR;
-import static com.android.SdkConstants.SPINNER;
-import static com.android.SdkConstants.TEXT_VIEW;
import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
/**
@@ -72,21 +62,7 @@
public final class BridgeInflater extends LayoutInflater {
private final LayoutlibCallback mLayoutlibCallback;
- /**
- * If true, the inflater will try to replace the framework widgets with the AppCompat versions.
- * Ideally, this should be based on the activity being an AppCompat activity but since that is
- * not trivial to check from layoutlib, we currently base the decision on the current theme
- * being an AppCompat theme.
- */
- private boolean mLoadAppCompatViews;
- /**
- * This set contains the framework views that have an AppCompat version but failed to load.
- * This might happen because not all widgets are contained in all versions of the support
- * library.
- * This will help us to avoid trying to load the AppCompat version multiple times if it
- * doesn't exist.
- */
- private Set<String> mFailedAppCompatViews = new HashSet<>();
+
private boolean mIsInMerge = false;
private ResourceReference mResourceReference;
private Map<View, String> mOpenDrawerLayouts;
@@ -94,15 +70,6 @@
// Keep in sync with the same value in LayoutInflater.
private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme };
- private static final String APPCOMPAT_WIDGET_PREFIX = "android.support.v7.widget.AppCompat";
- /** List of platform widgets that have an AppCompat version */
- private static final Set<String> APPCOMPAT_VIEWS = Collections.unmodifiableSet(
- new HashSet<>(
- Arrays.asList(TEXT_VIEW, IMAGE_VIEW, BUTTON, EDIT_TEXT, SPINNER,
- IMAGE_BUTTON, CHECK_BOX, RADIO_BUTTON, CHECKED_TEXT_VIEW,
- AUTO_COMPLETE_TEXT_VIEW, MULTI_AUTO_COMPLETE_TEXT_VIEW, "RatingBar",
- SEEK_BAR)));
-
/**
* List of class prefixes which are tried first by default.
* <p/>
@@ -113,6 +80,7 @@
"android.webkit.",
"android.app."
};
+ private BiFunction<String, AttributeSet, View> mCustomInflater;
public static String[] getClassPrefixList() {
return sClassPrefixList;
@@ -121,13 +89,9 @@
private BridgeInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
newContext = getBaseContext(newContext);
- if (newContext instanceof BridgeContext) {
- mLayoutlibCallback = ((BridgeContext) newContext).getLayoutlibCallback();
- mLoadAppCompatViews = ((BridgeContext) newContext).isAppCompatTheme();
- } else {
- mLayoutlibCallback = null;
- mLoadAppCompatViews = false;
- }
+ mLayoutlibCallback = (newContext instanceof BridgeContext) ?
+ ((BridgeContext) newContext).getLayoutlibCallback() :
+ null;
}
/**
@@ -140,26 +104,14 @@
super(context);
mLayoutlibCallback = layoutlibCallback;
mConstructorArgs[0] = context;
- mLoadAppCompatViews = context.isAppCompatTheme();
}
@Override
public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
- View view = null;
+ View view = createViewFromCustomInflater(name, attrs);
- try {
- if (mLoadAppCompatViews
- && APPCOMPAT_VIEWS.contains(name)
- && !mFailedAppCompatViews.contains(name)) {
- // We are using an AppCompat theme so try to load the appcompat views
- view = loadCustomView(APPCOMPAT_WIDGET_PREFIX + name, attrs, true);
-
- if (view == null) {
- mFailedAppCompatViews.add(name); // Do not try this one anymore
- }
- }
-
- if (view == null) {
+ if (view == null) {
+ try {
// First try to find a class using the default Android prefixes
for (String prefix : sClassPrefixList) {
try {
@@ -181,19 +133,19 @@
} catch (ClassNotFoundException e) {
// Ignore. We'll try again using the custom view loader below.
}
- }
- // Finally try again using the custom view loader
- if (view == null) {
- view = loadCustomView(name, attrs);
+ // Finally try again using the custom view loader
+ if (view == null) {
+ view = loadCustomView(name, attrs);
+ }
+ } catch (InflateException e) {
+ // Don't catch the InflateException below as that results in hiding the real cause.
+ throw e;
+ } catch (Exception e) {
+ // Wrap the real exception in a ClassNotFoundException, so that the calling method
+ // can deal with it.
+ throw new ClassNotFoundException("onCreateView", e);
}
- } catch (InflateException e) {
- // Don't catch the InflateException below as that results in hiding the real cause.
- throw e;
- } catch (Exception e) {
- // Wrap the real exception in a ClassNotFoundException, so that the calling method
- // can deal with it.
- throw new ClassNotFoundException("onCreateView", e);
}
setupViewInContext(view, attrs);
@@ -201,6 +153,110 @@
return view;
}
+ /**
+ * Finds the createView method in the given customInflaterClass. Since createView is
+ * currently package protected, it will show in the declared class so we iterate up the
+ * hierarchy and return the first instance we find.
+ * The returned method will be accessible.
+ */
+ @NotNull
+ private static Method getCreateViewMethod(Class<?> customInflaterClass) throws NoSuchMethodException {
+ Class<?> current = customInflaterClass;
+ do {
+ try {
+ Method method = current.getDeclaredMethod("createView", View.class, String.class,
+ Context.class, AttributeSet.class, boolean.class, boolean.class,
+ boolean.class, boolean.class);
+ method.setAccessible(true);
+ return method;
+ } catch (NoSuchMethodException ignore) {
+ }
+ current = current.getSuperclass();
+ } while (current != null && current != Object.class);
+
+ throw new NoSuchMethodException();
+ }
+
+ /**
+ * Finds the custom inflater class. If it's defined in the theme, we'll use that one (if the
+ * class does not exist, null is returned).
+ * If {@code viewInflaterClass} is not defined in the theme, we'll try to instantiate
+ * {@code android.support.v7.app.AppCompatViewInflater}
+ */
+ @Nullable
+ private static Class<?> findCustomInflater(@NotNull BridgeContext bc,
+ @NotNull LayoutlibCallback layoutlibCallback) {
+ ResourceValue value = bc.getRenderResources().findItemInTheme("viewInflaterClass", false);
+ String inflaterName = value != null ? value.getValue() : null;
+
+ if (inflaterName != null) {
+ try {
+ return layoutlibCallback.findClass(inflaterName);
+ } catch (ClassNotFoundException ignore) {
+ }
+
+ // 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
+ try {
+ return layoutlibCallback.findClass("android.support.v7.app.AppCompatViewInflater");
+ } catch (ClassNotFoundException ignore) {
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks if there is a custom inflater and, when present, tries to instantiate the view
+ * using it.
+ */
+ @Nullable
+ private View createViewFromCustomInflater(@NotNull String name, @NotNull AttributeSet attrs) {
+ if (mCustomInflater == null) {
+ Context context = getContext();
+ context = getBaseContext(context);
+ if (context instanceof BridgeContext) {
+ BridgeContext bc = (BridgeContext) context;
+ Class<?> inflaterClass = findCustomInflater(bc, mLayoutlibCallback);
+
+ if (inflaterClass != null) {
+ try {
+ Constructor<?> constructor = inflaterClass.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ Object inflater = constructor.newInstance();
+ Method method = getCreateViewMethod(inflaterClass);
+ Context finalContext = context;
+ mCustomInflater = (viewName, attributeSet) -> {
+ try {
+ return (View) method.invoke(inflater, null, viewName, finalContext,
+ attributeSet,
+ false,
+ false /*readAndroidTheme*/, // No need after L
+ true /*readAppTheme*/,
+ true /*wrapContext*/);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ assert false : "Call to createView failed";
+ }
+ return null;
+ };
+ } catch (InvocationTargetException | IllegalAccessException |
+ NoSuchMethodException | InstantiationException ignore) {
+ }
+ }
+ }
+
+ if (mCustomInflater == null) {
+ // There is no custom inflater. We'll create a nop custom inflater to avoid the
+ // penalty of trying to instantiate again
+ mCustomInflater = (s, attributeSet) -> null;
+ }
+ }
+
+ return mCustomInflater.apply(name, attrs);
+ }
+
@Override
public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
diff --git a/bridge/src/android/view/IWindowManagerImpl.java b/bridge/src/android/view/IWindowManagerImpl.java
index 82cec66..b1d9151 100644
--- a/bridge/src/android/view/IWindowManagerImpl.java
+++ b/bridge/src/android/view/IWindowManagerImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import android.app.IAssistDataReceiver;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.GraphicBuffer;
@@ -28,8 +29,8 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.DisplayMetrics;
+import android.view.RemoteAnimationAdapter;
-import com.android.internal.app.IAssistScreenshotReceiver;
import com.android.internal.os.IResultReceiver;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IShortcutService;
@@ -76,6 +77,11 @@
// ---- 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
@@ -156,12 +162,6 @@
}
@Override
- public boolean inKeyguardRestrictedInputMode() throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
public boolean inputMethodClientHasFocus(IInputMethodClient arg0) throws RemoteException {
// TODO Auto-generated method stub
return false;
@@ -243,6 +243,10 @@
}
@Override
+ public void overridePendingAppTransitionRemote(RemoteAnimationAdapter adapter) {
+ }
+
+ @Override
public void prepareAppTransition(int arg0, boolean arg1) throws RemoteException {
// TODO Auto-generated method stub
@@ -261,7 +265,7 @@
}
@Override
- public boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver)
+ public boolean requestAssistScreenshot(IAssistDataReceiver receiver)
throws RemoteException {
// TODO Auto-generated method stub
return false;
@@ -342,7 +346,7 @@
}
@Override
- public void setScreenCaptureDisabled(int userId, boolean disabled) {
+ public void refreshScreenCaptureDisabled(int userId) {
// TODO Auto-generated method stub
}
@@ -383,6 +387,15 @@
}
@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;
@@ -422,7 +435,8 @@
}
@Override
- public void dismissKeyguard(IKeyguardDismissCallback callback) throws RemoteException {
+ public void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message)
+ throws RemoteException {
}
@Override
@@ -469,10 +483,6 @@
}
@Override
- public void setDockedStackResizing(boolean resizing) throws RemoteException {
- }
-
- @Override
public void endProlongedAnimations() {
}
@@ -507,7 +517,7 @@
throws RemoteException {}
@Override
- public void createInputConsumer(String name, InputChannel inputChannel)
+ public void createInputConsumer(IBinder token, String name, InputChannel inputChannel)
throws RemoteException {}
@Override
@@ -521,14 +531,6 @@
}
@Override
- public void enableSurfaceTrace(ParcelFileDescriptor fd) throws RemoteException {
- }
-
- @Override
- public void disableSurfaceTrace() throws RemoteException {
- }
-
- @Override
public Region getCurrentImeTouchRegion() throws RemoteException {
return null;
}
@@ -545,6 +547,23 @@
}
@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/MenuInflater_Delegate.java b/bridge/src/android/view/MenuInflater_Delegate.java
index 08a97d6..d16d851 100644
--- a/bridge/src/android/view/MenuInflater_Delegate.java
+++ b/bridge/src/android/view/MenuInflater_Delegate.java
@@ -42,7 +42,6 @@
* ViewInfo}, we check the corresponding view key in the menu item for the view and add it
*/
public class MenuInflater_Delegate {
-
@LayoutlibDelegate
/*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem,
AttributeSet attrs) {
@@ -56,10 +55,18 @@
return;
}
}
- // This means that Bridge did not take over the instantiation of some object properly.
- // This is most likely a bug in the LayoutLib code.
- Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
- "Action Bar Menu rendering may be incorrect.", null);
+
+ String menuItemName = menuItem != null ? menuItem.getClass().getName() : null;
+ if (menuItemName == null ||
+ !menuItemName.startsWith("android.support.") ||
+ !menuItemName.startsWith("androidx.")) {
+ // This means that Bridge did not take over the instantiation of some object properly.
+ // This is most likely a bug in the LayoutLib code.
+ // We suppress this error for AppCompat menus since we do not support them in the menu
+ // editor yet.
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+ "Action Bar Menu rendering may be incorrect.", null);
+ }
}
diff --git a/bridge/src/android/view/RectShadowPainter.java b/bridge/src/android/view/RectShadowPainter.java
index 5665d4f..88771a7 100644
--- a/bridge/src/android/view/RectShadowPainter.java
+++ b/bridge/src/android/view/RectShadowPainter.java
@@ -19,8 +19,10 @@
import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.layoutlib.bridge.impl.ResourceHelper;
+import android.graphics.BaseCanvas_Delegate;
import android.graphics.Canvas;
import android.graphics.Canvas_Delegate;
+import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Outline;
import android.graphics.Paint;
@@ -46,7 +48,8 @@
private static final int END_COLOR = ResourceHelper.getColor("#03000000");
private static final float PERPENDICULAR_ANGLE = 90f;
- public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+ public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas,
+ float alpha) {
Rect outline = new Rect();
if (!viewOutline.getRect(outline)) {
assert false : "Outline is not a rect shadow";
@@ -74,9 +77,16 @@
edgePaint.setAntiAlias(false);
float outerArcRadius = radius + shadowSize;
int[] colors = {START_COLOR, START_COLOR, END_COLOR};
+ if (alpha != 1f) {
+ // Correct colors using the given component alpha
+ for (int i = 0; i < colors.length; i++) {
+ colors[i] = Color.argb((int) (Color.alpha(colors[i]) * alpha), Color.red(colors[i]),
+ Color.green(colors[i]), Color.blue(colors[i]));
+ }
+ }
cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
- edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, START_COLOR, END_COLOR,
+ edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, colors[0], colors[2],
TileMode.CLAMP));
Path path = new Path();
path.setFillType(FillType.EVEN_ODD);
@@ -184,7 +194,8 @@
/**
* Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks.
* <p/>
- * This is required because {@link Canvas_Delegate#native_drawRect(long, float, float, float,
+ * This is required because {@link BaseCanvas_Delegate#native_drawRect(long, float, float,
+ * float,
* float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up
* drawing empty rectangles, which results in IllegalArgumentException.
*/
diff --git a/bridge/src/android/view/ShadowPainter.java b/bridge/src/android/view/ShadowPainter.java
index f09fffd..788c6c3 100644
--- a/bridge/src/android/view/ShadowPainter.java
+++ b/bridge/src/android/view/ShadowPainter.java
@@ -41,14 +41,16 @@
* @param source the source image
* @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
* #SMALL_SHADOW_SIZE}}
+ * @param alpha alpha value to apply to the shadow
*
* @return an image with the shadow painted in or the source image if shadowSize <= 1
*/
@NonNull
- public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
+ public static BufferedImage createDropShadow(BufferedImage source, int shadowSize, float
+ alpha) {
shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
- return createDropShadow(source, shadowSize, 0.7f, 0);
+ return createDropShadow(source, shadowSize, 0.7f * alpha, 0);
}
/**
diff --git a/bridge/src/android/view/ViewGroup_Delegate.java b/bridge/src/android/view/ViewGroup_Delegate.java
index 4b760a7..10a4f31 100644
--- a/bridge/src/android/view/ViewGroup_Delegate.java
+++ b/bridge/src/android/view/ViewGroup_Delegate.java
@@ -67,31 +67,33 @@
Outline outline) {
float elevation = getElevation(child, parent);
if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
- RectShadowPainter.paintShadow(outline, elevation, canvas);
+ RectShadowPainter.paintShadow(outline, elevation, canvas, child.getAlpha());
return;
}
BufferedImage shadow = null;
if (outline.mPath != null) {
- shadow = getPathShadow(outline, canvas, elevation);
+ shadow = getPathShadow(outline, canvas, elevation, child.getAlpha());
}
if (shadow == null) {
return;
}
Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false,
Density.getEnum(canvas.getDensity()));
+ canvas.save();
Rect clipBounds = canvas.getClipBounds();
Rect newBounds = new Rect(clipBounds);
newBounds.inset((int)-elevation, (int)-elevation);
canvas.clipRect(newBounds, Op.REPLACE);
canvas.drawBitmap(bitmap, 0, 0, null);
- canvas.clipRect(clipBounds, Op.REPLACE);
+ canvas.restore();
}
private static float getElevation(View child, ViewGroup parent) {
return child.getZ() - parent.getZ();
}
- private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) {
+ private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation,
+ float alpha) {
Rect clipBounds = canvas.getClipBounds();
if (clipBounds.isEmpty()) {
return null;
@@ -101,7 +103,7 @@
Graphics2D graphics = image.createGraphics();
graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
graphics.dispose();
- return ShadowPainter.createDropShadow(image, (int) elevation);
+ return ShadowPainter.createDropShadow(image, (int) elevation, alpha);
}
// Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
@@ -144,11 +146,11 @@
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
- if (!childHasIdentityMatrix) {
- canvas.translate(-transX, -transY);
- canvas.concat(child.getMatrix());
- canvas.translate(transX, transY);
- }
+ }
+ if (!childHasIdentityMatrix) {
+ canvas.translate(-transX, -transY);
+ canvas.concat(child.getMatrix());
+ canvas.translate(transX, transY);
}
}
diff --git a/bridge/src/android/view/View_Delegate.java b/bridge/src/android/view/View_Delegate.java
index 408ec54..5d39e4c 100644
--- a/bridge/src/android/view/View_Delegate.java
+++ b/bridge/src/android/view/View_Delegate.java
@@ -16,10 +16,13 @@
package android.view;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.content.Context;
+import android.graphics.Canvas;
import android.os.IBinder;
/**
@@ -44,4 +47,50 @@
}
return null;
}
+
+ @LayoutlibDelegate
+ /*package*/ static void draw(View thisView, Canvas canvas) {
+ try {
+ // This code is run within a catch to prevent misbehaving components from breaking
+ // all the layout.
+ thisView.draw_Original(canvas);
+ } catch (Throwable t) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "View draw failed", t, null);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static boolean draw(
+ View thisView, Canvas canvas, ViewGroup parent, long drawingTime) {
+ try {
+ // This code is run within a catch to prevent misbehaving components from breaking
+ // all the layout.
+ return thisView.draw_Original(canvas, parent, drawingTime);
+ } catch (Throwable t) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "View draw failed", t, null);
+ }
+ return false;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void measure(View thisView, int widthMeasureSpec, int heightMeasureSpec) {
+ try {
+ // This code is run within a catch to prevent misbehaving components from breaking
+ // all the layout.
+ thisView.measure_Original(widthMeasureSpec, heightMeasureSpec);
+ } catch (Throwable t) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "View measure failed", t, null);
+ }
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void layout(View thisView, int l, int t, int r, int b) {
+ try {
+ // This code is run within a catch to prevent misbehaving components from breaking
+ // all the layout.
+ thisView.layout_Original(l, t, r, b);
+ } catch (Throwable th) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN, "View layout failed", th, null);
+ }
+ }
}
diff --git a/bridge/src/android/view/accessibility/AccessibilityManager.java b/bridge/src/android/view/accessibility/AccessibilityManager.java
index 11cb046..84b4064 100644
--- a/bridge/src/android/view/accessibility/AccessibilityManager.java
+++ b/bridge/src/android/view/accessibility/AccessibilityManager.java
@@ -17,6 +17,7 @@
package android.view.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -24,6 +25,7 @@
import android.os.Handler;
import android.view.IWindow;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent.EventType;
import java.util.Collections;
import java.util.List;
@@ -90,6 +92,60 @@
public void onHighTextContrastStateChanged(boolean enabled);
}
+ /**
+ * Policy to inject behavior into the accessibility manager.
+ *
+ * @hide
+ */
+ public interface AccessibilityPolicy {
+ /**
+ * Checks whether accessibility is enabled.
+ *
+ * @param accessibilityEnabled Whether the accessibility layer is enabled.
+ * @return whether accessibility is enabled.
+ */
+ boolean isEnabled(boolean accessibilityEnabled);
+
+ /**
+ * Notifies the policy for an accessibility event.
+ *
+ * @param event The event.
+ * @param accessibilityEnabled Whether the accessibility layer is enabled.
+ * @param relevantEventTypes The events relevant events.
+ * @return The event to dispatch or null.
+ */
+ @Nullable AccessibilityEvent onAccessibilityEvent(@NonNull AccessibilityEvent event,
+ boolean accessibilityEnabled, @EventType int relevantEventTypes);
+
+ /**
+ * Gets the list of relevant events.
+ *
+ * @param relevantEventTypes The relevant events.
+ * @return The relevant events to report.
+ */
+ @EventType int getRelevantEventTypes(@EventType int relevantEventTypes);
+
+ /**
+ * Gets the list of installed services to report.
+ *
+ * @param installedService The installed services.
+ * @return The services to report.
+ */
+ @NonNull List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
+ @Nullable List<AccessibilityServiceInfo> installedService);
+
+ /**
+ * Gets the list of enabled accessibility services.
+ *
+ * @param feedbackTypeFlags The feedback type to query for.
+ * @param enabledService The enabled services.
+ * @return The services to report.
+ */
+ @Nullable List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
+ @FeedbackType int feedbackTypeFlags,
+ @Nullable List<AccessibilityServiceInfo> enabledService);
+ }
+
private final IAccessibilityManagerClient.Stub mClient =
new IAccessibilityManagerClient.Stub() {
public void setState(int state) {
@@ -159,6 +215,18 @@
}
/**
+ * Returns whether there are observers registered for this event type. If
+ * this method returns false you shuold not generate events of this type
+ * to conserve resources.
+ *
+ * @param type The event type.
+ * @return Whether the event is being observed.
+ */
+ public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+ return false;
+ }
+
+ /**
* Requests interruption of the accessibility feedback from all accessibility services.
*/
public void interrupt() {
diff --git a/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java b/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
index 7c98847..b2a183b 100644
--- a/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
+++ b/bridge/src/android/view/inputmethod/InputMethodManager_Delegate.java
@@ -16,10 +16,10 @@
package android.view.inputmethod;
-import com.android.layoutlib.bridge.android.BridgeIInputMethodManager;
+import com.android.internal.view.IInputMethodManager;
+import com.android.layoutlib.bridge.util.ReflectionUtils;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import android.content.Context;
import android.os.Looper;
@@ -39,8 +39,8 @@
synchronized (InputMethodManager.class) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm == null) {
- imm = new InputMethodManager(
- new BridgeIInputMethodManager(), Looper.getMainLooper());
+ imm = new InputMethodManager(ReflectionUtils.createProxy(IInputMethodManager.class),
+ Looper.getMainLooper());
InputMethodManager.sInstance = imm;
}
return imm;
diff --git a/bridge/src/com/android/layoutlib/bridge/Bridge.java b/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 93fd005..5dca8e7 100644
--- a/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -56,6 +56,7 @@
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
+import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantLock;
import libcore.io.MemoryMappedFile_Delegate;
@@ -104,10 +105,9 @@
private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
- new HashMap<>();
+ new WeakHashMap<>();
private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
-
- new HashMap<>();
+ new WeakHashMap<>();
private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>();
private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
@@ -355,6 +355,8 @@
// dispose of the default typeface.
Typeface_Delegate.resetDefaults();
Typeface.sDynamicTypefaceCache.evictAll();
+ sProject9PatchCache.clear();
+ sProjectBitmapCache.clear();
return true;
}
@@ -397,7 +399,7 @@
// get the real cause of the exception.
Throwable t2 = t;
while (t2.getCause() != null) {
- t2 = t.getCause();
+ t2 = t2.getCause();
}
return new BridgeRenderSession(null,
ERROR_UNKNOWN.createResult(t2.getMessage(), t));
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 55636eb..b33344c 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -22,6 +22,8 @@
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.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;
@@ -40,7 +42,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Notification;
import android.app.SystemServiceRegistry_Accessor;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -86,7 +87,6 @@
import android.view.BridgeInflater;
import android.view.Display;
import android.view.DisplayAdjustments;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -101,6 +101,7 @@
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;
@@ -119,6 +120,9 @@
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));
@@ -610,45 +614,35 @@
@Override
public Object getSystemService(String service) {
- if (LAYOUT_INFLATER_SERVICE.equals(service)) {
- return mBridgeInflater;
+ switch (service) {
+ case LAYOUT_INFLATER_SERVICE:
+ return mBridgeInflater;
+
+ case TEXT_SERVICES_MANAGER_SERVICE:
+ // we need to return a valid service to avoid NPE
+ return TextServicesManager.getInstance();
+
+ case WINDOW_SERVICE:
+ return mWindowManager;
+
+ case POWER_SERVICE:
+ return new PowerManager(this, new BridgePowerManager(), new Handler());
+
+ case DISPLAY_SERVICE:
+ return mDisplayManager;
+
+ case ACCESSIBILITY_SERVICE:
+ return AccessibilityManager.getInstance(this);
+
+ case INPUT_METHOD_SERVICE: // needed by SearchView
+ case AUTOFILL_MANAGER_SERVICE:
+ case AUDIO_SERVICE:
+ case TEXT_CLASSIFICATION_SERVICE:
+ return null;
+ default:
+ assert false : "Unsupported Service: " + service;
}
- if (TEXT_SERVICES_MANAGER_SERVICE.equals(service)) {
- // we need to return a valid service to avoid NPE
- return TextServicesManager.getInstance();
- }
-
- if (WINDOW_SERVICE.equals(service)) {
- return mWindowManager;
- }
-
- // needed by SearchView
- if (INPUT_METHOD_SERVICE.equals(service)) {
- return null;
- }
-
- if (POWER_SERVICE.equals(service)) {
- return new PowerManager(this, new BridgePowerManager(), new Handler());
- }
-
- if (DISPLAY_SERVICE.equals(service)) {
- return mDisplayManager;
- }
-
- if (ACCESSIBILITY_SERVICE.equals(service)) {
- return AccessibilityManager.getInstance(this);
- }
-
- if (AUTOFILL_MANAGER_SERVICE.equals(service)) {
- return null;
- }
-
- if (AUDIO_SERVICE.equals(service)) {
- return null;
- }
-
- assert false : "Unsupported Service: " + service;
return null;
}
@@ -657,13 +651,13 @@
return SystemServiceRegistry_Accessor.getSystemServiceName(serviceClass);
}
- @Override
- public final BridgeTypedArray obtainStyledAttributes(int[] attrs) {
- return obtainStyledAttributes(0, attrs);
- }
- @Override
- public final BridgeTypedArray obtainStyledAttributes(int resId, int[] attrs)
+ /**
+ * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+ * original Context the chance to override the theme when needed.
+ */
+ @Nullable
+ public final BridgeTypedArray internalObtainStyledAttributes(int resId, int[] attrs)
throws Resources.NotFoundException {
StyleResourceValue style = null;
// get the StyleResourceValue based on the resId;
@@ -715,18 +709,24 @@
return typeArrayAndPropertiesPair.getFirst();
}
- @Override
- public final BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
- return obtainStyledAttributes(set, attrs, 0, 0);
- }
-
- @Override
- public BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
+ /**
+ * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+ * original Context the chance to override the theme when needed.
+ */
+ @Nullable
+ public BridgeTypedArray internalObtainStyledAttributes(@Nullable AttributeSet set, int[] attrs,
int defStyleAttr, int defStyleRes) {
PropertiesMap defaultPropMap = null;
boolean isPlatformFile = true;
+ // 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;
+
// Hint: for XmlPullParser, attach source //DEVICE_SRC/dalvik/libcore/xml/src/java
if (set instanceof BridgeXmlBlockParser) {
BridgeXmlBlockParser parser;
@@ -739,6 +739,8 @@
defaultPropMap = mDefaultPropMaps.computeIfAbsent(key, k -> new PropertiesMap());
}
+ resolver = parser::getNamespace;
+ currentFileNamespace = ResourceNamespace.fromBoolean(parser.isPlatformFile());
} else if (set instanceof BridgeLayoutParamsMapAttributes) {
// this is only for temp layout params generated dynamically, so this is never
// platform content.
@@ -807,15 +809,11 @@
}
defaultPropMap.put("style", new Property(defStyleName, item.getValue()));
}
- } else {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
- String.format(
- "Failed to find style '%s' in current theme",
- defStyleAttribute.getFirst()),
- null);
}
}
- } else if (defStyleRes != 0) {
+ }
+
+ if (defStyleValues == null && defStyleRes != 0) {
StyleResourceValue item = getStyleByDynamicId(defStyleRes);
if (item != null) {
defStyleValues = item;
@@ -968,10 +966,19 @@
ta.bridgeSetValue(index, attrName, frameworkAttr, attributeHolder.resourceId,
defaultValue);
} else {
- // there is a value in the XML, but we need to resolve it in case it's
+ // There is a value in the XML, but we need to resolve it in case it's
// referencing another resource or a theme value.
- ta.bridgeSetValue(index, attrName, frameworkAttr, attributeHolder.resourceId,
- mRenderResources.resolveValue(null, attrName, value, isPlatformFile));
+ ResourceValue dummy =
+ new ResourceValue(
+ currentFileNamespace,
+ null,
+ attrName,
+ value);
+ dummy.setNamespaceLookup(resolver);
+
+ ta.bridgeSetValue(
+ index, attrName, frameworkAttr, attributeHolder.resourceId,
+ mRenderResources.resolveResValue(dummy));
}
}
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
deleted file mode 100644
index 4805ed1..0000000
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2011 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.internal.inputmethod.IInputContentUriToken;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethodClient;
-import com.android.internal.view.IInputMethodManager;
-import com.android.internal.view.InputBindResult;
-
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.text.style.SuggestionSpan;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodSubtype;
-
-import java.util.List;
-
-/**
- * Basic implementation of IInputMethodManager that does nothing.
- *
- */
-public class BridgeIInputMethodManager implements IInputMethodManager {
-
- @Override
- public void addClient(IInputMethodClient arg0, IInputContext arg1, int arg2, int arg3)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void finishInput(IInputMethodClient arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public InputMethodSubtype getCurrentInputMethodSubtype() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List<InputMethodInfo> getEnabledInputMethodList() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String arg0,
- boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List<InputMethodInfo> getInputMethodList() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public InputMethodSubtype getLastInputMethodSubtype() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public List getShortcutInputMethodsAndSubtypes() throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void hideMySoftInput(IBinder arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean hideSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean notifySuggestionPicked(SuggestionSpan arg0, String arg1, int arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void registerSuggestionSpansForNotification(SuggestionSpan[] arg0)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void removeClient(IInputMethodClient arg0) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setAdditionalInputMethodSubtypes(String arg0, InputMethodSubtype[] arg1)
- throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public boolean setCurrentInputMethodSubtype(InputMethodSubtype arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void setImeWindowStatus(IBinder arg0, IBinder arg1, int arg2, int arg3)
- throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void setInputMethod(IBinder arg0, String arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void setInputMethodAndSubtype(IBinder arg0, String arg1, InputMethodSubtype arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean setInputMethodEnabled(String arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public void showInputMethodAndSubtypeEnablerFromClient(IInputMethodClient arg0, String arg1)
- throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void showInputMethodPickerFromClient(IInputMethodClient arg0,
- int arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void showMySoftInput(IBinder arg0, int arg1) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public boolean showSoftInput(IInputMethodClient arg0, int arg1, ResultReceiver arg2)
- throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean switchToLastInputMethod(IBinder arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public boolean shouldOfferSwitchingToNextInputMethod(IBinder arg0) throws RemoteException {
- // TODO Auto-generated method stub
- return false;
- }
-
- @Override
- public int getInputMethodWindowVisibleHeight() throws RemoteException {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public void notifyUserAction(int sequenceNumber) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public void updateStatusIcon(IBinder arg0, String arg1, int arg2) throws RemoteException {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void clearLastInputMethodWindowForTransition(IBinder arg0) throws RemoteException {
- // TODO Auto-generated method stub
- }
-
- @Override
- public InputBindResult startInputOrWindowGainedFocus(
- /* @InputMethodClient.StartInputReason */ int startInputReason,
- IInputMethodClient client, IBinder windowToken, int controlFlags,
- /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
- int windowFlags, EditorInfo attribute, IInputContext inputContext,
- /* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags)
- throws RemoteException {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public IBinder asBinder() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public IInputContentUriToken createInputContentUriToken(IBinder token, Uri contentUri,
- String packageName) {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public void reportFullscreenMode(IBinder token, boolean fullscreen) {
- // TODO Auto-generated method stub
- }
-}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java
index f5912e7..3ddf93a 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeLayoutParamsMapAttributes.java
@@ -56,6 +56,11 @@
}
@Override
+ public String getAttributeNamespace(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public String getAttributeName(int index) {
throw new UnsupportedOperationException();
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 98937ef..73a9a62 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -26,12 +25,11 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ChangedPackages;
-import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
@@ -52,10 +50,11 @@
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
import android.os.Handler;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
+
import java.util.List;
/**
@@ -112,6 +111,11 @@
}
@Override
+ public Intent getCarLaunchIntentForPackage(String packageName) {
+ return null;
+ }
+
+ @Override
public int[] getPackageGids(String packageName) throws NameNotFoundException {
return new int[0];
}
@@ -430,6 +434,11 @@
}
@Override
+ public ResolveInfo resolveServiceAsUser(Intent intent, int flags, int userId) {
+ return null;
+ }
+
+ @Override
public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
return null;
}
@@ -611,16 +620,6 @@
}
@Override
- public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
- String installerPackageName) {
- }
-
- @Override
- public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
- String installerPackageName) {
- }
-
- @Override
public int installExistingPackage(String packageName) throws NameNotFoundException {
return 0;
}
@@ -835,8 +834,8 @@
}
@Override
- public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
- int userId) {
+ public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
+ PersistableBundle appExtras, PersistableBundle launcherExtras, String dialogMessage) {
return new String[]{};
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
index ed428ec..b87ca3b 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -117,16 +117,6 @@
}
@Override
- public void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setTemporaryScreenBrightnessSettingOverride(int arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
public void setStayOnSetting(int arg0) throws RemoteException {
// pass for now.
}
@@ -176,4 +166,9 @@
public int getLastShutdownReason() {
return PowerManager.SHUTDOWN_REASON_UNKNOWN;
}
+
+ @Override
+ public void setDozeAfterScreenOff(boolean mode) throws RemoteException {
+ // pass for now.
+ }
}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
deleted file mode 100644
index ffbe7c4..0000000
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2010 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.internal.os.IResultReceiver;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.MergedConfiguration;
-import android.view.DragEvent;
-import android.view.IWindow;
-
-/**
- * Implementation of {@link IWindow} to pass to the AttachInfo.
- */
-public final class BridgeWindow implements IWindow {
-
- @Override
- public void dispatchAppVisibility(boolean arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void dispatchGetNewSurface() throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2)
- throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void resized(Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, Rect rect6,
- boolean b, MergedConfiguration mergedConfig, Rect rect7, boolean b2, boolean b3, int i0)
- throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void moved(int arg0, int arg1) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
- boolean sync) {
- // pass for now.
- }
-
- @Override
- public void dispatchWallpaperCommand(String action, int x, int y,
- int z, Bundle extras, boolean sync) {
- // pass for now.
- }
-
- @Override
- public void closeSystemDialogs(String reason) {
- // pass for now.
- }
-
- @Override
- public void dispatchDragEvent(DragEvent event) {
- // pass for now.
- }
-
- @Override
- public void updatePointerIcon(float x, float y) {
- // pass for now
- }
-
- @Override
- public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
- int localValue, int localChanges) {
- // pass for now.
- }
-
- @Override
- public void dispatchWindowShown() {
- }
-
- @Override
- public void requestAppKeyboardShortcuts(
- IResultReceiver receiver, int deviceId) throws RemoteException {
- }
-
- @Override
- public void dispatchPointerCaptureChanged(boolean hasCapture) {
- }
-
- @Override
- public IBinder asBinder() {
- // pass for now.
- return null;
- }
-
-}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
deleted file mode 100644
index 2c88394..0000000
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright (C) 2010 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 android.content.ClipData;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.MergedConfiguration;
-import android.view.IWindow;
-import android.view.IWindowId;
-import android.view.IWindowSession;
-import android.view.InputChannel;
-import android.view.Surface;
-import android.view.SurfaceView;
-import android.view.WindowManager.LayoutParams;
-
-/**
- * Implementation of {@link IWindowSession} so that mSession is not null in
- * the {@link SurfaceView}.
- */
-public final class BridgeWindowSession implements IWindowSession {
-
- @Override
- public int add(IWindow arg0, int seq, LayoutParams arg1, int arg2, Rect arg3, Rect arg4,
- InputChannel outInputchannel)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public int addToDisplay(IWindow arg0, int seq, LayoutParams arg1, int arg2, int displayId,
- Rect arg3, Rect arg4, Rect arg5, InputChannel outInputchannel)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public int addWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
- Rect arg3, Rect arg4)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public int addToDisplayWithoutInputChannel(IWindow arg0, int seq, LayoutParams arg1, int arg2,
- int displayId, Rect arg3, Rect arg4)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public void finishDrawing(IWindow arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public boolean getInTouchMode() throws RemoteException {
- // pass for now.
- return false;
- }
-
- @Override
- public boolean performHapticFeedback(IWindow window, int effectId, boolean always) {
- // pass for now.
- return false;
- }
-
- @Override
- public int relayout(IWindow iWindow, int i, LayoutParams layoutParams, int i2,
- int i3, int i4, int i5, Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5,
- Rect rect6, Rect rect7, MergedConfiguration mergedConfig, Surface surface)
- throws RemoteException {
- // pass for now.
- return 0;
- }
-
- @Override
- public boolean outOfMemory(IWindow window) throws RemoteException {
- return false;
- }
-
- @Override
- public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
- // pass for now.
- }
-
- @Override
- public void remove(IWindow arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setInTouchMode(boolean arg0) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException {
- // pass for now.
- }
-
- @Override
- public void setInsets(IWindow window, int touchable, Rect contentInsets,
- Rect visibleInsets, Region touchableRegion) {
- // pass for now.
- }
-
- @Override
- public IBinder prepareDrag(IWindow window, int flags,
- int thumbnailWidth, int thumbnailHeight, Surface outSurface)
- throws RemoteException {
- // pass for now
- return null;
- }
-
- @Override
- public boolean performDrag(IWindow window, IBinder dragToken,
- int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
- ClipData data)
- throws RemoteException {
- // pass for now
- return false;
- }
-
- @Override
- public boolean startMovingTask(IWindow window, float startX, float startY)
- throws RemoteException {
- // pass for now
- return false;
- }
-
- @Override
- public void reportDropResult(IWindow window, boolean consumed) throws RemoteException {
- // pass for now
- }
-
- @Override
- public void cancelDragAndDrop(IBinder dragToken) throws RemoteException {
- // pass for now
- }
-
- @Override
- public void dragRecipientEntered(IWindow window) throws RemoteException {
- // pass for now
- }
-
- @Override
- public void dragRecipientExited(IWindow window) throws RemoteException {
- // pass for now
- }
-
- @Override
- public void setWallpaperPosition(IBinder window, float x, float y,
- float xStep, float yStep) {
- // pass for now.
- }
-
- @Override
- public void wallpaperOffsetsComplete(IBinder window) {
- // pass for now.
- }
-
- @Override
- public void setWallpaperDisplayOffset(IBinder windowToken, int x, int y) {
- // pass for now.
- }
-
- @Override
- public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
- int z, Bundle extras, boolean sync) {
- // pass for now.
- return null;
- }
-
- @Override
- public void wallpaperCommandComplete(IBinder window, Bundle result) {
- // pass for now.
- }
-
- @Override
- public IBinder asBinder() {
- // pass for now.
- return null;
- }
-
- @Override
- public void onRectangleOnScreenRequested(IBinder window, Rect rectangle) {
- // pass for now.
- }
-
- @Override
- public IWindowId getWindowId(IBinder window) throws RemoteException {
- // pass for now.
- return null;
- }
-
- @Override
- public void pokeDrawLock(IBinder window) {
- // pass for now.
- }
-
- @Override
- public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
- // pass for now.
- }
-
- @Override
- public void updatePointerIcon(IWindow window) {
- // pass for now.
- }
-}
diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
index f18cd48..d50117e 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java
@@ -159,7 +159,7 @@
@Override
public String getNamespace(String prefix) {
- throw new RuntimeException("getNamespace() not supported");
+ return mParser.getNamespace(prefix);
}
@Override
diff --git a/bridge/src/com/android/layoutlib/bridge/android/NopAttributeSet.java b/bridge/src/com/android/layoutlib/bridge/android/NopAttributeSet.java
index fbb12d5..cd971ee 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/NopAttributeSet.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/NopAttributeSet.java
@@ -28,6 +28,11 @@
}
@Override
+ public String getAttributeNamespace(int index) {
+ return null;
+ }
+
+ @Override
public String getAttributeName(int index) {
return null;
}
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 aa873a6..aedcc48 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
@@ -31,14 +31,22 @@
* Utility class for working with the design support lib.
*/
public class DesignLibUtil {
-
- private static final String PKG_PREFIX = "android.support.design.widget.";
- public static final String CN_COORDINATOR_LAYOUT = PKG_PREFIX + "CoordinatorLayout";
- public static final String CN_APPBAR_LAYOUT = PKG_PREFIX + "AppBarLayout";
- public static final String CN_COLLAPSING_TOOLBAR_LAYOUT =
- PKG_PREFIX + "CollapsingToolbarLayout";
- public static final String CN_TOOLBAR = "android.support.v7.widget.Toolbar";
- public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
+ public static final String[] CN_COORDINATOR_LAYOUT = {
+ "android.support.design.widget.CoordinatorLayout",
+ "androidx.widget.CoordinatorLayout"
+ };
+ public static final String[] CN_APPBAR_LAYOUT = {
+ "android.support.design.widget.AppBarLayout",
+ "androidx.design.widget.AppBarLayout"
+ };
+ public static final String[] CN_COLLAPSING_TOOLBAR_LAYOUT = {
+ "android.support.design.widget.CollapsingToolbarLayout",
+ "androidx.design.widget.CollapsingToolbarLayout"
+ };
+ public static final String[] CN_TOOLBAR = {
+ "android.support.v7.widget.Toolbar",
+ "androidx.widget.Toolbar"
+ };
/**
* Tries to set the title of a view. This is used to set the title in a
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 40d3811..4b9f674 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/DrawerLayoutUtil.java
@@ -33,7 +33,10 @@
public class DrawerLayoutUtil {
- public static final String CN_DRAWER_LAYOUT = "android.support.v4.widget.DrawerLayout";
+ public static final String[] CN_DRAWER_LAYOUT = {
+ "android.support.v4.widget.DrawerLayout",
+ "androidx.widget.DrawerLayout"
+ };
public static void openDrawer(View drawerLayout, @Nullable String drawerGravity) {
int gravity = -1;
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 730ac13..138d914 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/FragmentTabHostUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/FragmentTabHostUtil.java
@@ -32,18 +32,43 @@
*/
public class FragmentTabHostUtil {
- public static final String CN_FRAGMENT_TAB_HOST = "android.support.v4.app.FragmentTabHost";
+ public static final String[] CN_FRAGMENT_TAB_HOST = {
+ "android.support.v4.app.FragmentTabHost",
+ "androidx.app.FragmentTabHost"
+ };
+
+ private static final String[] CN_FRAGMENT_MANAGER = {
+ "android.support.v4.app.FragmentManager",
+ "androidx.app.FragmentManager"
+ };
/**
* Calls the setup method for the FragmentTabHost tabHost
*/
public static void setup(TabHost tabHost, Context context) {
+ Class<?> fragmentManager = null;
+
+ for (int i = CN_FRAGMENT_MANAGER.length - 1; i >= 0; i--) {
+ String className = CN_FRAGMENT_MANAGER[i];
+ try {
+ fragmentManager = Class.forName(className, true,
+ tabHost.getClass().getClassLoader());
+ break;
+ } catch (ClassNotFoundException ignore) {
+ }
+ }
+
+ if (fragmentManager == null) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Unable to find FragmentManager.", null);
+ return;
+ }
+
try {
invoke(getMethod(tabHost.getClass(), "setup", Context.class,
- Class.forName("android.support.v4.app.FragmentManager", true,
- tabHost.getClass().getClassLoader()), int.class), tabHost, context, null,
+ fragmentManager, int.class), tabHost, context, null,
android.R.id.tabcontent);
- } catch (ReflectionException | ClassNotFoundException e) {
+ } catch (ReflectionException e) {
Throwable cause = getCause(e);
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
"Error occurred while trying to setup FragmentTabHost.", cause, null);
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 c6e034f..bf9a7e5 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java
@@ -18,6 +18,7 @@
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;
@@ -34,17 +35,15 @@
import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
/**
- * Utility class for working with android.support.v7.widget.RecyclerView
+ * Utility class for working with android.support.v7.widget.RecyclerView and
+ * androidx.widget.RecyclerView
*/
public class RecyclerViewUtil {
+ public static final String[] CN_RECYCLER_VIEW = {
+ "android.support.v7.widget.RecyclerView",
+ "androidx.widget.RecyclerView"
+ };
- private static final String RV_PKG_PREFIX = "android.support.v7.widget.";
- public static final String CN_RECYCLER_VIEW = RV_PKG_PREFIX + "RecyclerView";
- private static final String CN_LAYOUT_MANAGER = CN_RECYCLER_VIEW + "$LayoutManager";
- private static final String CN_ADAPTER = CN_RECYCLER_VIEW + "$Adapter";
-
- // LinearLayoutManager related constants.
- private static final String CN_LINEAR_LAYOUT_MANAGER = RV_PKG_PREFIX + "LinearLayoutManager";
private static final Class<?>[] LLM_CONSTRUCTOR_SIGNATURE = new Class<?>[]{Context.class};
/**
@@ -56,11 +55,15 @@
*/
public static void setAdapter(@NonNull View recyclerView, @NonNull BridgeContext context,
@NonNull LayoutlibCallback layoutlibCallback, int adapterLayout, int itemCount) {
+ String recyclerViewClassName = recyclerView.getClass().getName();
+ String adapterClassName = recyclerViewClassName + "$Adapter";
+ String layoutMgrClassName = recyclerViewClassName + "$LayoutManager";
+
try {
- setLayoutManager(recyclerView, context, layoutlibCallback);
- Object adapter = createAdapter(layoutlibCallback);
+ setLayoutManager(recyclerView, layoutMgrClassName, context, layoutlibCallback);
+ Object adapter = createAdapter(layoutlibCallback, adapterClassName);
if (adapter != null) {
- setProperty(recyclerView, CN_ADAPTER, adapter, "setAdapter");
+ setProperty(recyclerView, adapterClassName, adapter, "setAdapter");
setProperty(adapter, int.class, adapterLayout, "setLayoutId");
if (itemCount != -1) {
@@ -74,13 +77,17 @@
}
}
- private static void setLayoutManager(@NonNull View recyclerView, @NonNull BridgeContext context,
+ private static void setLayoutManager(@NonNull View recyclerView,
+ @NonNull String layoutMgrClassName, @NonNull BridgeContext context,
@NonNull LayoutlibCallback callback) throws ReflectionException {
if (getLayoutManager(recyclerView) == null) {
+ String linearLayoutMgrClassManager =
+ recyclerView.getClass().getPackage().getName() + ".LinearLayoutManager";
// Only set the layout manager if not already set by the recycler view.
- Object layoutManager = createLayoutManager(context, callback);
+ Object layoutManager =
+ createLayoutManager(context, linearLayoutMgrClassManager, callback);
if (layoutManager != null) {
- setProperty(recyclerView, CN_LAYOUT_MANAGER, layoutManager, "setLayoutManager");
+ setProperty(recyclerView, layoutMgrClassName, layoutManager, "setLayoutManager");
}
}
}
@@ -88,10 +95,10 @@
/** Creates a LinearLayoutManager using the provided context. */
@Nullable
private static Object createLayoutManager(@NonNull Context context,
- @NonNull LayoutlibCallback callback)
+ @NonNull String linearLayoutMgrClassName, @NonNull LayoutlibCallback callback)
throws ReflectionException {
try {
- return callback.loadView(CN_LINEAR_LAYOUT_MANAGER, LLM_CONSTRUCTOR_SIGNATURE,
+ return callback.loadView(linearLayoutMgrClassName, LLM_CONSTRUCTOR_SIGNATURE,
new Object[]{context});
} catch (Exception e) {
throw new ReflectionException(e);
@@ -104,22 +111,22 @@
}
@Nullable
- private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback)
- throws ReflectionException {
+ private static Object createAdapter(@NonNull LayoutlibCallback layoutlibCallback,
+ @NonNull String layoutMgrClassName) throws ReflectionException {
Boolean ideSupport =
layoutlibCallback.getFlag(RenderParamsFlags.FLAG_KEY_RECYCLER_VIEW_SUPPORT);
if (ideSupport != Boolean.TRUE) {
return null;
}
try {
- return layoutlibCallback.loadClass(CN_ADAPTER, new Class[0], new Object[0]);
+ return layoutlibCallback.loadClass(layoutMgrClassName, new Class[0], new Object[0]);
} catch (Exception e) {
throw new ReflectionException(e);
}
}
private static void setProperty(@NonNull Object object, @NonNull String propertyClassName,
- @NonNull Object propertyValue, @NonNull String propertySetter)
+ @NonNull Object propertyValue, @NonNull String propertySetter)
throws ReflectionException {
Class<?> propertyClass = ReflectionUtils.getClassInstance(propertyValue, propertyClassName);
setProperty(object, propertyClass, propertyValue, propertySetter);
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 6ad9efc..fda4693 100644
--- a/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
+++ b/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java
@@ -52,12 +52,10 @@
* the API being stable.
*/
public class SupportPreferencesUtil {
- private static final String PREFERENCE_PKG = "android.support.v7.preference";
- private static final String PREFERENCE_MANAGER = PREFERENCE_PKG + ".PreferenceManager";
- private static final String PREFERENCE_GROUP = PREFERENCE_PKG + ".PreferenceGroup";
- private static final String PREFERENCE_GROUP_ADAPTER =
- PREFERENCE_PKG + ".PreferenceGroupAdapter";
- private static final String PREFERENCE_INFLATER = PREFERENCE_PKG + ".PreferenceInflater";
+ private static final String[] PREFERENCES_PKG_NAMES = {
+ "android.support.v7.preference",
+ "androidx.preference"
+ };
private SupportPreferencesUtil() {
}
@@ -79,21 +77,27 @@
@NonNull
private static Object createPreferenceGroupAdapter(@NonNull LayoutlibCallback callback,
- @NonNull Object preferenceScreen) throws ReflectionException {
- Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP);
+ @NonNull String preferenceGroupClassName,
+ @NonNull String preferenceGroupAdapterClassName, @NonNull Object preferenceScreen)
+ throws ReflectionException {
+ Class<?> preferenceGroupClass =
+ getClassInstance(preferenceScreen, preferenceGroupClassName);
- return instantiateClass(callback, PREFERENCE_GROUP_ADAPTER,
+ return instantiateClass(callback, preferenceGroupAdapterClassName,
new Class[]{preferenceGroupClass}, new Object[]{preferenceScreen});
}
@NonNull
private static Object createInflatedPreference(@NonNull LayoutlibCallback callback,
- @NonNull Context context, @NonNull XmlPullParser parser, @NonNull Object preferenceScreen,
- @NonNull Object preferenceManager) throws ReflectionException {
- Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP);
- Object preferenceInflater = instantiateClass(callback, PREFERENCE_INFLATER,
- new Class[]{Context.class, preferenceManager.getClass()},
- new Object[]{context, preferenceManager});
+ @NonNull String preferenceGroupClassName, @NonNull String preferenceInflaterClassName,
+ @NonNull Context context, @NonNull XmlPullParser parser,
+ @NonNull Object preferenceScreen, @NonNull Object preferenceManager)
+ throws ReflectionException {
+ Class<?> preferenceGroupClass =
+ getClassInstance(preferenceScreen, preferenceGroupClassName);
+ Object preferenceInflater = instantiateClass(callback, preferenceInflaterClassName,
+ new Class[]{Context.class, preferenceManager.getClass()},
+ new Object[]{context, preferenceManager});
Object inflatedPreference =
invoke(getAccessibleMethod(preferenceInflater.getClass(), "inflate",
XmlPullParser.class, preferenceGroupClass), preferenceInflater, parser,
@@ -169,8 +173,7 @@
// Get the type of the preference layout and bind it to a newly created view holder
Integer type = (Integer) invoke(getItemViewType, preferenceGroupAdapter, i);
- Object viewHolder =
- invoke(onCreateViewHolder, preferenceGroupAdapter, listView, type);
+ Object viewHolder = invoke(onCreateViewHolder, preferenceGroupAdapter, listView, type);
if (viewHolder == null) {
continue;
}
@@ -179,8 +182,7 @@
try {
// Get the view from the view holder and add it to our layout
- View itemView =
- (View) viewHolder.getClass().getField("itemView").get(viewHolder);
+ View itemView = (View) viewHolder.getClass().getField("itemView").get(viewHolder);
int arrayPosition = id.intValue() - 1; // IDs are 1 based
if (arrayPosition >= 0 && arrayPosition < viewCookie.size()) {
@@ -201,6 +203,24 @@
@Nullable
public static View inflatePreference(@NonNull BridgeContext bridgeContext,
@NonNull XmlPullParser parser, @Nullable ViewGroup root) {
+ String preferencePackageName = null;
+ String preferenceManagerClassName = null;
+ // Find the correct package for the classes
+ for (int i = PREFERENCES_PKG_NAMES.length - 1; i >= 0; i--) {
+ preferencePackageName = PREFERENCES_PKG_NAMES[i];
+ preferenceManagerClassName = preferencePackageName + ".PreferenceManager";
+ try {
+ bridgeContext.getLayoutlibCallback().findClass(preferenceManagerClassName);
+ break;
+ } catch (ClassNotFoundException ignore) {
+ }
+ }
+
+ assert preferencePackageName != null;
+ String preferenceGroupClassName = preferencePackageName + ".PreferenceGroup";
+ String preferenceGroupAdapterClassName = preferencePackageName + ".PreferenceGroupAdapter";
+ String preferenceInflaterClassName = preferencePackageName + ".PreferenceInflater";
+
try {
LayoutlibCallback callback = bridgeContext.getLayoutlibCallback();
@@ -211,9 +231,8 @@
}
// Create PreferenceManager
- Object preferenceManager =
- instantiateClass(callback, PREFERENCE_MANAGER, new Class[]{Context.class},
- new Object[]{context});
+ Object preferenceManager = instantiateClass(callback, preferenceManagerClassName,
+ new Class[]{Context.class}, new Object[]{context});
// From this moment on, we can assume that we found the support library and that
// nothing should fail
@@ -251,13 +270,14 @@
}
// Create the PreferenceInflater
- Object inflatedPreference =
- createInflatedPreference(callback, context, parser, preferenceScreen,
- preferenceManager);
+ Object inflatedPreference = createInflatedPreference(callback, preferenceGroupClassName,
+ preferenceInflaterClassName, context, parser, preferenceScreen,
+ preferenceManager);
// Setup the RecyclerView (set adapter and layout manager)
Object preferenceGroupAdapter =
- createPreferenceGroupAdapter(callback, inflatedPreference);
+ createPreferenceGroupAdapter(callback, preferenceGroupClassName,
+ preferenceGroupAdapterClassName, inflatedPreference);
// Instead of just setting the group adapter as adapter for a RecyclerView, we manually
// get all the items and add them to a LinearLayout. This allows us to set the view
@@ -266,8 +286,8 @@
preferenceGroupAdapter);
ScrollView scrollView = new ScrollView(context);
- scrollView.setLayoutParams(
- new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ scrollView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
scrollView.addView(listView);
if (root != null) {
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index cdcf0ea..919d57c 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -26,6 +26,7 @@
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;
@@ -33,11 +34,18 @@
import android.graphics.drawable.Drawable;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
import android.view.View;
import android.widget.FrameLayout;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
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;
/**
@@ -47,9 +55,12 @@
public class AppCompatActionBar extends BridgeActionBar {
private Object mWindowDecorActionBar;
- private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
- // This is used on v23.1.1 and later.
- private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
+ 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
+ };
+
private Class<?> mWindowActionBarClass;
/**
@@ -77,19 +88,23 @@
Object[] constructorArgs = {getDecorContent()};
LayoutlibCallback callback = params.getLayoutlibCallback();
- // Check if the old action bar class is present.
- String actionBarClass = WINDOW_ACTION_BAR_CLASS;
- try {
- callback.findClass(actionBarClass);
- } catch (ClassNotFoundException expected) {
- // Failed to find the old class, use the newer one.
- actionBarClass = WINDOW_ACTION_BAR_CLASS_NEW;
+ // Find the correct WindowActionBar class
+ String actionBarClass = null;
+ for (int i = WINDOW_ACTION_BAR_CLASS_NAMES.length - 1; i >= 0; i--) {
+ 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();
+ inflateMenus();
setupActionBar();
} catch (Exception e) {
Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
@@ -165,6 +180,51 @@
}
}
+ private void inflateMenus() {
+ List<String> menuNames = getCallBack().getMenuIdNames();
+ if (menuNames.isEmpty()) {
+ return;
+ }
+
+ if (menuNames.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,
+ "Support Toolbar does not currently support multiple menus in the preview.",
+ 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);
+ }
+ if (id < 1) {
+ return;
+ }
+ // Get toolbar decorator
+ Object mDecorToolbar = getFieldValue(mWindowDecorActionBar, "mDecorToolbar");
+ if (mDecorToolbar == null) {
+ return;
+ }
+
+ Class<?> mDecorToolbarClass = mDecorToolbar.getClass();
+ Context themedContext = (Context)invoke(
+ getMethod(mWindowActionBarClass, "getThemedContext"),
+ mWindowDecorActionBar);
+ MenuInflater inflater = new MenuInflater(themedContext);
+ Menu menuBuilder = (Menu)invoke(getMethod(mDecorToolbarClass, "getMenu"), mDecorToolbar);
+ inflater.inflate(id, menuBuilder);
+
+ // Set the actual menu
+ invoke(findMethod(mDecorToolbarClass, "setMenu"), mDecorToolbar, menuBuilder, null);
+ }
+
@Override
public void createMenuPopup() {
// it's hard to add menus to appcompat's actionbar, since it'll use a lot of reflection.
@@ -181,13 +241,53 @@
return null;
}
+ /**
+ * Same as getMethod but doesn't require the parameterTypes. This allows us to call methods
+ * without having to get all the types for the parameters when we do not need them
+ */
@Nullable
- private static Object invoke(Method method, Object owner, Object... args) {
+ private static Method findMethod(@Nullable Class<?> owner, @NotNull String name) {
+ if (owner == null) {
+ return null;
+ }
+ for (Method method : owner.getMethods()) {
+ if (name.equals(method.getName())) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static Object getFieldValue(@Nullable Object instance, @NotNull String name) {
+ if (instance == null) {
+ return null;
+ }
+
+ Class<?> instanceClass = instance.getClass();
+ try {
+ Field field = instanceClass.getDeclaredField(name);
+ boolean accesible = field.isAccessible();
+ if (!accesible) {
+ field.setAccessible(true);
+ }
+ try {
+ return field.get(instance);
+ } finally {
+ field.setAccessible(accesible);
+ }
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Nullable
+ private static Object invoke(@Nullable Method method, Object owner, Object... args) {
try {
return method == null ? null : method.invoke(owner, args);
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
diff --git a/bridge/src/com/android/layoutlib/bridge/bars/Config.java b/bridge/src/com/android/layoutlib/bridge/bars/Config.java
index 7f8d992..569d7b6 100644
--- a/bridge/src/com/android/layoutlib/bridge/bars/Config.java
+++ b/bridge/src/com/android/layoutlib/bridge/bars/Config.java
@@ -74,8 +74,8 @@
}
public static String getTime(int platformVersion) {
- if (isGreaterOrEqual(platformVersion, N)) {
- return "7:00";
+ if (isGreaterOrEqual(platformVersion, O)) {
+ return "8:00";
}
if (platformVersion < GINGERBREAD) {
return "2:20";
@@ -101,6 +101,9 @@
if (platformVersion < N) {
return "6:00";
}
+ if (platformVersion < O) {
+ return "7:00";
+ }
// Should never happen.
return "4:04";
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 2e31980..7168b54 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -724,7 +724,7 @@
DesignLibUtil.setTitle(collapsingToolbar, title);
}
- private View findChildView(View view, String className) {
+ private View findChildView(View view, String[] className) {
if (!(view instanceof ViewGroup)) {
return null;
}
diff --git a/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index c3b9aed..16f92f3 100644
--- a/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -49,6 +49,7 @@
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.Typeface_Accessor;
+import android.graphics.Typeface_Delegate;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -159,31 +160,9 @@
} catch (NumberFormatException ignored) {
}
- XmlPullParser parser = null;
- // first check if the value is a file (xml most likely)
- Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
- RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
- if (psiParserSupport != null && psiParserSupport) {
- parser = context.getLayoutlibCallback().getXmlFileParser(value);
- }
- if (parser == null) {
- File f = new File(value);
- if (f.isFile()) {
- // let the framework inflate the color from the XML file, by
- // providing an XmlPullParser
- try {
- parser = ParserFactory.create(f);
- } catch (XmlPullParserException | FileNotFoundException e) {
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed to parse file " + value, e, null /*data*/);
- }
- }
- }
-
- if (parser != null) {
- try {
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- parser, context, resValue.isFramework());
+ try {
+ BridgeXmlBlockParser blockParser = getXmlBlockParser(context, resValue);
+ if (blockParser != null) {
try {
// Advance the parser to the first element so we can detect if it's a
// color list or a gradient color
@@ -214,18 +193,18 @@
} finally {
blockParser.ensurePopped();
}
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value, e, null /*data*/);
- // we'll return null below.
- } catch (Exception e) {
- // this is an error and not warning since the file existence is
- // checked before attempting to parse it.
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed to parse file " + value, e, null /*data*/);
-
- return null;
}
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + value, e, null /*data*/);
+ // we'll return null below.
+ } catch (Exception e) {
+ // this is an error and not warning since the file existence is
+ // checked before attempting to parse it.
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed to parse file " + value, e, null /*data*/);
+
+ return null;
}
return null;
@@ -409,59 +388,8 @@
return null;
}
- // Check if this is an asset that we've already loaded dynamically
- Typeface typeface = Typeface.findFromCache(context.getAssets(), fontName);
- if (typeface != null) {
- return typeface;
- }
- String lowerCaseValue = fontName.toLowerCase();
- if (lowerCaseValue.endsWith(".xml")) {
- // create a block parser for the file
- Boolean psiParserSupport = context.getLayoutlibCallback().getFlag(
- RenderParamsFlags.FLAG_KEY_XML_FILE_PARSER_SUPPORT);
- XmlPullParser parser = null;
- if (psiParserSupport != null && psiParserSupport) {
- parser = context.getLayoutlibCallback().getXmlFileParser(fontName);
- }
- else {
- File f = new File(fontName);
- 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 " + fontName,
- e, null /*data*/);
- }
- }
- }
-
- if (parser != null) {
- BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
- parser, context, isFramework);
- try {
- FontResourcesParser.FamilyResourceEntry entry =
- FontResourcesParser.parse(blockParser, context.getResources());
- typeface = Typeface.createFromResources(entry, context.getAssets(),
- fontName);
- } catch (XmlPullParserException | IOException e) {
- Bridge.getLog().error(null, "Failed to parse file " + fontName,
- e, null /*data*/);
- } finally {
- blockParser.ensurePopped();
- }
- } else {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- String.format("File %s does not exist (or is not a file)", fontName),
- null /*data*/);
- }
- } else {
- typeface = Typeface.createFromResources(context.getAssets(), fontName, 0);
- }
-
- return typeface;
+ return Typeface_Delegate.createFromDisk(context, fontName, isFramework);
}
/**
diff --git a/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
index b89718f..64d100a 100644
--- a/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
+++ b/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java
@@ -19,8 +19,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
/**
* Utility to convert checked Reflection exceptions to unchecked exceptions.
@@ -74,6 +76,24 @@
return false;
}
+ /**
+ * Check if the object is an instance of any of the class named in {@code className}. This
+ * doesn't work for interfaces.
+ */
+ public static boolean isInstanceOf(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;
+ }
+ }
+ superClass = superClass.getSuperclass();
+ }
+ return false;
+ }
+
@NonNull
public static Throwable getCause(@NonNull Throwable throwable) {
Throwable cause = throwable.getCause();
@@ -99,6 +119,34 @@
throw new RuntimeException("invalid object/classname combination.");
}
+ public static <T> T createProxy(Class<T> interfaze) {
+ ClassLoader loader = interfaze.getClassLoader();
+ return (T) Proxy.newProxyInstance(loader, new Class[]{interfaze}, new InvocationHandler() {
+ public Object invoke(Object proxy, Method m, Object[] args) {
+ final Class<?> returnType = m.getReturnType();
+ if (returnType == boolean.class) {
+ return false;
+ } else if (returnType == int.class) {
+ return 0;
+ } else if (returnType == long.class) {
+ return 0L;
+ } else if (returnType == short.class) {
+ return 0;
+ } else if (returnType == char.class) {
+ return 0;
+ } else if (returnType == byte.class) {
+ return 0;
+ } else if (returnType == float.class) {
+ return 0f;
+ } else if (returnType == double.class) {
+ return 0.0;
+ } else {
+ return null;
+ }
+ }
+ });
+ }
+
/**
* Wraps all reflection related exceptions. Created since ReflectiveOperationException was
* introduced in 1.7 and we are still on 1.6
diff --git a/bridge/tests/Android.mk b/bridge/tests/Android.mk
index 1b65eee..e51d75d 100644
--- a/bridge/tests/Android.mk
+++ b/bridge/tests/Android.mk
@@ -32,7 +32,8 @@
sdk-common \
junit-host \
guavalib \
- mockito-host
+ mockito-host \
+ objenesis-host
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/bridge/tests/res/testApp/MyApplication/build.gradle b/bridge/tests/res/testApp/MyApplication/build.gradle
index 4781660..c9058b5 100644
--- a/bridge/tests/res/testApp/MyApplication/build.gradle
+++ b/bridge/tests/res/testApp/MyApplication/build.gradle
@@ -3,7 +3,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.2.3'
+ classpath 'com.android.tools.build:gradle:3.0.0-beta5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -19,12 +19,12 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 25
- buildToolsVersion '25.0.0'
+ compileSdkVersion 26
+ buildToolsVersion '26.0.2'
defaultConfig {
applicationId 'com.android.layoutlib.test.myapplication'
minSdkVersion 21
- targetSdkVersion 25
+ targetSdkVersion 26
versionCode 1
versionName '1.0'
}
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
new file mode 100644
index 0000000..a734a21
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/androidTest/debug/com/android/layoutlib/test/myapplication/test/R.class
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/classes/debug/com/android/layoutlib/test/myapplication/ArraysCheckWidget.class
index f73528a..ea2e30d 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/classes/debug/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/MyActivity.class b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
index 5bb04fc..be984f0 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/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class
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
index b87f193..d279a3e 100644
--- 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
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/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
index e2968d4..2b98809 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/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class
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
index ff699d1..a73fcca 100644
--- 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
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/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
index a3931b8..6f6bd54 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/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class
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
index e293677..93c5977 100644
--- 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
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/classes/debug/com/android/layoutlib/test/myapplication/R$font.class
new file mode 100644
index 0000000..a3e9926
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$font.class
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
index d6268bf..e580ef5 100644
--- 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
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/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
index 08b98fb..4f1a852 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/classes/debug/com/android/layoutlib/test/myapplication/R$integer.class
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
index f9be1ca..9bdc44f 100644
--- 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
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
index 6874b49..11e0686 100644
--- 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
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/classes/debug/com/android/layoutlib/test/myapplication/R$string.class
index a4205a8..331ea4a 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/classes/debug/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/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
index 4fb3b61..3b47ac9 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/classes/debug/com/android/layoutlib/test/myapplication/R$style.class
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
index dba67fd..e91c774 100644
--- 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
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/classes/debug/com/android/layoutlib/test/myapplication/ThemableWidget.class
new file mode 100644
index 0000000..8d96ac1
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/ThemableWidget.class
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/asset.png b/bridge/tests/res/testApp/MyApplication/golden/asset.png
new file mode 100644
index 0000000..f69b128
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/asset.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png b/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png
new file mode 100644
index 0000000..27bc3d8
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/ondraw_crash.png b/bridge/tests/res/testApp/MyApplication/golden/ondraw_crash.png
new file mode 100644
index 0000000..ca51e21
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/ondraw_crash.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/onmeasure_crash.png b/bridge/tests/res/testApp/MyApplication/golden/onmeasure_crash.png
new file mode 100644
index 0000000..780f8e5
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/onmeasure_crash.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/remote_component_load.png b/bridge/tests/res/testApp/MyApplication/golden/remote_component_load.png
new file mode 100644
index 0000000..2920b7d
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/remote_component_load.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/golden/remote_component_load_fail.png b/bridge/tests/res/testApp/MyApplication/golden/remote_component_load_fail.png
new file mode 100644
index 0000000..0ed85d1
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/remote_component_load_fail.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
new file mode 100644
index 0000000..62fb869
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/rtl_ltr.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 4f3ed60..8212d04 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/vector_drawable.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable.png
index 7bbae09..eddb5e6 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_gradient.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png
new file mode 100644
index 0000000..8bb1677
--- /dev/null
+++ 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_multi_line_of_path_data.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_multi_line_of_path_data.png
index 5fc6052..ebc8a11 100644
--- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_multi_line_of_path_data.png
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_multi_line_of_path_data.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
new file mode 100644
index 0000000..2a3f3dd
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.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
new file mode 100644
index 0000000..bf358fa
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/golden/view_boundaries.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 3b51ffe..565238f 100644
--- a/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
+++ b/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/assets/asset.png b/bridge/tests/res/testApp/MyApplication/src/main/assets/asset.png
new file mode 100644
index 0000000..0327e13
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/assets/asset.png
Binary files differ
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ThemableWidget.java b/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ThemableWidget.java
new file mode 100644
index 0000000..10189b7
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/ThemableWidget.java
@@ -0,0 +1,40 @@
+package com.android.layoutlib.test.myapplication;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.widget.FrameLayout;
+
+public class ThemableWidget extends FrameLayout {
+ public ThemableWidget(Context context) {
+ super(context);
+
+ init();
+ }
+
+ public ThemableWidget(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ init();
+ }
+
+ public ThemableWidget(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ init();
+ }
+
+ public ThemableWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ init();
+ }
+
+ private void init() {
+ ContextThemeWrapper context = new ContextThemeWrapper(getContext(), getContext().getTheme());
+ context.setTheme(R.style.ThemableWidgetStyle);
+
+ LayoutInflater.from(context).inflate(R.layout.themable_widget_layout, this);
+ }
+}
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/AssetView.java b/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/AssetView.java
new file mode 100644
index 0000000..9e43d8f
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/AssetView.java
@@ -0,0 +1,35 @@
+package com.android.layoutlib.test.myapplication.widgets;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class AssetView extends View {
+ public AssetView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public AssetView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public AssetView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ private void init(Context context) {
+ try {
+ InputStream istr = context.getAssets().open("asset.png");
+ setBackground(Drawable.createFromStream(istr, null));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_gradient.xml
new file mode 100644
index 0000000..1578192
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_gradient.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<gradient
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:endX="200"
+ android:endY="200"
+ android:startX="100"
+ android:startY="100"
+ android:type="linear">
+ <item
+ android:color="#123"
+ android:offset="0.0" />
+ <item
+ android:color="#AAA"
+ android:offset="0.5" />
+ <item
+ android:color="#00F"
+ android:offset="1.0" />
+</gradient>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_radial_gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_radial_gradient.xml
new file mode 100644
index 0000000..ce6a3de
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/color/complex_radial_gradient.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+
+<gradient
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:centerX="50"
+ android:centerY="50"
+ android:gradientRadius="20"
+ android:type="radial">
+ <item
+ android:color="#123"
+ android:offset="0.0" />
+ <item
+ android:color="#00F"
+ android:offset="1.0" />
+</gradient>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
index fc0afa6..371d4fe 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/color/gradient.xml
@@ -16,9 +16,9 @@
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
- android:startX="10"
- android:startY="10"
- android:endX="50"
- android:endY="50"
+ android:startX="-20"
+ android:startY="-20"
+ android:endX="-10"
+ android:endY="-10"
android:startColor="#ffff0000"
android:endColor="#ff00ff00" />
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
index 8f862c8..cc9c449 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
- <background android:drawable="@android:color/red" />
+ <background android:drawable="@color/red" />
<foreground android:drawable="@drawable/headset" />
</adaptive-icon>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_line_of_path_data.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_line_of_path_data.xml
index c5b0f01..3afb7b6 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_line_of_path_data.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_line_of_path_data.xml
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="16dp"
- android:viewportHeight="16"
+ android:viewportHeight="20"
android:viewportWidth="16"
android:width="16dp">
<group
android:translateX="-2.000000"
- android:translateY="-14.000000">
+ android:translateY="-12.000000">
<group
android:translateX="2.000000"
android:translateY="14.000000">
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
index 0998b25..6f4a350 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/multi_path.xml
@@ -1,22 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
-
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="76dp"
- android:width="76dp"
- android:viewportHeight="48"
- android:viewportWidth="48"
+ android:height="50dp"
+ android:width="50dp"
+ android:viewportHeight="100"
+ android:viewportWidth="100"
android:alpha="0.6">
<group
+ android:name="clipBackground">
+ <!--
+ Any content outside this box, should not be there as it will be outside the viewport
+ -->
+ <path
+ android:pathData="M0,0h100v100H0z"
+ android:fillColor="#301BB0D3"/>
+
+ <!--
+ So this box shouldn't be visible
+ -->
+ <path
+ android:pathData="M0,-10h10v10H0z"
+ android:strokeWidth="1"
+ android:strokeColor="#000"
+ android:fillColor="#F00"/>
+
+ <!--
+ This one should be partially visible
+ -->
+ <path
+ android:pathData="M80,-10h10v20h-10z"
+ android:strokeWidth="1"
+ android:strokeColor="#000"
+ android:fillColor="#FF0"/>
+
+ </group>
+
+ <group
android:name="root"
- android:translateX="24.0"
- android:translateY="24.0">
+ android:translateX="50.0"
+ android:translateY="50.0"
+ android:scaleX="1.2"
+ android:scaleY="1.2">
+
+ <path
+ android:strokeWidth="2"
+ android:strokeColor="#FFFF00"
+ android:pathData="M-10,-10 m20,0 m0,20 m-20,0 m0,-20z"
+ android:fillColor="#FEBEBE"
+ />
+
<!--
This is the same as the material indeterminate progressbar which involves drawing
several cubic segments
-->
<path
- android:pathData="M0, 0 m 0, -19 a 19,19 0 1,1 0,38 a 19,19 0 1,1 0,-38"
+ android:pathData="M0, 0 m 0, -30 a 10,10 0 1,1 0,38 a 10,10 0 1,1 0,-38"
android:strokeColor="#00FF00"
android:strokeLineCap="square"
android:strokeLineJoin="miter"
@@ -25,13 +63,13 @@
android:trimPathStart="0.3" />
<!-- Same figure with reversed end and start -->
<path
- android:pathData="M0, 0 m 0, -12 a 19,19 0 1,1 0,38 a 19,19 0 1,1 0,-38"
+ android:pathData="M0, 0 m 0, -12 a 10,10 0 1,1 0,38 a 10,10 0 1,1 0,-38"
android:strokeColor="#FFFF00"
android:strokeLineCap="square"
android:strokeLineJoin="miter"
android:strokeWidth="1"
android:trimPathEnd="0.3"
- android:trimPathStart="0.8" />
+ android:trimPathStart="0.5" />
<!--
Draw a few partial quadratic segments
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/radial_gradient.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/radial_gradient.xml
new file mode 100644
index 0000000..3ab9d72
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/radial_gradient.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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 xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="100"
+ android:viewportWidth="100">
+ <path
+ android:pathData="m100,0 0,100 -100,0 0,-100z"
+ android:strokeColor="#F00000"
+ android:strokeWidth="1"
+ android:fillColor="@color/complex_radial_gradient">
+ </path>
+</vector>
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/shadow.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/shadow.xml
new file mode 100644
index 0000000..67824fd
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/shadow.xml
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="300dp"
+ android:height="300dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200">
+ <path
+ android:pathData="m200,0 0,200 -200,0 0,-200z"
+ android:strokeColor="#F00000"
+ android:strokeWidth="1"
+ android:fillColor="@color/complex_gradient">
+ </path>
+</vector>
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 59dbbec..c083a6f 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
@@ -94,4 +94,50 @@
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: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:alpha="0.5"/>
+
+ </RelativeLayout>
+
+ <!-- Test translation -->
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+
+ <Button
+ android:translationX="5dp"
+ android:translationY="20dp"
+ 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" />
+
+ </RelativeLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/layout/themable_widget_layout.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/themable_widget_layout.xml
new file mode 100644
index 0000000..507bba6
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/layout/themable_widget_layout.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:background="#00F"
+ android:layout_width="50dp"/>
+<!-- Intentionally omitting height since it will be added via themeing -->
\ No newline at end of file
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 5f58d39..1059d2e 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
@@ -17,7 +17,7 @@
<!-- theme ref in android NS. value = @string/candidates_style = <u>candidates</u> -->
<item>?android:attr/candidatesTextStyleSpans</item>
<item>@android:string/unknownName</item> <!-- value = Unknown -->
- <item>?EC</item>
+ <item>\?EC</item>
</string-array>
<!-- resources that the above array can refer to -->
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/values/colors.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/values/colors.xml
new file mode 100644
index 0000000..4754690
--- /dev/null
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/values/colors.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="red">#F00</color>
+</resources>
\ No newline at end of file
diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
index debe33b..f598fda 100644
--- a/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
+++ b/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml
@@ -5,4 +5,8 @@
<item name="myattr">@integer/ten</item>
</style>
+ <style name="ThemableWidgetStyle">
+ <item name="android:layout_height">150dp</item>
+ </style>
+
</resources>
diff --git a/bridge/tests/src/android/graphics/Matrix_DelegateTest.java b/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
index d20fb14..aa51773 100644
--- a/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
+++ b/bridge/tests/src/android/graphics/Matrix_DelegateTest.java
@@ -16,6 +16,8 @@
package android.graphics;
+import java.util.Arrays;
+
import junit.framework.TestCase;
/**
@@ -28,7 +30,7 @@
assertTrue(m1.isIdentity());
- m1.setValues(new float[] { 1,0,0, 0,1,0, 0,0,1 });
+ m1.setValues(new float[]{1, 0, 0, 0, 1, 0, 0, 0, 1});
assertTrue(m1.isIdentity());
}
@@ -41,8 +43,22 @@
m1.getValues(v1);
m2.getValues(v2);
- for (int i = 0 ; i < 9; i++) {
+ for (int i = 0; i < 9; i++) {
assertEquals(v1[i], v2[i]);
}
}
+
+ public void testInvert() {
+ Matrix m1 = new Matrix();
+ Matrix inverse = new Matrix();
+ m1.setValues(new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9});
+ 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}));
+ }
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java b/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java
new file mode 100644
index 0000000..96146dc
--- /dev/null
+++ b/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.SessionParams;
+import com.android.layoutlib.bridge.impl.RenderAction;
+import com.android.layoutlib.bridge.impl.RenderActionTestUtil;
+import com.android.layoutlib.bridge.intensive.RenderTestBase;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
+import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
+
+import org.junit.Test;
+
+import android.R.attr;
+import android.R.style;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.util.DisplayMetrics;
+import android.view.ContextThemeWrapper;
+
+import static org.junit.Assert.assertTrue;
+
+public class BridgeContextTest extends RenderTestBase {
+ @Test
+ public void basic() throws ClassNotFoundException {
+ // Setup
+ // Create the layout pull parser for our resources (empty.xml can not be part of the test
+ // app as it won't compile).
+ LayoutPullParser parser = LayoutPullParser.createFromPath("/empty.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material", false)
+ .build();
+ DisplayMetrics metrics = new DisplayMetrics();
+ Configuration configuration = RenderAction.getConfiguration(params);
+ BridgeContext context = new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+ params.getAssets(), params.getLayoutlibCallback(), configuration,
+ params.getTargetSdkVersion(), params.isRtlSupported());
+
+ context.initResources();
+ BridgeContext oldContext = RenderActionTestUtil.setBridgeContext(context);
+ try {
+ Context themeContext = new ContextThemeWrapper(context, style.Theme_Material);
+ // First we try to get the style from the ?attr/editTextStyle fallback value.
+ // We pass an invalid value to defStyleRes
+ TypedArray array = themeContext.obtainStyledAttributes(null,
+ new int[]{attr.clickable}, attr.editTextStyle, Integer.MAX_VALUE);
+ assertTrue(array.getBoolean(0, false));
+ // Now, we try to get it directly from the Widget.EditText. We pass an invalid value
+ // to defStyleAttr so it fails and falls back to the defStyleRes
+ array = themeContext.obtainStyledAttributes(null,
+ new int[]{attr.clickable}, Integer.MAX_VALUE,
+ style.Widget_EditText);
+ assertTrue(array.getBoolean(0, false));
+
+ } finally {
+ RenderActionTestUtil.setBridgeContext(oldContext);
+ context.disposeResources();
+ }
+
+ // This message is expected when asking for an invalid defStyleAttr
+ sRenderMessages.removeIf(msg ->
+ msg.startsWith("Failed to find the style corresponding to the id"));
+ }
+
+ @Test
+ public void checkNoErrorWhenUsingDefaults() throws ClassNotFoundException {
+ // Setup
+ // Create the layout pull parser for our resources (empty.xml can not be part of the test
+ // app as it won't compile).
+ LayoutPullParser parser = LayoutPullParser.createFromPath("/empty.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material", false)
+ .build();
+ DisplayMetrics metrics = new DisplayMetrics();
+ Configuration configuration = RenderAction.getConfiguration(params);
+ BridgeContext context = new BridgeContext(params.getProjectKey(), metrics, params.getResources(),
+ params.getAssets(), params.getLayoutlibCallback(), configuration,
+ params.getTargetSdkVersion(), params.isRtlSupported());
+
+ context.initResources();
+ BridgeContext oldContext = RenderActionTestUtil.setBridgeContext(context);
+ try {
+ Context themeContext = new ContextThemeWrapper(context, style.Theme_Material);
+ // First we try to get the style from the ?attr/editTextStyle fallback value.
+ // We pass an invalid value to defStyleRes
+ themeContext.obtainStyledAttributes(null,
+ new int[]{attr.clickable}, 0, style.Widget_EditText);
+ themeContext.obtainStyledAttributes(null,
+ new int[]{attr.clickable}, attr.editTextStyle, 0);
+ } finally {
+ RenderActionTestUtil.setBridgeContext(oldContext);
+ context.disposeResources();
+ }
+
+
+ }
+}
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 e98c789..1360334 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -18,6 +18,7 @@
import com.android.layoutlib.bridge.BridgeRenderSessionTest;
import com.android.layoutlib.bridge.TestDelegates;
+import com.android.layoutlib.bridge.android.BridgeContextTest;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParserTest;
import com.android.layoutlib.bridge.impl.LayoutParserWrapperTest;
import com.android.layoutlib.bridge.impl.ResourceHelperTest;
@@ -36,8 +37,8 @@
@SuiteClasses({
RenderTests.class, LayoutParserWrapperTest.class,
BridgeXmlBlockParserTest.class, BridgeXmlPullAttributesTest.class,
- Matrix_DelegateTest.class, TestDelegates.class, PerformanceTests.class,
- BridgeRenderSessionTest.class, ResourceHelperTest.class
+ Matrix_DelegateTest.class, TestDelegates.class,
+ BridgeRenderSessionTest.class, ResourceHelperTest.class, BridgeContextTest.class
})
public class Main {
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
index 230e116..4d7438f 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/PerformanceTests.java
@@ -43,7 +43,7 @@
private void render(@NonNull String layoutFileName)
throws ClassNotFoundException, FileNotFoundException {
SessionParams params = createSessionParams(layoutFileName, ConfigGenerator.NEXUS_5);
- render(params, 250);
+ render(sBridge, params, 250);
}
@Test
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
index 087478f..989d146 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderResult.java
@@ -28,7 +28,7 @@
import java.util.Collections;
import java.util.List;
-class RenderResult {
+public class RenderResult {
private final List<ViewInfo> mRootViews;
private final List<ViewInfo> mSystemViews;
private final Result mRenderResult;
@@ -51,7 +51,7 @@
}
@Nullable
- Result getResult() {
+ public Result getResult() {
return mRenderResult;
}
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 1fc7015..56d1d65 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTestBase.java
@@ -21,11 +21,9 @@
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
-import com.android.ide.common.resources.FrameworkResources;
-import com.android.ide.common.resources.ResourceItem;
-import com.android.ide.common.resources.ResourceRepository;
-import com.android.ide.common.resources.ResourceResolver;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.deprecated.FrameworkResources;
+import com.android.ide.common.resources.deprecated.ResourceItem;
+import com.android.ide.common.resources.deprecated.ResourceRepository;
import com.android.io.FolderWrapper;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
@@ -35,6 +33,7 @@
import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
import com.android.layoutlib.bridge.intensive.util.ImageUtils;
import com.android.layoutlib.bridge.intensive.util.ModuleClassLoader;
+import com.android.layoutlib.bridge.intensive.util.SessionParamsBuilder;
import com.android.layoutlib.bridge.intensive.util.TestAssetRepository;
import com.android.layoutlib.bridge.intensive.util.TestUtils;
import com.android.tools.layoutlib.java.System_Delegate;
@@ -50,6 +49,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -59,6 +59,7 @@
import java.util.concurrent.TimeUnit;
import com.google.android.collect.Lists;
+import com.google.common.collect.ImmutableMap;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -91,12 +92,14 @@
private static final String PLATFORM_DIR_PROPERTY = "platform.dir";
private static final String RESOURCE_DIR_PROPERTY = "test_res.dir";
- private static final String PLATFORM_DIR;
+ protected static final String PLATFORM_DIR;
private static final String TEST_RES_DIR;
/** Location of the app to test inside {@link #TEST_RES_DIR} */
- private static final String APP_TEST_DIR = "testApp/MyApplication";
+ protected static final String APP_TEST_DIR = "testApp/MyApplication";
/** Location of the app's res dir inside {@link #TEST_RES_DIR} */
private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
+ /** 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/";
protected static Bridge sBridge;
@@ -133,9 +136,8 @@
}
}
};
- // Default class loader with access to the app classes
- protected ClassLoader mDefaultClassLoader =
- new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
+
+ protected ClassLoader mDefaultClassLoader;
private static String getPlatformDir() {
String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY);
@@ -163,6 +165,18 @@
if (currentDir.getName().equalsIgnoreCase("bridge")) {
currentDir = currentDir.getParentFile();
}
+
+ // Find frameworks/layoutlib
+ while (currentDir != null && !"layoutlib".equals(currentDir.getName())) {
+ currentDir = currentDir.getParentFile();
+ }
+
+ if (currentDir == null ||
+ currentDir.getParentFile() == null ||
+ !"frameworks".equals(currentDir.getParentFile().getName())) {
+ return null;
+ }
+
// Test if currentDir is platform/frameworks/layoutlib. That is, root should be
// workingDir/../../ (2 levels up)
for (int i = 0; i < 2; i++) {
@@ -186,7 +200,8 @@
return null;
}
File[] hosts = host.listFiles(path -> path.isDirectory() &&
- (path.getName().startsWith("linux-") || path.getName().startsWith("darwin-")));
+ (path.getName().startsWith("linux-") ||
+ path.getName().startsWith("darwin-")));
assert hosts != null;
for (File hostOut : hosts) {
String platformDir = getPlatformDirFromHostOut(hostOut);
@@ -194,6 +209,7 @@
return platformDir;
}
}
+
return null;
}
@@ -321,12 +337,14 @@
}
@NonNull
- protected static RenderResult render(SessionParams params, long frameTimeNanos) {
+ protected static RenderResult render(com.android.ide.common.rendering.api.Bridge bridge,
+ SessionParams params,
+ long frameTimeNanos) {
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
System_Delegate.setBootTimeNanos(TimeUnit.MILLISECONDS.toNanos(871732800000L));
System_Delegate.setNanosTime(TimeUnit.MILLISECONDS.toNanos(871732800000L));
- RenderSession session = sBridge.createSession(params);
+ RenderSession session = bridge.createSession(params);
try {
if (frameTimeNanos != -1) {
@@ -353,6 +371,18 @@
}
/**
+ * Compares the golden image with the passed image
+ */
+ protected static void verify(@NonNull String goldenImageName, @NonNull BufferedImage image) {
+ try {
+ String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenImageName;
+ ImageUtils.requireSimilar(goldenImagePath, image);
+ } catch (IOException e) {
+ getLogger().error(e, e.getMessage());
+ }
+ }
+
+ /**
* Create a new rendering session and test that rendering the given layout doesn't throw any
* exceptions and matches the provided image.
* <p>
@@ -362,14 +392,9 @@
@Nullable
protected static RenderResult renderAndVerify(SessionParams params, String goldenFileName,
long frameTimeNanos) throws ClassNotFoundException {
- RenderResult result = RenderTestBase.render(params, frameTimeNanos);
- try {
- String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
- assertNotNull(result.getImage());
- ImageUtils.requireSimilar(goldenImagePath, result.getImage());
- } catch (IOException e) {
- getLogger().error(e, e.getMessage());
- }
+ RenderResult result = RenderTestBase.render(sBridge, params, frameTimeNanos);
+ assertNotNull(result.getImage());
+ verify(goldenFileName, result.getImage());
return result;
}
@@ -384,7 +409,7 @@
return RenderTestBase.renderAndVerify(params, goldenFileName, -1);
}
- private static LayoutLog getLayoutLog() {
+ protected static LayoutLog getLayoutLog() {
if (sLayoutLibLog == null) {
sLayoutLibLog = new LayoutLog() {
@Override
@@ -394,9 +419,8 @@
}
@Override
- public void fidelityWarning(@Nullable String tag, String message,
- Throwable throwable, Object data) {
-
+ public void fidelityWarning(String tag, String message, Throwable throwable,
+ Object viewCookie, Object data) {
System.out.println("FidelityWarning " + tag + ": " + message);
if (throwable != null) {
throwable.printStackTrace();
@@ -480,6 +504,8 @@
@Before
public void beforeTestCase() {
+ // Default class loader with access to the app classes
+ mDefaultClassLoader = new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
sRenderMessages.clear();
}
@@ -520,28 +546,28 @@
layoutLibCallback.initResources();
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
- return getSessionParams(parser, deviceConfig, layoutLibCallback, "AppTheme", true,
- RenderingMode.NORMAL, 22);
+ return getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(deviceConfig)
+ .setCallback(layoutLibCallback)
+ .build();
}
/**
- * Uses Theme.Material and Target sdk version as 22.
+ * Returns a pre-configured {@link SessionParamsBuilder} for target API 22, Normal rendering
+ * mode, AppTheme as theme and Nexus 5.
*/
- protected SessionParams getSessionParams(LayoutPullParser layoutParser,
- ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback,
- String themeName, boolean isProjectTheme, RenderingMode renderingMode,
- @SuppressWarnings("SameParameterValue") int targetSdk) {
- FolderConfiguration config = configGenerator.getFolderConfig();
- ResourceResolver resourceResolver =
- ResourceResolver.create(sProjectResources.getConfiguredResources(config),
- sFrameworkRepo.getConfiguredResources(config), themeName, isProjectTheme);
-
- SessionParams sessionParams =
- new SessionParams(layoutParser, renderingMode, null /*used for caching*/,
- configGenerator.getHardwareConfig(), resourceResolver, layoutLibCallback, 0,
- targetSdk, getLayoutLog());
- sessionParams.setFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE, true);
- sessionParams.setAssetRepository(new TestAssetRepository());
- return sessionParams;
+ @NonNull
+ protected SessionParamsBuilder getSessionParamsBuilder() {
+ return new SessionParamsBuilder()
+ .setLayoutLog(getLayoutLog())
+ .setFrameworkResources(sFrameworkRepo)
+ .setConfigGenerator(ConfigGenerator.NEXUS_5)
+ .setProjectResources(sProjectResources)
+ .setTheme("AppTheme", true)
+ .setRenderingMode(RenderingMode.NORMAL)
+ .setTargetSdk(22)
+ .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 b09184b..3b4851f 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java
@@ -17,6 +17,7 @@
package com.android.layoutlib.bridge.intensive;
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.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
@@ -36,11 +37,13 @@
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.content.res.AssetManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -50,8 +53,10 @@
import android.util.DisplayMetrics;
import android.util.StateSet;
import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -64,7 +69,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
@@ -72,6 +76,11 @@
*/
public class RenderTests extends RenderTestBase {
+ @After
+ public void afterTestCase() {
+ com.android.layoutlib.bridge.test.widgets.HookWidget.reset();
+ }
+
@Test
public void testActivity() throws ClassNotFoundException, FileNotFoundException {
renderAndVerify("activity.xml", "activity.png");
@@ -99,9 +108,11 @@
" android:background=\"#FF0000\"\n" +
" android:id=\"@+id/text1\"/>\n" +
"</RelativeLayout>");
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.NoTitleBar", false,
- RenderingMode.NORMAL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.NoTitleBar", false)
+ .build();
renderAndVerify(params, "simple_activity-old-theme.png");
}
@@ -113,21 +124,28 @@
layoutLibCallback.initResources();
LayoutPullParser parser = createParserFromPath("four_corners.xml");
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
- RenderingMode.NORMAL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar.TranslucentDecor", false)
+ .build();
renderAndVerify(params, "four_corners_translucent.png");
parser = createParserFromPath("four_corners.xml");
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5_LAND,
- layoutLibCallback, "Theme.Material.Light.NoActionBar.TranslucentDecor", false,
- RenderingMode.NORMAL, 22);
+ params = getSessionParamsBuilder()
+ .setConfigGenerator(ConfigGenerator.NEXUS_5_LAND)
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar.TranslucentDecor", false)
+ .build();
renderAndVerify(params, "four_corners_translucent_land.png");
parser = createParserFromPath("four_corners.xml");
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
- RenderingMode.NORMAL, 22);
+ params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar", false)
+ .build();
renderAndVerify(params, "four_corners.png");
}
@@ -175,25 +193,34 @@
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
- RenderingMode.V_SCROLL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "simple_activity_noactionbar.png");
parser = LayoutPullParser.createFromString(simpleActivity);
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light", false,
- RenderingMode.V_SCROLL, 22);
+ params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "simple_activity.png");
// This also tests that a theme with "NoActionBar" DOES HAVE an action bar when we are
// displaying menus.
parser = LayoutPullParser.createFromString(simpleActivity);
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
- RenderingMode.V_SCROLL, 22);
+ params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
params.setFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG, "menu");
renderAndVerify(params, "simple_activity.png");
}
@@ -222,11 +249,13 @@
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.Light.NoActionBar", false,
- RenderingMode.NORMAL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar", false)
+ .build();
- render(params, -1);
+ render(sBridge, params, -1);
assertTrue((Boolean)field.get(null));
field.set(null, false);
@@ -248,9 +277,13 @@
.setDensity(Density.XHIGH)
.setNavigation(Navigation.NONAV);
- SessionParams params = getSessionParams(parser, customConfigGenerator,
- layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(customConfigGenerator)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "expand_vert_layout.png");
@@ -260,9 +293,13 @@
.setDensity(Density.XHIGH)
.setNavigation(Navigation.NONAV);
parser = createParserFromPath("expand_horz_layout.xml");
- params = getSessionParams(parser, customConfigGenerator,
- layoutLibCallback, "Theme.Material.Light.NoActionBar.Fullscreen", false,
- RenderingMode.H_SCROLL, 22);
+ params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(customConfigGenerator)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.Light.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.H_SCROLL)
+ .build();
renderAndVerify(params, "expand_horz_layout.png");
}
@@ -287,16 +324,22 @@
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
parser = LayoutPullParser.createFromString(layout);
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
}
@@ -323,9 +366,12 @@
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "vector_drawable.png", TimeUnit.SECONDS.toNanos(2));
}
@@ -356,9 +402,12 @@
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "vector_drawable_91383.png", TimeUnit.SECONDS.toNanos(2));
}
@@ -386,14 +435,91 @@
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "vector_drawable_multi_line_of_path_data.png",
TimeUnit.SECONDS.toNanos(2));
}
+ /**
+ * Tests that the gradients are correctly transformed using the viewport of the vector drawable.
+ * <p/>
+ * If a vector drawable is 50x50 and the gradient has startX=25 and startY=25, the gradient
+ * will start in the middle of the box.
+ * <p/>
+ * http://b/65495452
+ */
+ @Test
+ public void testVectorDrawableGradient() 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/shadow\" />\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)
+ .build();
+
+ renderAndVerify(params, "vector_drawable_gradient.png",
+ TimeUnit.SECONDS.toNanos(2));
+ }
+
+ /**
+ * Tests that the radial gradients are correctly transformed using the viewport of the vector
+ * drawable.
+ * <p/>
+ * http://b/66168608
+ */
+ @Test
+ public void testVectorDrawableRadialGradient() 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/radial_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)
+ .build();
+
+ renderAndVerify(params, "vector_drawable_radial_gradient.png",
+ TimeUnit.SECONDS.toNanos(2));
+ }
+
/** Test activity.xml */
@Test
public void testScrollingAndMeasure() throws ClassNotFoundException, FileNotFoundException {
@@ -404,9 +530,12 @@
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
params.setForceNoDecor();
params.setExtendedViewInfoMode(true);
@@ -434,9 +563,12 @@
// Do a full render pass
parser = createParserFromPath("scrolled.xml");
- params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
params.setForceNoDecor();
params.setExtendedViewInfoMode(true);
@@ -456,8 +588,11 @@
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
- layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setConfigGenerator(ConfigGenerator.NEXUS_4)
+ .setCallback(layoutLibCallback)
+ .build();
AssetManager assetManager = AssetManager.getSystem();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
@@ -495,8 +630,11 @@
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
- layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setConfigGenerator(ConfigGenerator.NEXUS_4)
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .build();
AssetManager assetManager = AssetManager.getSystem();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
@@ -544,36 +682,45 @@
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "adaptive_icon.png");
layoutLibCallback.setAdaptiveIconMaskPath(
"M50 0C77.6 0 100 22.4 100 50C100 77.6 77.6 100 50 100C22.4 100 0 77.6 0 50C0 " +
"22.4 22.4 0 50 0Z");
- params =
- getSessionParams(LayoutPullParser.createFromString(layout), ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ params = getSessionParamsBuilder()
+ .setParser(LayoutPullParser.createFromString(layout))
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "adaptive_icon_circle.png");
layoutLibCallback.setAdaptiveIconMaskPath(
"M50,0L92,0C96.42,0 100,4.58 100 8L100,92C100, 96.42 96.42 100 92 100L8 100C4.58," +
" 100 0 96.42 0 92L0 8 C 0 4.42 4.42 0 8 0L50 0Z");
- params =
- getSessionParams(LayoutPullParser.createFromString(layout), ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ params = getSessionParamsBuilder()
+ .setParser(LayoutPullParser.createFromString(layout))
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "adaptive_icon_rounded_corners.png");
layoutLibCallback.setAdaptiveIconMaskPath(
"M50,0 C10,0 0,10 0,50 0,90 10,100 50,100 90,100 100,90 100,50 100,10 90,0 50,0 Z");
- params =
- getSessionParams(LayoutPullParser.createFromString(layout), ConfigGenerator.NEXUS_5,
- layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
- RenderingMode.V_SCROLL, 22);
+ params = getSessionParamsBuilder()
+ .setParser(LayoutPullParser.createFromString(layout))
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "adaptive_icon_squircle.png");
}
@@ -587,8 +734,11 @@
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
- layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setConfigGenerator(ConfigGenerator.NEXUS_4)
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .build();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
@@ -633,8 +783,12 @@
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
- layoutLibCallback, "Theme.Material", false, RenderingMode.NORMAL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setConfigGenerator(ConfigGenerator.NEXUS_4)
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material", false)
+ .build();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
@@ -647,8 +801,13 @@
try {
ColorStateList stateList = ResourceHelper.getColorStateList(
- new ResourceValue(ResourceUrl.create(null, ResourceType.COLOR, "test_list"),
- tmpColorList.getAbsolutePath()), mContext, null);
+ new ResourceValue(
+ ResourceNamespace.RES_AUTO,
+ ResourceType.COLOR,
+ "test_list",
+ tmpColorList.getAbsolutePath()),
+ mContext,
+ null);
assertNotNull(stateList);
assertEquals(Color.parseColor("#ffffffff"), stateList.getColorForState(
StateSet.get(StateSet.VIEW_STATE_PRESSED),
@@ -664,8 +823,13 @@
Resources.Theme theme = mContext.getResources().newTheme();
theme.applyStyle(R.style.ThemeOverlay_Material_Light, true);
stateList = ResourceHelper.getColorStateList(
- new ResourceValue(ResourceUrl.create(null, ResourceType.COLOR, "test_list"),
- tmpColorList.getAbsolutePath()), mContext, theme);
+ new ResourceValue(
+ ResourceNamespace.RES_AUTO,
+ ResourceType.COLOR,
+ "test_list",
+ tmpColorList.getAbsolutePath()),
+ mContext,
+ theme);
assertNotNull(stateList);
assertEquals(Color.parseColor("#ff000000"), stateList.getColorForState(
StateSet.get(StateSet.VIEW_STATE_PRESSED),
@@ -697,8 +861,11 @@
LayoutLibTestCallback layoutLibCallback =
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_4,
- layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setConfigGenerator(ConfigGenerator.NEXUS_4)
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .build();
AssetManager assetManager = AssetManager.getSystem();
DisplayMetrics metrics = new DisplayMetrics();
Configuration configuration = RenderAction.getConfiguration(params);
@@ -757,9 +924,225 @@
new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
layoutLibCallback.initResources();
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, layoutLibCallback,
- "Theme.Material.NoActionBar.Fullscreen", false, RenderingMode.V_SCROLL, 22);
+ SessionParams params = getSessionParamsBuilder()
+ .setParser(parser)
+ .setCallback(layoutLibCallback)
+ .setTheme("Theme.Material.NoActionBar.Fullscreen", false)
+ .setRenderingMode(RenderingMode.V_SCROLL)
+ .build();
renderAndVerify(params, "ninepatch_background.png");
}
+
+ @Test
+ public void testAssetManager() throws Exception {
+ String layout =
+ "<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.AssetView\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_width=\"wrap_content\" />\n" +
+ "</LinearLayout>\n";
+ 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)
+ .build();
+
+ renderAndVerify(params, "asset.png");
+ }
+
+ /**
+ * Tests that calling setTheme in a ContextThemeWrapper actually applies the theme
+ *
+ * http://b/66902070
+ */
+ @Test
+ public void testContextThemeWrapper() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = LayoutPullParser.createFromString(
+ "<com.android.layoutlib.test.myapplication.ThemableWidget " +
+ "xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\" />\n");
+ // 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)
+ .build();
+
+ renderAndVerify(params, "context_theme_wrapper.png", TimeUnit.SECONDS.toNanos(2));
+ }
+
+ /**
+ * Tests that a crashing view does not prevent others from working. This is meant to prevent
+ * crashes in framework views since custom views are already handled by Android Studio by
+ * rewriting the byte code.
+ */
+ @Test
+ public void testCrashes() throws ClassNotFoundException {
+ 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" +
+ "<com.android.layoutlib.bridge.test.widgets.HookWidget " +
+ " android:layout_width=\"100dp\"\n" +
+ " android:layout_height=\"200dp\" />\n" +
+ "<LinearLayout " +
+ " android:background=\"#CBBAF0\"\n" +
+ " android:layout_width=\"100dp\"\n" +
+ " android:layout_height=\"200dp\" />\n" +
+ "</LinearLayout>";
+ {
+ com.android.layoutlib.bridge.test.widgets.HookWidget.setOnPreDrawHook(() -> {
+ throw new NullPointerException();
+ });
+
+ // 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)
+ .build();
+
+ renderAndVerify(params, "ondraw_crash.png", TimeUnit.SECONDS.toNanos(2));
+ }
+
+ com.android.layoutlib.bridge.test.widgets.HookWidget.reset();
+
+ {
+ com.android.layoutlib.bridge.test.widgets.HookWidget.setOnPreMeasure(() -> {
+ throw new NullPointerException();
+ });
+
+ LayoutPullParser parser = LayoutPullParser.createFromString(layout);
+ 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)
+ .build();
+
+ renderAndVerify(params, "onmeasure_crash.png", TimeUnit.SECONDS.toNanos(2));
+ }
+
+ // We expect the view error messages. Fail for anything else.
+ sRenderMessages.removeIf(message -> message.equals("View draw failed"));
+ sRenderMessages.removeIf(message -> message.equals("View measure failed"));
+ }
+
+ /**
+ * Paints the borders of the given {@link ViewInfo} and its children to the passed
+ * {@link Graphics2D} context.
+ * The method will used the given parentLeft and parentTop as the given vInfo coordinates.
+ * The depth is used to calculate different colors for the borders depending on the hierarchy
+ * depth.
+ */
+ private void paintBorders(@NonNull Graphics2D g, int parentLeft, int parentTop, int depth,
+ @NonNull ViewInfo vInfo) {
+ int leftMargin = Math.max(0, vInfo.getLeftMargin());
+ int topMargin = Math.max(0, vInfo.getTopMargin());
+ int x = parentLeft + vInfo.getLeft() + leftMargin;
+ int y = parentTop + vInfo.getTop() + topMargin;
+ int w = vInfo.getRight() - vInfo.getLeft();
+ int h = vInfo.getBottom() - vInfo.getTop();
+ g.setXORMode(java.awt.Color.decode(Integer.toString(depth * 50000)));
+ g.drawRect(x, y, w, h);
+
+ for (ViewInfo child : vInfo.getChildren()) {
+ paintBorders(g, x, y, depth + 1, child);
+ }
+ }
+
+ @Test
+ public void testViewBoundariesReporting() throws Exception {
+ 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:background=\"@drawable/ninepatch\"\n" +
+ " android:layout_margin=\"20dp\"\n" +
+ " android:orientation=\"vertical\">\n" + "\n" +
+ " <TextView\n" +
+ " android:layout_width=\"150dp\"\n" +
+ " android:layout_height=\"50dp\"\n" +
+ " android:background=\"#FF0\"/>\n" +
+ " <TextView\n" +
+ " android:layout_width=\"150dp\"\n" +
+ " android:layout_height=\"50dp\"\n" +
+ " android:background=\"#F00\"/>\n" +
+ " <LinearLayout\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:paddingLeft=\"10dp\">\n" +
+ " <TextView\n" +
+ " android:layout_width=\"150dp\"\n" +
+ " android:layout_height=\"50dp\"\n" +
+ " android:background=\"#00F\"/>\n" +
+ " </LinearLayout>\n" +
+ " <LinearLayout\n" +
+ " android:layout_width=\"wrap_content\"\n" +
+ " android:layout_height=\"wrap_content\"\n" +
+ " android:layout_marginLeft=\"30dp\"\n" +
+ " android:layout_marginTop=\"15dp\">\n" +
+ " <TextView\n" +
+ " android:layout_width=\"150dp\"\n" +
+ " android:layout_height=\"50dp\"\n" +
+ " android:background=\"#F0F\"/>\n" +
+ " </LinearLayout>\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)
+ .build();
+ params.setForceNoDecor();
+
+ RenderResult result = RenderTestBase.render(sBridge, params, -1);
+ BufferedImage image = result.getImage();
+ assertNotNull(image);
+ Graphics2D g = (Graphics2D) image.getGraphics();
+ g.setStroke(new BasicStroke(8));
+ for (ViewInfo vInfo : result.getSystemViews()) {
+ paintBorders(g, 0, 0, 0, vInfo);
+ }
+
+ RenderTestBase.verify("view_boundaries.png", image);
+ }
}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
index 34fc726..684cf12 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
@@ -163,8 +163,7 @@
private Navigation mNavigation = Navigation.NONAV;
public FolderConfiguration getFolderConfig() {
- FolderConfiguration config = new FolderConfiguration();
- config.createDefault();
+ FolderConfiguration config = FolderConfiguration.createDefault();
config.setDensityQualifier(new DensityQualifier(mDensity));
config.setNavigationMethodQualifier(new NavigationMethodQualifier(mNavigation));
if (mScreenWidth > mScreenHeight) {
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 45facf5..4866051 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
@@ -86,8 +86,6 @@
public static void assertImageSimilar(String relativePath, BufferedImage goldenImage,
BufferedImage image, double maxPercentDifferent) throws IOException {
- assertEquals("Only TYPE_INT_ARGB image types are supported", TYPE_INT_ARGB, image.getType());
-
if (goldenImage.getType() != TYPE_INT_ARGB) {
BufferedImage temp = new BufferedImage(goldenImage.getWidth(), goldenImage.getHeight(),
TYPE_INT_ARGB);
@@ -165,7 +163,6 @@
"vs" + image.getWidth() + "x" + image.getHeight();
}
- assertEquals(TYPE_INT_ARGB, image.getType());
if (error != null) {
// Expected on the left
// Golden on the right
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
new file mode 100644
index 0000000..1c7857c
--- /dev/null
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java
@@ -0,0 +1,175 @@
+/*
+ * 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.intensive.util;
+
+import com.android.SdkConstants;
+import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
+import com.android.ide.common.rendering.api.ResourceNamespace;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.deprecated.ResourceRepository;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
+import com.android.resources.ResourceType;
+
+import android.annotation.NonNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * Builder to help setting up {@link SessionParams} objects.
+ */
+public class SessionParamsBuilder {
+
+ private LayoutPullParser mLayoutParser;
+ private RenderingMode mRenderingMode = RenderingMode.NORMAL;
+ private Object mProjectKey = null;
+ private ConfigGenerator mConfigGenerator = ConfigGenerator.NEXUS_5;
+ private ResourceRepository mFrameworkResources;
+ private ResourceRepository mProjectResources;
+ private String mThemeName;
+ private boolean isProjectTheme;
+ private LayoutlibCallback mLayoutlibCallback;
+ private int mTargetSdk;
+ private int mMinSdk = 0;
+ private LayoutLog mLayoutLog;
+ private Map<SessionParams.Key, Object> mFlags = new HashMap<>();
+ private AssetRepository mAssetRepository = null;
+
+ @NonNull
+ public SessionParamsBuilder setParser(@NonNull LayoutPullParser layoutParser) {
+ mLayoutParser = layoutParser;
+
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setRenderingMode(@NonNull RenderingMode renderingMode) {
+ mRenderingMode = renderingMode;
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setConfigGenerator(@NonNull ConfigGenerator configGenerator) {
+ mConfigGenerator = configGenerator;
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setProjectResources(@NonNull ResourceRepository resources) {
+ mProjectResources = resources;
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setFrameworkResources(@NonNull ResourceRepository resources) {
+ mFrameworkResources = resources;
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setTheme(@NonNull String themeName, boolean isProjectTheme) {
+ mThemeName = themeName;
+ this.isProjectTheme = isProjectTheme;
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setTheme(@NonNull String themeName) {
+ boolean isProjectTheme;
+ if (themeName.startsWith(SdkConstants.PREFIX_ANDROID)) {
+ themeName = themeName.substring(SdkConstants.PREFIX_ANDROID.length());
+ isProjectTheme = false;
+ } else {
+ isProjectTheme = true;
+ }
+ return setTheme(themeName, isProjectTheme);
+ }
+
+ @NonNull
+ public SessionParamsBuilder setCallback(@NonNull LayoutlibCallback callback) {
+ mLayoutlibCallback = callback;
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setTargetSdk(int targetSdk) {
+ mTargetSdk = targetSdk;
+ return this;
+ }
+
+ @SuppressWarnings("unused")
+ @NonNull
+ public SessionParamsBuilder setMinSdk(int minSdk) {
+ mMinSdk = minSdk;
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setLayoutLog(@NonNull LayoutLog layoutLog) {
+ mLayoutLog = layoutLog;
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setFlag(@NonNull SessionParams.Key flag, Object value) {
+ mFlags.put(flag, value);
+ return this;
+ }
+
+ @NonNull
+ public SessionParamsBuilder setAssetRepository(@NonNull AssetRepository repository) {
+ mAssetRepository = repository;
+ return this;
+ }
+
+ @NonNull
+ public SessionParams build() {
+ assert mFrameworkResources != null;
+ assert mProjectResources != null;
+ assert mThemeName != null;
+ assert mLayoutLog != null;
+ assert mLayoutlibCallback != null;
+
+ FolderConfiguration config = mConfigGenerator.getFolderConfig();
+ ResourceResolver resourceResolver = ResourceResolver.create(
+ ImmutableMap.of(
+ ResourceNamespace.ANDROID, mFrameworkResources.getConfiguredResources(config),
+ ResourceNamespace.TODO, mProjectResources.getConfiguredResources(config)),
+ new ResourceReference(
+ ResourceNamespace.fromBoolean(!isProjectTheme),
+ ResourceType.STYLE,
+ mThemeName));
+
+ SessionParams params = new SessionParams(mLayoutParser, mRenderingMode, mProjectKey /* for
+ caching */, mConfigGenerator.getHardwareConfig(), resourceResolver, mLayoutlibCallback,
+ mMinSdk, mTargetSdk, mLayoutLog);
+
+ mFlags.forEach(params::setFlag);
+ params.setAssetRepository(mAssetRepository);
+
+ return params;
+ }
+}
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
index 0856ac9..54af92d 100644
--- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
+++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/util/TestAssetRepository.java
@@ -18,6 +18,8 @@
import com.android.ide.common.rendering.api.AssetRepository;
+import android.annotation.NonNull;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -28,6 +30,12 @@
* {@link AssetRepository} used for render tests.
*/
public class TestAssetRepository extends AssetRepository {
+ private final String mAssetPath;
+
+ public TestAssetRepository(@NonNull String assetPath) {
+ mAssetPath = assetPath;
+ }
+
private static InputStream open(String path) throws FileNotFoundException {
File asset = new File(path);
if (asset.isFile()) {
@@ -44,7 +52,7 @@
@Override
public InputStream openAsset(String path, int mode) throws IOException {
- return open(path);
+ return open(mAssetPath + path);
}
@Override
diff --git a/bridge/tests/src/com/android/layoutlib/bridge/test/widgets/HookWidget.java b/bridge/tests/src/com/android/layoutlib/bridge/test/widgets/HookWidget.java
new file mode 100644
index 0000000..7efb9b8
--- /dev/null
+++ b/bridge/tests/src/com/android/layoutlib/bridge/test/widgets/HookWidget.java
@@ -0,0 +1,113 @@
+/*
+ * 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.test.widgets;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+
+@SuppressWarnings("unused") // Used from RenderTests
+public class HookWidget extends View {
+ private static final Runnable NOP_RUNNABLE = () -> {};
+ private static Runnable sOnPreDrawHook = NOP_RUNNABLE;
+ private static Runnable sOnPreMeasure = NOP_RUNNABLE;
+ private static Runnable sOnPreLayout = NOP_RUNNABLE;
+ private static Runnable sOnPostDrawHook = NOP_RUNNABLE;
+ private static Runnable sOnPostMeasure = NOP_RUNNABLE;
+ private static Runnable sOnPostLayout = NOP_RUNNABLE;
+
+ public static void setOnPreDrawHook(Runnable runnable) {
+ sOnPreDrawHook = runnable;
+ }
+
+ public static void setOnPreMeasure(Runnable runnable) {
+ sOnPreMeasure = runnable;
+ }
+
+ public static void setOnPreLayout(Runnable runnable) {
+ sOnPreLayout = runnable;
+ }
+
+ public static void setOnPostDrawHook(Runnable onPostDrawHook) {
+ sOnPostDrawHook = onPostDrawHook;
+ }
+
+ public static void setOnPostMeasure(Runnable onPostMeasure) {
+ sOnPostMeasure = onPostMeasure;
+ }
+
+ public static void setOnPostLayout(Runnable onPostLayout) {
+ sOnPostLayout = onPostLayout;
+ }
+
+ public static void reset() {
+ sOnPreDrawHook = NOP_RUNNABLE;
+ sOnPreMeasure = NOP_RUNNABLE;
+ sOnPreLayout = NOP_RUNNABLE;
+ sOnPostDrawHook = NOP_RUNNABLE;
+ sOnPostMeasure = NOP_RUNNABLE;
+ sOnPostLayout = NOP_RUNNABLE;
+ }
+
+ public HookWidget(Context context) {
+ super(context);
+ }
+
+ public HookWidget(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public HookWidget(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public HookWidget(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ sOnPreDrawHook.run();
+
+ super.onDraw(canvas);
+ Paint paint = new Paint();
+ paint.setColor(Color.BLUE);
+ Rect rect = new Rect();
+ getDrawingRect(rect);
+ canvas.drawRect(rect, paint);
+
+ sOnPostDrawHook.run();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ sOnPreMeasure.run();
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ sOnPostMeasure.run();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ sOnPreLayout.run();
+ super.onLayout(changed, left, top, right, bottom);
+ sOnPostLayout.run();
+ }
+}
diff --git a/common/src/com/android/tools/layoutlib/annotations/NotNull.java b/common/src/com/android/tools/layoutlib/annotations/NotNull.java
new file mode 100644
index 0000000..4dcb24b
--- /dev/null
+++ b/common/src/com/android/tools/layoutlib/annotations/NotNull.java
@@ -0,0 +1,34 @@
+/*
+ * 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.tools.layoutlib.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a parameter or field can not be null.
+ * <p/>
+ * When decorating a method call parameter, this denotes the parameter can
+ * not be null.
+ * <p/>
+ * When decorating a method, this denotes the method can not return null.
+ * <p/>
+ * This is a marker annotation and it has no specific attributes.
+ */
+@Retention(RetentionPolicy.SOURCE)
+public @interface NotNull {
+}
diff --git a/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index f595803..f57e1e6 100644
--- a/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -132,6 +132,7 @@
"android.content.res.Resources#getDimensionPixelOffset",
"android.content.res.Resources#getDimensionPixelSize",
"android.content.res.Resources#getDrawable",
+ "android.content.res.Resources#getFloat",
"android.content.res.Resources#getFont",
"android.content.res.Resources#getIdentifier",
"android.content.res.Resources#getIntArray",
@@ -148,6 +149,7 @@
"android.content.res.Resources#getText",
"android.content.res.Resources#getTextArray",
"android.content.res.Resources#getValue",
+ "android.content.res.Resources#getValueForDensity",
"android.content.res.Resources#getXml",
"android.content.res.Resources#loadXmlResourceParser",
"android.content.res.Resources#obtainAttributes",
@@ -157,6 +159,7 @@
"android.content.res.Resources$Theme#obtainStyledAttributes",
"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#getAssignedPackageIdentifiers",
@@ -169,13 +172,15 @@
"android.graphics.drawable.GradientDrawable#buildRing",
"android.graphics.drawable.AdaptiveIconDrawable#<init>",
"android.graphics.FontFamily#addFont",
- "android.graphics.Typeface#getSystemFontConfigLocation",
- "android.graphics.Typeface#makeFamilyFromParsed",
+ "android.graphics.Typeface#buildSystemFallback",
+ "android.graphics.Typeface#create",
+ "android.graphics.Typeface#createFontFamily",
+ "android.os.Binder#getNativeBBinderHolder",
+ "android.os.Binder#getNativeFinalizer",
"android.os.Handler#sendMessageAtTime",
"android.os.HandlerThread#run",
"android.preference.Preference#getView",
"android.text.format.DateFormat#is24HourFormat",
- "android.text.Hyphenator#getSystemHyphenatorLocation",
"android.util.Xml#newPullParser",
"android.view.Choreographer#getInstance",
"android.view.Choreographer#getRefreshRate",
@@ -185,6 +190,9 @@
"android.view.HandlerActionQueue#postDelayed",
"android.view.LayoutInflater#rInflate",
"android.view.LayoutInflater#parseInclude",
+ "android.view.View#draw",
+ "android.view.View#layout",
+ "android.view.View#measure",
"android.view.View#getWindowToken",
"android.view.View#isInEditMode",
"android.view.ViewRootImpl#isInTouchMode",
@@ -272,7 +280,7 @@
"android.graphics.drawable.VectorDrawable",
"android.os.SystemClock",
"android.os.SystemProperties",
- "android.text.AndroidBidi",
+ "android.text.MeasuredParagraph",
"android.text.StaticLayout",
"android.util.PathParser",
"android.view.Display",
@@ -293,6 +301,7 @@
"android.view.SurfaceView", "android.view._Original_SurfaceView",
"android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager",
"android.webkit.WebView", "android.webkit._Original_WebView",
+ "android.graphics.ImageDecoder", "android.graphics._Original_ImageDecoder",
};
/**
diff --git a/remote/client/remote client.iml b/remote/client/remote client.iml
new file mode 100644
index 0000000..051a2c1
--- /dev/null
+++ b/remote/client/remote client.iml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="file://$MODULE_DIR$/../../../../libcore/xml/src/main/java" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module" module-name="remote common" />
+ <orderEntry type="module" module-name="common" />
+ </component>
+</module>
\ No newline at end of file
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
new file mode 100644
index 0000000..99143ad
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/RemoteBridgeClient.java
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.Bridge;
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layout.remote.api.RemoteBridge;
+import com.android.layout.remote.api.RemoteDrawableParams;
+import com.android.layout.remote.api.RemoteSessionParams;
+import com.android.layoutlib.bridge.remote.client.adapters.RemoteDrawableParamsAdapter;
+import com.android.layoutlib.bridge.remote.client.adapters.RemoteLayoutLogAdapter;
+import com.android.layoutlib.bridge.remote.client.adapters.RemoteRenderSessionAdapter;
+import com.android.layoutlib.bridge.remote.client.adapters.RemoteSessionParamsAdapter;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.File;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.util.Map;
+
+public class RemoteBridgeClient extends Bridge {
+ private final RemoteBridge mDelegate;
+
+ private RemoteBridgeClient(@NotNull RemoteBridge delegate) {
+ mDelegate = delegate;
+ }
+
+ @NotNull
+ public static RemoteBridgeClient getRemoteBridge(int registryPort) throws RemoteException,
+ NotBoundException {
+ Registry registry = LocateRegistry.getRegistry(registryPort);
+ RemoteBridge remoteBridge = (RemoteBridge) registry.lookup(RemoteBridge.class.getName());
+
+ return new RemoteBridgeClient(remoteBridge);
+ }
+
+ @Override
+ public int getApiLevel() {
+ try {
+ return mDelegate.getApiLevel();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+
+ }
+ }
+
+ @Override
+ public int getRevision() {
+ try {
+ return mDelegate.getRevision();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean supports(int feature) {
+ try {
+ return mDelegate.supports(feature);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean init(Map<String, String> platformProperties, File fontLocation,
+ Map<String, Map<String, Integer>> enumValueMap, LayoutLog log) {
+ try {
+ return mDelegate.init(platformProperties, fontLocation, enumValueMap,
+ RemoteLayoutLogAdapter.create(log));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean dispose() {
+ try {
+ return mDelegate.dispose();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public RenderSession createSession(SessionParams params) {
+ try {
+ RemoteSessionParams remoteParams = RemoteSessionParamsAdapter.create(params);
+
+ return new RemoteRenderSessionAdapter(mDelegate.createSession(remoteParams));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Result renderDrawable(DrawableParams params) {
+ try {
+ return mDelegate.renderDrawable(RemoteDrawableParamsAdapter.create(params));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void clearCaches(Object projectKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Result getViewParent(Object viewObject) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Result getViewIndex(Object viewObject) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isRtl(String locale) {
+ try {
+ return mDelegate.isRtl(locale);
+ } 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
new file mode 100644
index 0000000..e1cfe15
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteActionBarCallbackAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * 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.ActionBarCallback;
+import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
+import com.android.layout.remote.api.RemoteActionBarCallback;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.List;
+
+class RemoteActionBarCallbackAdapter implements RemoteActionBarCallback {
+ private final ActionBarCallback mDelegate;
+
+ private RemoteActionBarCallbackAdapter(@NotNull ActionBarCallback delegate) {
+ mDelegate = delegate;
+ }
+
+ public static RemoteActionBarCallback create(@NotNull ActionBarCallback delegate)
+ throws RemoteException {
+ return (RemoteActionBarCallback) UnicastRemoteObject.exportObject(
+ new RemoteActionBarCallbackAdapter(delegate), 0);
+ }
+
+ @Override
+ public List<String> getMenuIdNames() {
+ return mDelegate.getMenuIdNames();
+ }
+
+ @Override
+ public boolean getSplitActionBarWhenNarrow() {
+ return mDelegate.getSplitActionBarWhenNarrow();
+ }
+
+ @Override
+ public int getNavigationMode() {
+ return mDelegate.getNavigationMode();
+ }
+
+ @Override
+ public String getSubTitle() {
+ return mDelegate.getSubTitle();
+ }
+
+ @Override
+ public HomeButtonStyle getHomeButtonStyle() {
+ return mDelegate.getHomeButtonStyle();
+ }
+
+ @Override
+ public boolean isOverflowPopupNeeded() {
+ return mDelegate.isOverflowPopupNeeded();
+ }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteAssetRepositoryAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteAssetRepositoryAdapter.java
new file mode 100644
index 0000000..4def740
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteAssetRepositoryAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.AssetRepository;
+import com.android.layout.remote.api.RemoteAssetRepository;
+import com.android.layout.remote.util.RemoteInputStream;
+import com.android.layout.remote.util.RemoteInputStreamAdapter;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteAssetRepositoryAdapter implements RemoteAssetRepository {
+ private final AssetRepository mDelegate;
+
+ private RemoteAssetRepositoryAdapter(@NotNull AssetRepository delegate) {
+ mDelegate = delegate;
+ }
+
+ static RemoteAssetRepository create(@NotNull AssetRepository delegate) throws RemoteException {
+ return (RemoteAssetRepository) UnicastRemoteObject.exportObject(
+ new RemoteAssetRepositoryAdapter(delegate), 0);
+ }
+
+ @Override
+ public RemoteInputStream openAsset(String path, int mode) throws IOException, RemoteException {
+ return RemoteInputStreamAdapter.create(mDelegate.openAsset(path, mode));
+ }
+
+ @Override
+ public RemoteInputStream openNonAsset(int cookie, String path, int mode)
+ throws IOException, RemoteException {
+ return RemoteInputStreamAdapter.create(mDelegate.openNonAsset(cookie, path, mode));
+ }
+
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteDrawableParamsAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteDrawableParamsAdapter.java
new file mode 100644
index 0000000..d2c2cd8
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteDrawableParamsAdapter.java
@@ -0,0 +1,48 @@
+/*
+ * 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.DrawableParams;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.layout.remote.api.RemoteDrawableParams;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteDrawableParamsAdapter extends RemoteRenderParamsAdapter implements
+ RemoteDrawableParams {
+ private final DrawableParams mDelegate;
+
+ private RemoteDrawableParamsAdapter(@NotNull DrawableParams drawableParams) {
+ super(drawableParams);
+ mDelegate = drawableParams;
+ }
+
+ @NotNull
+ public static RemoteDrawableParams create(@NotNull DrawableParams drawableParams)
+ throws RemoteException {
+ return (RemoteDrawableParams) UnicastRemoteObject.exportObject(
+ new RemoteDrawableParamsAdapter(drawableParams), 0);
+ }
+
+ @NotNull
+ @Override
+ public ResourceValue getDrawable() throws RemoteException {
+ return mDelegate.getDrawable();
+ }
+}
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
new file mode 100644
index 0000000..451d93e
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteILayoutPullParserAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * 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.ILayoutPullParser;
+import com.android.layout.remote.api.RemoteILayoutPullParser;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+class RemoteILayoutPullParserAdapter extends RemoteXmlPullParserAdapter
+ implements RemoteILayoutPullParser {
+ private RemoteILayoutPullParserAdapter(@NotNull ILayoutPullParser delegate) {
+ super(delegate);
+ }
+
+ public static RemoteILayoutPullParser create(@NotNull ILayoutPullParser delegate)
+ throws RemoteException {
+ return (RemoteILayoutPullParser) UnicastRemoteObject.exportObject(
+ new RemoteILayoutPullParserAdapter(delegate), 0);
+ }
+
+ @Override
+ public Object getViewCookie() throws RemoteException {
+ return ((ILayoutPullParser) mDelegate).getViewCookie();
+ }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutLogAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutLogAdapter.java
new file mode 100644
index 0000000..c5dbfab
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutLogAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * 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.LayoutLog;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteLayoutLogAdapter implements RemoteLayoutLog {
+ private final LayoutLog mLog;
+
+ private RemoteLayoutLogAdapter(@NotNull LayoutLog log) {
+ mLog = log;
+ }
+
+ public static RemoteLayoutLog create(@NotNull LayoutLog log) throws RemoteException {
+ return (RemoteLayoutLog) UnicastRemoteObject.exportObject(new RemoteLayoutLogAdapter(log),
+ 0);
+ }
+
+ @Override
+ public void warning(String tag, String message, Serializable data) {
+ mLog.warning(tag, message, null);
+ }
+
+ @Override
+ public void fidelityWarning(String tag, String message, Throwable throwable, Object viewCookie,
+ Object data) {
+ mLog.fidelityWarning(tag, message, throwable, viewCookie, data);
+ }
+
+ @Override
+ public void error(String tag, String message, Serializable data) {
+ mLog.error(tag, message, null);
+ }
+
+ @Override
+ public void error(String tag, String message, Throwable throwable, Serializable data) {
+ mLog.error(tag, message, throwable, null);
+ }
+}
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
new file mode 100644
index 0000000..b9b1a00
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -0,0 +1,159 @@
+/*
+ * 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.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.ResourceReference;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.layout.remote.api.RemoteActionBarCallback;
+import com.android.layout.remote.api.RemoteILayoutPullParser;
+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;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteLayoutlibCallbackAdapter implements RemoteLayoutlibCallback {
+ private final LayoutlibCallback mDelegate;
+
+ private RemoteLayoutlibCallbackAdapter(@NotNull LayoutlibCallback delegate) {
+ mDelegate = delegate;
+ }
+
+ public static RemoteLayoutlibCallback create(@NotNull LayoutlibCallback delegate)
+ throws RemoteException {
+ return (RemoteLayoutlibCallback) UnicastRemoteObject.exportObject(
+ new RemoteLayoutlibCallbackAdapter(delegate), 0);
+ }
+
+ @Override
+ public boolean supports(int ideFeature) {
+ return mDelegate.supports(ideFeature);
+ }
+
+ @Override
+ public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
+ throws Exception {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ @Override
+ public String getNamespace() {
+ return mDelegate.getNamespace();
+ }
+
+ @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) {
+ return mDelegate.resolveResourceId(id);
+ }
+
+ @Override
+ public Integer getResourceId(ResourceType type, String name) {
+ return mDelegate.getResourceId(type, name);
+ }
+
+ @Override
+ public RemoteILayoutPullParser getParser(ResourceValue layoutResource) {
+ try {
+ return RemoteILayoutPullParserAdapter.create(mDelegate.getParser(layoutResource));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
+ ResourceReference itemRef, int fullPosition, int positionPerType,
+ int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
+ ViewAttribute viewAttribute, Object defaultValue) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ @Override
+ public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
+ Object viewObject) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ @Override
+ public RemoteActionBarCallback getActionBarCallback() {
+ try {
+ return RemoteActionBarCallbackAdapter.create(mDelegate.getActionBarCallback());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public <T> T getFlag(Key<T> key) {
+ 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) {
+ try {
+ Class<?> clazz = mDelegate.findClass(name);
+ URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
+ if (url != null) {
+ return Paths.get(url.toURI());
+ }
+ } catch (ClassNotFoundException ignore) {
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+
+ return null;
+ }
+
+ @Override
+ public RemoteXmlPullParser getXmlFileParser(String fileName) {
+ try {
+ return RemoteXmlPullParserAdapter.create(mDelegate.getXmlFileParser(fileName));
+ } 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
new file mode 100644
index 0000000..d1f0411
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteParserFactoryAdapter.java
@@ -0,0 +1,51 @@
+/*
+ * 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
new file mode 100644
index 0000000..a3950d7
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderParamsAdapter.java
@@ -0,0 +1,161 @@
+/*
+ * 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.IImageFactory;
+import com.android.ide.common.rendering.api.RenderParams;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.layout.remote.api.RemoteAssetRepository;
+import com.android.layout.remote.api.RemoteHardwareConfig;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.layout.remote.api.RemoteLayoutlibCallback;
+import com.android.layout.remote.api.RemoteRenderParams;
+import com.android.layout.remote.api.RemoteRenderResources;
+import com.android.layout.remote.api.RemoteSessionParams;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteRenderParamsAdapter implements RemoteRenderParams {
+ private final RenderParams mDelegate;
+
+ protected RemoteRenderParamsAdapter(@NotNull RenderParams params) {
+ mDelegate = params;
+ }
+
+ public static RemoteSessionParams create(@NotNull SessionParams params) throws RemoteException {
+ return (RemoteSessionParams) UnicastRemoteObject.exportObject(
+ new RemoteRenderParamsAdapter(params), 0);
+ }
+
+ @Nullable
+ @Override
+ public String getProjectKey() {
+ Object projectKey = mDelegate.getProjectKey();
+ // We can not transfer a random object so let's send just a string
+ return projectKey != null ? projectKey.toString() : null;
+ }
+
+ @Override
+ public RemoteHardwareConfig getRemoteHardwareConfig() {
+ return new RemoteHardwareConfig(mDelegate.getHardwareConfig());
+ }
+
+ @Override
+ public int getMinSdkVersion() {
+ return mDelegate.getMinSdkVersion();
+ }
+
+ @Override
+ public int getTargetSdkVersion() {
+ return mDelegate.getTargetSdkVersion();
+ }
+
+ @Override
+ public RemoteRenderResources getRemoteResources() {
+ try {
+ return RemoteRenderResourcesAdapter.create(mDelegate.getResources());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public RemoteAssetRepository getAssets() {
+ try {
+ return RemoteAssetRepositoryAdapter.create(mDelegate.getAssets());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public RemoteLayoutlibCallback getRemoteLayoutlibCallback() {
+ try {
+ return RemoteLayoutlibCallbackAdapter.create(mDelegate.getLayoutlibCallback());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public RemoteLayoutLog getLog() {
+ try {
+ return RemoteLayoutLogAdapter.create(mDelegate.getLog());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isBgColorOverridden() {
+ return mDelegate.isBgColorOverridden();
+ }
+
+ @Override
+ public int getOverrideBgColor() {
+ return mDelegate.getOverrideBgColor();
+ }
+
+ @Override
+ public long getTimeout() {
+ return mDelegate.getTimeout();
+ }
+
+ @Override
+ public IImageFactory getImageFactory() {
+ return mDelegate.getImageFactory();
+ }
+
+ @Override
+ public String getAppIcon() {
+ return mDelegate.getAppIcon();
+ }
+
+ @Override
+ public String getAppLabel() {
+ return mDelegate.getAppLabel();
+ }
+
+ @Override
+ public String getLocale() {
+ return mDelegate.getLocale();
+ }
+
+ @Override
+ public String getActivityName() {
+ return mDelegate.getActivityName();
+ }
+
+ @Override
+ public boolean isForceNoDecor() {
+ return mDelegate.isForceNoDecor();
+ }
+
+ @Override
+ public boolean isRtlSupported() {
+ return mDelegate.isRtlSupported();
+ }
+
+ @Override
+ public <T> T getFlag(Key<T> key) {
+ return mDelegate.getFlag(key);
+ }
+}
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
new file mode 100644
index 0000000..9ae58a9
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderResourcesAdapter.java
@@ -0,0 +1,124 @@
+/*
+ * 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.RenderResources;
+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.layout.remote.api.RemoteRenderResources;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.List;
+
+public class RemoteRenderResourcesAdapter implements RemoteRenderResources {
+ private final RenderResources mDelegate;
+
+ private RemoteRenderResourcesAdapter(@NotNull RenderResources delegate) {
+ mDelegate = delegate;
+ }
+
+ public static RemoteRenderResources create(@NotNull RenderResources resources)
+ throws RemoteException {
+ return (RemoteRenderResources) UnicastRemoteObject.exportObject(
+ new RemoteRenderResourcesAdapter(resources), 0);
+ }
+
+ @Override
+ public StyleResourceValue getDefaultTheme() {
+ return mDelegate.getDefaultTheme();
+ }
+
+ @Override
+ public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
+ mDelegate.applyStyle(theme, useAsPrimary);
+ }
+
+ @Override
+ public void clearStyles() {
+ mDelegate.clearStyles();
+ }
+
+ @Override
+ public List<StyleResourceValue> getAllThemes() {
+ return mDelegate.getAllThemes();
+ }
+
+ @Override
+ public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
+ return mDelegate.getTheme(name, frameworkTheme);
+ }
+
+ @Override
+ public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
+ return mDelegate.themeIsParentOf(parentTheme, childTheme);
+ }
+
+ @Override
+ public ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName) {
+ return mDelegate.getFrameworkResource(resourceType, resourceName);
+ }
+
+ @Override
+ public ResourceValue getProjectResource(ResourceType resourceType, String resourceName) {
+ return mDelegate.getProjectResource(resourceType, resourceName);
+ }
+
+ @Override
+ public ResourceValue findItemInTheme(ResourceReference attr) {
+ return mDelegate.findItemInTheme(attr);
+ }
+
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, ResourceReference attr) {
+ return mDelegate.findItemInStyle(style, attr);
+ }
+
+ @Override
+ public ResourceValue resolveValue(ResourceValue value) {
+ return mDelegate.resolveResValue(value);
+ }
+
+ @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);
+ }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderSessionAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderSessionAdapter.java
new file mode 100644
index 0000000..c2c892e
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteRenderSessionAdapter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.ViewInfo;
+import com.android.layout.remote.api.RemoteRenderSession;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.awt.image.BufferedImage;
+import java.rmi.RemoteException;
+import java.util.Collections;
+import java.util.List;
+
+public class RemoteRenderSessionAdapter extends RenderSession {
+ private final RemoteRenderSession mDelegate;
+
+ public RemoteRenderSessionAdapter(@NotNull RemoteRenderSession delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public Result getResult() {
+ try {
+ return mDelegate.getResult();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public BufferedImage getImage() {
+ try {
+ return mDelegate.getSerializableImage().getBufferedImage();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void setSystemTimeNanos(long nanos) {
+ try {
+ mDelegate.setSystemTimeNanos(nanos);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void setSystemBootTimeNanos(long nanos) {
+ try {
+ mDelegate.setSystemBootTimeNanos(nanos);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void setElapsedFrameTimeNanos(long nanos) {
+ try {
+ mDelegate.setElapsedFrameTimeNanos(nanos);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Result render(long timeout, boolean forceMeasure) {
+ try {
+ return mDelegate.render(timeout, forceMeasure);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public List<ViewInfo> getRootViews() {
+ // TODO
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<ViewInfo> getSystemRootViews() {
+ // TODO
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void dispose() {
+ try {
+ mDelegate.dispose();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteSessionParamsAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteSessionParamsAdapter.java
new file mode 100644
index 0000000..e6ed871
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteSessionParamsAdapter.java
@@ -0,0 +1,80 @@
+/*
+ * 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.AdapterBinding;
+import com.android.ide.common.rendering.api.IImageFactory;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+import com.android.layout.remote.api.RemoteAssetRepository;
+import com.android.layout.remote.api.RemoteHardwareConfig;
+import com.android.layout.remote.api.RemoteILayoutPullParser;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.layout.remote.api.RemoteLayoutlibCallback;
+import com.android.layout.remote.api.RemoteRenderResources;
+import com.android.layout.remote.api.RemoteSessionParams;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Map;
+
+public class RemoteSessionParamsAdapter extends RemoteRenderParamsAdapter implements RemoteSessionParams {
+ private final SessionParams mDelegate;
+
+ private RemoteSessionParamsAdapter(@NotNull SessionParams params) {
+ super(params);
+ mDelegate = params;
+ }
+
+ public static RemoteSessionParams create(@NotNull SessionParams params) throws RemoteException {
+ return (RemoteSessionParams) UnicastRemoteObject.exportObject(
+ new RemoteSessionParamsAdapter(params), 0);
+ }
+
+ @Override
+ public RenderingMode getRenderingMode() {
+ return mDelegate.getRenderingMode();
+ }
+
+ @Override
+ public boolean isLayoutOnly() {
+ return mDelegate.isLayoutOnly();
+ }
+
+ @Override
+ public Map<ResourceReference, AdapterBinding> getAdapterBindings() {
+ return mDelegate.getAdapterBindings();
+ }
+
+ @Override
+ public boolean getExtendedViewInfoMode() {
+ return mDelegate.getExtendedViewInfoMode();
+ }
+
+ @Override
+ public int getSimulatedPlatformVersion() {
+ return mDelegate.getSimulatedPlatformVersion();
+ }
+
+ @Override
+ public RemoteILayoutPullParser getLayoutDescription() throws RemoteException {
+ return RemoteILayoutPullParserAdapter.create(mDelegate.getLayoutDescription());
+ }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteXmlPullParserAdapter.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteXmlPullParserAdapter.java
new file mode 100644
index 0000000..e646bbc
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/RemoteXmlPullParserAdapter.java
@@ -0,0 +1,234 @@
+/*
+ * 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.layout.remote.api.RemoteXmlPullParser;
+import com.android.layout.remote.util.RemoteInputStream;
+import com.android.layout.remote.util.StreamUtil;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteXmlPullParserAdapter implements RemoteXmlPullParser {
+ protected XmlPullParser mDelegate;
+
+ protected RemoteXmlPullParserAdapter(@NotNull XmlPullParser delegate) {
+ mDelegate = delegate;
+ }
+
+ public static RemoteXmlPullParser create(@NotNull XmlPullParser delegate)
+ throws RemoteException {
+ return (RemoteXmlPullParser) UnicastRemoteObject.exportObject(
+ new RemoteXmlPullParserAdapter(delegate), 0);
+ }
+
+ @Override
+ public void setFeature(String name, boolean state)
+ throws XmlPullParserException, RemoteException {
+ mDelegate.setFeature(name, state);
+ }
+
+ @Override
+ public boolean getFeature(String name) throws RemoteException {
+ return mDelegate.getFeature(name);
+ }
+
+ @Override
+ public void setProperty(String name, Object value)
+ throws XmlPullParserException, RemoteException {
+ mDelegate.setProperty(name, value);
+ }
+
+ @Override
+ public Object getProperty(String name) throws RemoteException {
+ return mDelegate.getProperty(name);
+ }
+
+ @Override
+ public void setInput(Reader in) throws XmlPullParserException, RemoteException {
+ mDelegate.setInput(in);
+ }
+
+ @Override
+ public void setInput(RemoteInputStream inputStream, String inputEncoding)
+ throws XmlPullParserException, RemoteException {
+ mDelegate.setInput(StreamUtil.getInputStream(inputStream), inputEncoding);
+ }
+
+ @Override
+ public String getInputEncoding() throws RemoteException {
+ return mDelegate.getInputEncoding();
+ }
+
+ @Override
+ public void defineEntityReplacementText(String entityName, String replacementText)
+ throws XmlPullParserException {
+
+ }
+
+ @Override
+ public int getNamespaceCount(int depth) throws XmlPullParserException, RemoteException {
+ return mDelegate.getNamespaceCount(depth);
+ }
+
+ @Override
+ public String getNamespacePrefix(int pos) throws XmlPullParserException, RemoteException {
+ return mDelegate.getNamespacePrefix(pos);
+ }
+
+ @Override
+ public String getNamespaceUri(int pos) throws XmlPullParserException, RemoteException {
+ return mDelegate.getNamespaceUri(pos);
+ }
+
+ @Override
+ public String getNamespace(String prefix) throws RemoteException {
+ return mDelegate.getNamespace(prefix);
+ }
+
+ @Override
+ public int getDepth() throws RemoteException {
+ return mDelegate.getDepth();
+ }
+
+ @Override
+ public String getPositionDescription() throws RemoteException {
+ return mDelegate.getPositionDescription();
+ }
+
+ @Override
+ public int getLineNumber() throws RemoteException {
+ return mDelegate.getLineNumber();
+ }
+
+ @Override
+ public int getColumnNumber() throws RemoteException {
+ return mDelegate.getColumnNumber();
+ }
+
+ @Override
+ public boolean isWhitespace() throws XmlPullParserException, RemoteException {
+ return mDelegate.isWhitespace();
+ }
+
+ @Override
+ public String getText() throws RemoteException {
+ return mDelegate.getText();
+ }
+
+ @Override
+ public char[] getTextCharacters(int[] holderForStartAndLength) throws RemoteException {
+ return mDelegate.getTextCharacters(holderForStartAndLength);
+ }
+
+ @Override
+ public String getNamespace() throws RemoteException {
+ return mDelegate.getNamespace();
+ }
+
+ @Override
+ public String getName() throws RemoteException {
+ return mDelegate.getName();
+ }
+
+ @Override
+ public String getPrefix() throws RemoteException {
+ return mDelegate.getPrefix();
+ }
+
+ @Override
+ public boolean isEmptyElementTag() throws XmlPullParserException, RemoteException {
+ return mDelegate.isEmptyElementTag();
+ }
+
+ @Override
+ public int getAttributeCount() throws RemoteException {
+ return mDelegate.getAttributeCount();
+ }
+
+ @Override
+ public String getAttributeNamespace(int index) throws RemoteException {
+ return mDelegate.getAttributeNamespace(index);
+ }
+
+ @Override
+ public String getAttributeName(int index) throws RemoteException {
+ return mDelegate.getAttributeName(index);
+ }
+
+ @Override
+ public String getAttributePrefix(int index) throws RemoteException {
+ return mDelegate.getAttributePrefix(index);
+ }
+
+ @Override
+ public String getAttributeType(int index) throws RemoteException {
+ return mDelegate.getAttributeType(index);
+ }
+
+ @Override
+ public boolean isAttributeDefault(int index) throws RemoteException {
+ return mDelegate.isAttributeDefault(index);
+ }
+
+ @Override
+ public String getAttributeValue(int index) throws RemoteException {
+ return mDelegate.getAttributeValue(index);
+ }
+
+ @Override
+ public String getAttributeValue(String namespace, String name) throws RemoteException {
+ return mDelegate.getAttributeValue(namespace, name);
+ }
+
+ @Override
+ public int getEventType() throws XmlPullParserException, RemoteException {
+ return mDelegate.getEventType();
+ }
+
+ @Override
+ public int next() throws XmlPullParserException, IOException, RemoteException {
+ return mDelegate.next();
+ }
+
+ @Override
+ public int nextToken() throws XmlPullParserException, IOException, RemoteException {
+ return mDelegate.nextToken();
+ }
+
+ @Override
+ public void require(int type, String namespace, String name)
+ throws XmlPullParserException, IOException, RemoteException {
+ mDelegate.require(type, namespace, name);
+ }
+
+ @Override
+ public String nextText() throws XmlPullParserException, IOException, RemoteException {
+ return mDelegate.nextText();
+ }
+
+ @Override
+ public int nextTag() throws XmlPullParserException, IOException, RemoteException {
+ return mDelegate.nextTag();
+ }
+}
diff --git a/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/package-info.java b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/package-info.java
new file mode 100644
index 0000000..6edf00d
--- /dev/null
+++ b/remote/client/src/com/android/layoutlib/bridge/remote/client/adapters/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Package containing all the client side adapters. These adapters have the mission of receiving
+ * remote calls and translating them into the local API.
+ */
+package com.android.layoutlib.bridge.remote.client.adapters;
\ No newline at end of file
diff --git a/remote/common/remote common.iml b/remote/common/remote common.iml
new file mode 100644
index 0000000..e56c17c
--- /dev/null
+++ b/remote/common/remote common.iml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="file://$MODULE_DIR$/../../../../libcore/xml/src/main/java" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
+ <orderEntry type="module" module-name="common" />
+ </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
new file mode 100644
index 0000000..153a575
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteActionBarCallback.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License") throws RemoteException;
+ * 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.ActionBarCallback;
+import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.List;
+
+/**
+ * Remote version of the {@link ActionBarCallback} class
+ */
+public interface RemoteActionBarCallback extends Remote {
+
+ List<String> getMenuIdNames() 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/RemoteAssetRepository.java b/remote/common/src/com/android/layout/remote/api/RemoteAssetRepository.java
new file mode 100644
index 0000000..5b4d412
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteAssetRepository.java
@@ -0,0 +1,36 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.AssetRepository;
+import com.android.layout.remote.util.RemoteInputStream;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link AssetRepository} class
+ */
+public interface RemoteAssetRepository extends Remote {
+ @NotNull
+ RemoteInputStream openAsset(String path, int mode) throws IOException;
+
+ @NotNull
+ RemoteInputStream openNonAsset(int cookie, String path, int mode) throws IOException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteBridge.java b/remote/common/src/com/android/layout/remote/api/RemoteBridge.java
new file mode 100644
index 0000000..8188198
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteBridge.java
@@ -0,0 +1,122 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.Bridge;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.io.File;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.Map;
+
+/**
+ * Interface that defines the operations available in the remote bridge. This is a remote version of
+ * the local {@link Bridge} API. Most of the methods are mapped 1:1 with the {@link Bridge} API
+ * unless there is a need for a method to be adapted for remote use.
+ */
+public interface RemoteBridge extends Remote {
+ /**
+ * Returns the API level of the layout library.
+ * <p>
+ * While no methods will ever be removed, some may become deprecated, and some new ones will
+ * appear. <p>All Layout libraries based on {@link Bridge} return at minimum an API level of 5.
+ */
+ int getApiLevel() throws RemoteException;
+
+ /**
+ * Returns the revision of the library inside a given (layoutlib) API level. The true revision
+ * number of the library is {@link #getApiLevel()}.{@link #getRevision()}
+ */
+ @SuppressWarnings("JavaDoc")
+ // javadoc pointing to itself.
+ int getRevision() throws RemoteException;
+
+ /**
+ * Returns true if the layout library supports the given feature.
+ *
+ * @see com.android.ide.common.rendering.api.Features
+ */
+ boolean supports(int feature) throws RemoteException;
+
+ /**
+ * Initializes the Bridge object.
+ *
+ * @param platformProperties The build properties for the platform.
+ * @param fontLocation the location of the fonts.
+ * @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.
+ *
+ * @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;
+
+ /**
+ * Prepares the layoutlib to be unloaded.
+ */
+ boolean dispose() throws RemoteException;
+
+ /**
+ * Starts a layout session by inflating and rendering it. The method returns a {@link
+ * RenderSession} on which further actions can be taken.
+ *
+ * @return a new {@link RenderSession} object that contains the result of the scene creation and
+ * first rendering.
+ */
+ @NotNull
+ RemoteRenderSession createSession(@NotNull RemoteSessionParams params) throws RemoteException;
+
+ /**
+ * Renders a Drawable. If the rendering is successful, the result image is accessible through
+ * {@link Result#getData()}. It is of type {@link BufferedImage}
+ *
+ * @param params the rendering parameters.
+ *
+ * @return the result of the action.
+ */
+ @NotNull
+ Result renderDrawable(@NotNull RemoteDrawableParams params) throws RemoteException;
+
+ /**
+ * Clears the resource cache for a specific project.
+ * <p>This cache contains bitmaps and nine patches that are loaded from the disk and reused
+ * until this method is called.
+ * <p>The cache is not configuration dependent and should only be cleared when a
+ * resource changes (at this time only bitmaps and 9 patches go into the cache).
+ * <p>
+ * The project key provided must be similar to the one passed in {@link RenderParams}.
+ *
+ * @param projectKey the key for the project.
+ */
+ void clearCaches(String projectKey) throws RemoteException;
+
+ /**
+ * Returns true if the character orientation of the locale is right to left.
+ *
+ * @param locale The locale formatted as language-region
+ *
+ * @return true if the locale is right to left.
+ */
+ boolean isRtl(String locale) throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteDrawableParams.java b/remote/common/src/com/android/layout/remote/api/RemoteDrawableParams.java
new file mode 100644
index 0000000..9ef1e9e
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteDrawableParams.java
@@ -0,0 +1,31 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link DrawableParams} class
+ */
+public interface RemoteDrawableParams extends RemoteRenderParams {
+ @NotNull
+ ResourceValue getDrawable() throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteHardwareConfig.java b/remote/common/src/com/android/layout/remote/api/RemoteHardwareConfig.java
new file mode 100644
index 0000000..191cca7
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteHardwareConfig.java
@@ -0,0 +1,71 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.HardwareConfig;
+import com.android.resources.Density;
+import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenRound;
+import com.android.resources.ScreenSize;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.Serializable;
+
+/**
+ * Remote version of the {@link HardwareConfig} class
+ */
+// TODO: Just make HardwareConfig serializable
+public class RemoteHardwareConfig implements Serializable {
+ private int mScreenWidth;
+ private int mScreenHeight;
+ private Density mDensity;
+ private float mXdpi;
+ private float mYdpi;
+ private ScreenSize mScreenSize;
+ private ScreenOrientation mOrientation;
+ private ScreenRound mScreenRoundness;
+ private boolean mHasSoftwareButtons;
+
+ public RemoteHardwareConfig() {
+ }
+
+ public RemoteHardwareConfig(@NotNull HardwareConfig config) {
+ this(config.getScreenWidth(), config.getScreenHeight(), config.getDensity(),
+ config.getXdpi(), config.getYdpi(), config.getScreenSize(), config.getOrientation(),
+ config.getScreenRoundness(), config.hasSoftwareButtons());
+ }
+
+ private RemoteHardwareConfig(int screenWidth, int screenHeight, Density density, float xdpi,
+ float ydpi, ScreenSize screenSize, ScreenOrientation orientation,
+ ScreenRound screenRoundness, boolean hasSoftwareButtons) {
+ mScreenWidth = screenWidth;
+ mScreenHeight = screenHeight;
+ mDensity = density;
+ mXdpi = xdpi;
+ mYdpi = ydpi;
+ mScreenSize = screenSize;
+ mOrientation = orientation;
+ mScreenRoundness = screenRoundness;
+ mHasSoftwareButtons = hasSoftwareButtons;
+ }
+
+ @NotNull
+ public HardwareConfig getHardwareConfig() {
+ return new HardwareConfig(mScreenWidth, mScreenHeight, mDensity, mXdpi, mYdpi, mScreenSize,
+ mOrientation, mScreenRoundness, mHasSoftwareButtons);
+ }
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java b/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java
new file mode 100644
index 0000000..585535b
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteILayoutPullParser.java
@@ -0,0 +1,28 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.ILayoutPullParser;
+
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link ILayoutPullParser} interface
+ */
+public interface RemoteILayoutPullParser extends RemoteXmlPullParser {
+ Object getViewCookie() throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteLayoutLog.java b/remote/common/src/com/android/layout/remote/api/RemoteLayoutLog.java
new file mode 100644
index 0000000..c3b5c61
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteLayoutLog.java
@@ -0,0 +1,72 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+
+import java.io.Serializable;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link LayoutLog} class
+ */
+public interface RemoteLayoutLog extends Remote {
+ /**
+ * Logs a warning.
+ *
+ * @param tag a tag describing the type of the warning
+ * @param message the message of the warning
+ * @param data an optional data bundle that the client can use to improve the warning display.
+ */
+ void warning(String tag, String message, Serializable data) throws RemoteException;
+
+ /**
+ * Logs a fidelity warning.
+ * <p>
+ * This type of warning indicates that the render will not be the same as the rendering on a
+ * device due to limitation of the Java rendering API.
+ *
+ * @param tag a tag describing the type of the warning
+ * @param message the message of the warning
+ * @param throwable an optional Throwable that triggered the warning
+ * @param viewCookie optional cookie of the view associated to this error
+ * @param data an optional data bundle that the client can use to improve the warning display.
+ */
+ void fidelityWarning(String tag, String message, Throwable throwable, Object viewCookie,
+ Object data) throws RemoteException;
+
+ /**
+ * Logs an error.
+ *
+ * @param tag a tag describing the type of the error
+ * @param message the message of the error
+ * @param data an optional data bundle that the client can use to improve the error display.
+ */
+ void error(String tag, String message, Serializable data) throws RemoteException;
+
+ /**
+ * Logs an error, and the {@link Throwable} that triggered it.
+ *
+ * @param tag a tag describing the type of the error
+ * @param message the message of the error
+ * @param throwable the Throwable that triggered the error
+ * @param data an optional data bundle that the client can use to improve the error display.
+ */
+ void error(String tag, String message, Throwable throwable, Serializable data)
+ 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
new file mode 100644
index 0000000..0f315ca
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteLayoutlibCallback.java
@@ -0,0 +1,83 @@
+/*
+ * 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.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.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 java.nio.file.Path;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link LayoutlibCallback} class
+ */
+public interface RemoteLayoutlibCallback extends Remote {
+ boolean supports(int ideFeature) throws RemoteException;
+
+ Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
+ throws Exception, RemoteException;
+
+ String getNamespace() throws RemoteException;
+
+ RemoteResolveResult resolveResourceId(int id) throws RemoteException;
+
+ String resolveResourceId(int[] id) throws RemoteException;
+
+ Integer getResourceId(ResourceType type, String name) throws RemoteException;
+
+ RemoteILayoutPullParser getParser(ResourceValue layoutResource) throws RemoteException;
+
+ Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
+ ResourceReference itemRef, int fullPosition, int positionPerType,
+ int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
+ ViewAttribute viewAttribute, Object defaultValue) throws RemoteException;
+
+ AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
+ Object viewObject) throws RemoteException;
+
+ RemoteActionBarCallback getActionBarCallback() throws RemoteException;
+
+ <T> T getFlag(Key<T> key) throws RemoteException;
+
+ RemoteParserFactory getParserFactory() throws RemoteException;
+
+ Path findClassPath(String name) throws RemoteException;
+
+ RemoteXmlPullParser getXmlFileParser(String fileName) throws RemoteException;
+
+ class RemoteResolveResult implements Serializable {
+ private ResourceType type;
+ private String value;
+
+ public RemoteResolveResult(ResourceType type, String value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ public Pair<ResourceType, String> asPair() {
+ return Pair.of(type, value);
+ }
+ }
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java b/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java
new file mode 100644
index 0000000..31a35b2
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteParserFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.ParserFactory;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link ParserFactory} class
+ */
+public interface RemoteParserFactory extends Remote {
+ RemoteXmlPullParser createParser(@Nullable String debugName) throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java b/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java
new file mode 100644
index 0000000..da2bd8e
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteRenderParams.java
@@ -0,0 +1,65 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.IImageFactory;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+public interface RemoteRenderParams extends Remote {
+ @Nullable
+ String getProjectKey() throws RemoteException;
+
+ RemoteHardwareConfig getRemoteHardwareConfig() throws RemoteException;
+
+ int getMinSdkVersion() throws RemoteException;
+
+ int getTargetSdkVersion() throws RemoteException;
+
+ RemoteRenderResources getRemoteResources() throws RemoteException;
+
+ RemoteAssetRepository getAssets() throws RemoteException;
+
+ RemoteLayoutlibCallback getRemoteLayoutlibCallback() throws RemoteException;
+
+ RemoteLayoutLog getLog() throws RemoteException;
+
+ boolean isBgColorOverridden() throws RemoteException;
+
+ int getOverrideBgColor() throws RemoteException;
+
+ long getTimeout() throws RemoteException;
+
+ IImageFactory getImageFactory() throws RemoteException;
+
+ String getAppIcon() throws RemoteException;
+
+ String getAppLabel() throws RemoteException;
+
+ String getLocale() throws RemoteException;
+
+ String getActivityName() throws RemoteException;
+
+ boolean isForceNoDecor() throws RemoteException;
+
+ boolean isRtlSupported() throws RemoteException;
+
+ <T> T getFlag(Key<T> key) 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
new file mode 100644
index 0000000..f54d44d
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteRenderResources.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License") throws RemoteException;
+ * 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.RenderResources;
+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 java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.List;
+
+/**
+ * Remote version of the {@link RenderResources} class
+ */
+public interface RemoteRenderResources extends Remote {
+ StyleResourceValue getDefaultTheme() throws RemoteException;
+
+ void applyStyle(StyleResourceValue theme, boolean useAsPrimary) throws RemoteException;
+
+ void clearStyles() throws RemoteException;
+
+ List<StyleResourceValue> getAllThemes() throws RemoteException;
+
+
+ StyleResourceValue getTheme(String name, boolean frameworkTheme) throws RemoteException;
+
+
+ boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme)
+ throws RemoteException;
+
+ ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName)
+ throws RemoteException;
+
+ ResourceValue getProjectResource(ResourceType resourceType, String resourceName)
+ throws RemoteException;
+
+
+ ResourceValue findItemInTheme(ResourceReference attr) 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;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteRenderSession.java b/remote/common/src/com/android/layout/remote/api/RemoteRenderSession.java
new file mode 100644
index 0000000..648186f
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteRenderSession.java
@@ -0,0 +1,48 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.layout.remote.util.SerializableImage;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+
+/**
+ * Remote version of the {@link RenderSession} class
+ */
+public interface RemoteRenderSession extends Remote {
+ @NotNull
+ Result getResult() throws RemoteException;
+
+ @NotNull
+ SerializableImage getSerializableImage() throws RemoteException;
+
+ void setSystemTimeNanos(long nanos) throws RemoteException;
+
+ void setSystemBootTimeNanos(long nanos) throws RemoteException;
+
+ void setElapsedFrameTimeNanos(long nanos) throws RemoteException;
+
+ void dispose() throws RemoteException;
+
+ @NotNull
+ Result render(long timeout, boolean forceMeasure) throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteSessionParams.java b/remote/common/src/com/android/layout/remote/api/RemoteSessionParams.java
new file mode 100644
index 0000000..52a2a09
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteSessionParams.java
@@ -0,0 +1,45 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.ide.common.rendering.api.AdapterBinding;
+import com.android.ide.common.rendering.api.IImageFactory;
+import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.ide.common.rendering.api.SessionParams.Key;
+import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.Map;
+
+/**
+ * Remote version of the {@link SessionParams} class
+ */
+public interface RemoteSessionParams extends RemoteRenderParams {
+ RenderingMode getRenderingMode() throws RemoteException;
+
+ boolean isLayoutOnly() throws RemoteException;
+
+ Map<ResourceReference, AdapterBinding> getAdapterBindings() throws RemoteException;
+
+ boolean getExtendedViewInfoMode() throws RemoteException;
+
+ int getSimulatedPlatformVersion() throws RemoteException;
+
+ RemoteILayoutPullParser getLayoutDescription() throws RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/RemoteXmlPullParser.java b/remote/common/src/com/android/layout/remote/api/RemoteXmlPullParser.java
new file mode 100644
index 0000000..e3e05d3
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/RemoteXmlPullParser.java
@@ -0,0 +1,109 @@
+/*
+ * 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.layout.remote.api;
+
+import com.android.layout.remote.util.RemoteInputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Remote version of the {@link XmlPullParser} interface
+ */
+public interface RemoteXmlPullParser extends Remote {
+ void setFeature(String name, boolean state) throws XmlPullParserException, RemoteException;
+
+ boolean getFeature(String name) throws RemoteException;
+
+ void setProperty(String name, Object value) throws XmlPullParserException, RemoteException;
+
+ Object getProperty(String name) throws RemoteException;
+
+ void setInput(Reader in) throws XmlPullParserException, RemoteException;
+
+ void setInput(RemoteInputStream inputStream, String inputEncoding)
+ throws XmlPullParserException, RemoteException;
+
+ String getInputEncoding() throws RemoteException;
+
+ void defineEntityReplacementText(String entityName, String replacementText)
+ throws XmlPullParserException, RemoteException;
+
+ int getNamespaceCount(int depth) throws XmlPullParserException, RemoteException;
+
+ String getNamespacePrefix(int pos) throws XmlPullParserException, RemoteException;
+
+ String getNamespaceUri(int pos) throws XmlPullParserException, RemoteException;
+
+ String getNamespace(String prefix) throws RemoteException;
+
+ int getDepth() throws RemoteException;
+
+ String getPositionDescription() throws RemoteException;
+
+ int getLineNumber() throws RemoteException;
+
+ int getColumnNumber() throws RemoteException;
+
+ boolean isWhitespace() throws XmlPullParserException, RemoteException;
+
+ String getText() throws RemoteException;
+
+ char[] getTextCharacters(int[] holderForStartAndLength) throws RemoteException;
+
+ String getNamespace() throws RemoteException;
+
+ String getName() throws RemoteException;
+
+ String getPrefix() throws RemoteException;
+
+ boolean isEmptyElementTag() throws XmlPullParserException, RemoteException;
+
+ int getAttributeCount() throws RemoteException;
+
+ String getAttributeNamespace(int index) throws RemoteException;
+
+ String getAttributeName(int index) throws RemoteException;
+
+ String getAttributePrefix(int index) throws RemoteException;
+
+ String getAttributeType(int index) throws RemoteException;
+
+ boolean isAttributeDefault(int index) throws RemoteException;
+
+ String getAttributeValue(int index) throws RemoteException;
+
+ String getAttributeValue(String namespace, String name) throws RemoteException;
+
+ int getEventType() throws XmlPullParserException, RemoteException;
+
+ int next() throws XmlPullParserException, IOException, RemoteException;
+
+ int nextToken() throws XmlPullParserException, IOException, RemoteException;
+
+ void require(int type, String namespace, String name)
+ throws XmlPullParserException, IOException, RemoteException;
+
+ String nextText() throws XmlPullParserException, IOException, RemoteException;
+
+ int nextTag() throws XmlPullParserException, IOException, RemoteException;
+}
diff --git a/remote/common/src/com/android/layout/remote/api/package-info.java b/remote/common/src/com/android/layout/remote/api/package-info.java
new file mode 100644
index 0000000..c7591c6
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/api/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Package containing all the interfaces that define the remote version of the Layoutlib API. This
+ * interface matches the local layout API closely. In some cases where special capabilities to
+ * support the remote calls are needed, the API will be different.
+ */
+package com.android.layout.remote.api;
\ No newline at end of file
diff --git a/remote/common/src/com/android/layout/remote/util/RemoteInputStream.java b/remote/common/src/com/android/layout/remote/util/RemoteInputStream.java
new file mode 100644
index 0000000..f3b1611
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/RemoteInputStream.java
@@ -0,0 +1,42 @@
+/*
+ * 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.layout.remote.util;
+
+import java.io.IOException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+public interface RemoteInputStream extends Remote {
+ int read() throws IOException;
+
+ byte[] read(int off, int len) throws IOException;
+
+ long skip(long n) throws IOException;
+
+ int available() throws IOException;
+
+ void close() throws IOException;
+
+ void mark(int readlimit) throws RemoteException;
+
+ void reset() throws IOException;
+
+ boolean markSupported() throws RemoteException;
+
+ class EndOfStreamException extends IOException {
+ }
+}
diff --git a/remote/common/src/com/android/layout/remote/util/RemoteInputStreamAdapter.java b/remote/common/src/com/android/layout/remote/util/RemoteInputStreamAdapter.java
new file mode 100644
index 0000000..e983202
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/RemoteInputStreamAdapter.java
@@ -0,0 +1,82 @@
+/*
+ * 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.layout.remote.util;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteInputStreamAdapter implements RemoteInputStream {
+
+ private InputStream mDelegate;
+
+ private RemoteInputStreamAdapter(@NotNull InputStream delegate) {
+ mDelegate = delegate;
+ }
+
+ public static RemoteInputStream create(@NotNull InputStream is) throws RemoteException {
+ return (RemoteInputStream) UnicastRemoteObject.exportObject(
+ new RemoteInputStreamAdapter(is), 0);
+ }
+
+ @Override
+ public int read() throws IOException {
+ return mDelegate.read();
+ }
+
+ @Override
+ public byte[] read(int off, int len) throws IOException, RemoteException {
+ byte[] buffer = new byte[len];
+ if (mDelegate.read(buffer, off, len) == -1) {
+ throw new EndOfStreamException();
+ }
+ return buffer;
+ }
+
+ @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();
+ }
+
+ @Override
+ public boolean markSupported() {
+ return mDelegate.markSupported();
+ }
+}
diff --git a/remote/common/src/com/android/layout/remote/util/SerializableImage.java b/remote/common/src/com/android/layout/remote/util/SerializableImage.java
new file mode 100644
index 0000000..999aae4
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/SerializableImage.java
@@ -0,0 +1,30 @@
+/*
+ * 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.layout.remote.util;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.awt.image.BufferedImage;
+import java.io.Serializable;
+
+/**
+ * Serializable BufferedImage that can be sent across VMs
+ */
+public interface SerializableImage extends Serializable {
+ @NotNull
+ BufferedImage getBufferedImage();
+}
diff --git a/remote/common/src/com/android/layout/remote/util/SerializableImageImpl.java b/remote/common/src/com/android/layout/remote/util/SerializableImageImpl.java
new file mode 100644
index 0000000..59c8c3f
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/SerializableImageImpl.java
@@ -0,0 +1,55 @@
+/*
+ * 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.layout.remote.util;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Naive implementation of {@link SerializableImage} using {@link ImageIO} and PNG format as
+ * transport.
+ */
+public class SerializableImageImpl implements SerializableImage {
+ @NotNull
+ transient private BufferedImage mBufferedImage;
+
+ public SerializableImageImpl(@NotNull BufferedImage delegate) {
+ mBufferedImage = delegate;
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.defaultWriteObject();
+ ImageIO.write(mBufferedImage, "png", out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ mBufferedImage = ImageIO.read(in);
+ }
+
+ @Override
+ @NotNull
+ public BufferedImage getBufferedImage() {
+ return mBufferedImage;
+ }
+}
diff --git a/remote/common/src/com/android/layout/remote/util/StreamUtil.java b/remote/common/src/com/android/layout/remote/util/StreamUtil.java
new file mode 100644
index 0000000..e05b5de
--- /dev/null
+++ b/remote/common/src/com/android/layout/remote/util/StreamUtil.java
@@ -0,0 +1,96 @@
+/*
+ * 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.layout.remote.util;
+
+import com.android.layout.remote.util.RemoteInputStream.EndOfStreamException;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.rmi.RemoteException;
+
+public class StreamUtil {
+ /**
+ * Returns a local {@link InputStream} from a {@link RemoteInputStream}
+ */
+ @NotNull
+ public static InputStream getInputStream(@NotNull RemoteInputStream is) {
+ return new InputStream() {
+ @Override
+ public int read() throws IOException {
+ return is.read();
+ }
+
+ @SuppressWarnings("NullableProblems")
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @SuppressWarnings("NullableProblems")
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ try {
+ byte[] read = is.read(off, len);
+ int actualLength = Math.min(len, read.length);
+ System.arraycopy(read, 0, b, off, actualLength);
+ return actualLength;
+ } catch (EndOfStreamException e) {
+ return -1;
+ }
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ return is.skip(n);
+ }
+
+ @Override
+ public int available() throws IOException {
+ return is.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ is.close();
+ }
+
+ @Override
+ public synchronized void mark(int readlimit) {
+ try {
+ is.mark(readlimit);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ is.reset();
+ }
+
+ @Override
+ public boolean markSupported() {
+ try {
+ return is.markSupported();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ }
+}
diff --git a/remote/server/remote server.iml b/remote/server/remote server.iml
new file mode 100644
index 0000000..9fc0852
--- /dev/null
+++ b/remote/server/remote server.iml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="bridge" />
+ <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="file://$MODULE_DIR$/../../../../libcore/xml/src/main/java" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module" module-name="remote common" />
+ <orderEntry type="module" module-name="common" />
+ </component>
+</module>
\ No newline at end of file
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
new file mode 100644
index 0000000..2593afc
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/RemoteBridgeImpl.java
@@ -0,0 +1,163 @@
+/*
+ * 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;
+
+import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.RenderParams;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layout.remote.api.RemoteBridge;
+import com.android.layout.remote.api.RemoteDrawableParams;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.layout.remote.api.RemoteRenderParams;
+import com.android.layout.remote.api.RemoteRenderSession;
+import com.android.layout.remote.api.RemoteSessionParams;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteAssetRepositoryAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteILayoutPullParserAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteLayoutLogAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteLayoutlibCallbackAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteRenderResourcesAdapter;
+import com.android.layoutlib.bridge.remote.server.adapters.RemoteRenderSessionAdapter;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.File;
+import java.rmi.RemoteException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Remote {@link Bridge} implementation. This class is the remote entry point for the server. Its
+ * responsibility is to receive the remote calls and apply the required transformations to convert
+ * it into a regular call to the {@link Bridge} class.
+ */
+public class RemoteBridgeImpl implements RemoteBridge {
+ private Bridge mBridge = new Bridge();
+
+ /**
+ * The project keys are used as key for some caches. They are usually expected to remain in
+ * memory so WeakReferences are used in the caches.
+ * Because in the remote bridge we do not have a real pointer to the object, we keep the strings
+ * in memory until they are cleared.
+ */
+ private Map<String, String> mCachedProjectKeys = new HashMap<>();
+
+ @Override
+ public int getApiLevel() {
+ return mBridge.getApiLevel();
+ }
+
+ @Override
+ public int getRevision() {
+ return mBridge.getRevision();
+ }
+
+ @Override
+ public boolean supports(int feature) {
+ return mBridge.supports(feature);
+ }
+
+ @Override
+ public boolean init(Map<String, String> platformProperties, File fontLocation,
+ Map<String, Map<String, Integer>> enumValueMap, RemoteLayoutLog log) {
+ return mBridge.init(platformProperties, fontLocation, enumValueMap,
+ log != null ? new RemoteLayoutLogAdapter(log) : null);
+ }
+
+ @Override
+ public boolean dispose() {
+ return mBridge.dispose();
+ }
+
+ private static void setupRenderParams(@NotNull RenderParams params,
+ @NotNull RemoteRenderParams remoteParams) throws RemoteException {
+ params.setAssetRepository(new RemoteAssetRepositoryAdapter(remoteParams.getAssets()));
+ params.setActivityName(remoteParams.getActivityName());
+ params.setAppIcon(remoteParams.getAppIcon());
+ params.setAppLabel(remoteParams.getAppLabel());
+ params.setTimeout(remoteParams.getTimeout());
+ params.setLocale(remoteParams.getLocale());
+ if (remoteParams.isForceNoDecor()) {
+ params.setForceNoDecor();
+ }
+ params.setRtlSupport(remoteParams.isRtlSupported());
+ if (remoteParams.isBgColorOverridden()) {
+ params.setOverrideBgColor(remoteParams.getOverrideBgColor());
+ }
+ params.setImageFactory(remoteParams.getImageFactory());
+ // TODO: Also unpack remote flags and pass them to RenderParams
+ }
+
+ @NotNull
+ @Override
+ public RemoteRenderSession createSession(@NotNull RemoteSessionParams remoteParams) {
+ try {
+ String projectKey = mCachedProjectKeys.putIfAbsent(remoteParams.getProjectKey(),
+ remoteParams.getProjectKey());
+
+ // Unpack the remote params and convert it into the local SessionParams
+ SessionParams params = new SessionParams(
+ new RemoteILayoutPullParserAdapter(remoteParams.getLayoutDescription()),
+ remoteParams.getRenderingMode(), projectKey,
+ remoteParams.getRemoteHardwareConfig().getHardwareConfig(),
+ new RemoteRenderResourcesAdapter(remoteParams.getRemoteResources()),
+ new RemoteLayoutlibCallbackAdapter(remoteParams.getRemoteLayoutlibCallback()),
+ remoteParams.getMinSdkVersion(), remoteParams.getTargetSdkVersion(),
+ new RemoteLayoutLogAdapter(remoteParams.getLog()));
+ setupRenderParams(params, remoteParams);
+ return RemoteRenderSessionAdapter.create(mBridge.createSession(params));
+
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @NotNull
+ @Override
+ public Result renderDrawable(@NotNull RemoteDrawableParams remoteParams) {
+ try {
+ String projectKey = mCachedProjectKeys.putIfAbsent(remoteParams.getProjectKey(),
+ remoteParams.getProjectKey());
+
+ DrawableParams params = new DrawableParams(
+ remoteParams.getDrawable(),
+ projectKey,
+ remoteParams.getRemoteHardwareConfig().getHardwareConfig(),
+ new RemoteRenderResourcesAdapter(remoteParams.getRemoteResources()),
+ new RemoteLayoutlibCallbackAdapter(remoteParams.getRemoteLayoutlibCallback()),
+ remoteParams.getMinSdkVersion(), remoteParams.getTargetSdkVersion(),
+ new RemoteLayoutLogAdapter(remoteParams.getLog()));
+ setupRenderParams(params, remoteParams);
+ return mBridge.renderDrawable(params);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void clearCaches(String projectKey) {
+ mCachedProjectKeys.remove(projectKey);
+ mBridge.clearCaches(projectKey);
+ }
+
+ @Override
+ public boolean isRtl(String locale) {
+ return mBridge.isRtl(locale);
+ }
+}
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
new file mode 100644
index 0000000..09c27fd
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/ServerMain.java
@@ -0,0 +1,192 @@
+/*
+ * 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;
+
+import com.android.layout.remote.api.RemoteBridge;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * Main server class. The main method will start an RMI server for the {@link RemoteBridgeImpl}
+ * class.
+ */
+public class ServerMain {
+ private static final String RUNNING_SERVER_STR = "Server is running on port ";
+ public static int REGISTRY_BASE_PORT = 9000;
+
+ private final int mPort;
+ private final Registry mRegistry;
+
+ private ServerMain(int port, @NotNull Registry registry) {
+ mPort = port;
+ mRegistry = registry;
+ }
+
+ public int getPort() {
+ return mPort;
+ }
+
+ public void stop() {
+ try {
+ UnicastRemoteObject.unexportObject(mRegistry, true);
+ } catch (NoSuchObjectException ignored) {
+ }
+ }
+
+ /**
+ * This will start a new JVM and connect to the new JVM RMI registry.
+ * <p/>
+ * The server will start looking for ports available for the {@link Registry} until a free one
+ * is found. The port number will be returned.
+ * If no ports are available, a {@link RemoteException} will be thrown.
+ * @param basePort port number to start looking for available ports
+ * @param limit number of ports to check. The last port to be checked will be (basePort + limit)
+ */
+ public static ServerMain forkAndStartServer(int basePort, int limit)
+ throws IOException, InterruptedException {
+ // We start a new VM by copying all the parameter that we received in the current VM.
+ // We only remove the agentlib parameter since that could cause a port collision and avoid
+ // the new VM from starting.
+ RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
+ List<String> arguments = runtimeMxBean.getInputArguments().stream()
+ .filter(arg -> !arg.contains("-agentlib")) // Filter agentlib to avoid conflicts
+ .collect(Collectors.toList());
+
+ Path javaPath = Paths.get(System.getProperty("java.home"), "bin", "java");
+ String thisClassName = ServerMain.class.getName()
+ .replace('.','/');
+
+ List<String> cmd = new ArrayList<>();
+ cmd.add(javaPath.toString());
+
+ // Inherited arguments
+ cmd.addAll(arguments);
+
+ // Classpath
+ cmd.add("-cp");
+ cmd.add(System.getProperty("java.class.path"));
+
+ // Class name and path
+ cmd.add(thisClassName);
+
+ // ServerMain parameters [basePort. limit]
+ cmd.add(Integer.toString(basePort));
+ cmd.add(Integer.toString(limit));
+
+ Process process = new ProcessBuilder()
+ .command(cmd)
+ .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();
+
+ Runnable killServer = () -> {
+ process.destroyForcibly();
+ outputThread.interrupt();
+ try {
+ outputThread.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);
+
+ if (line.startsWith(RUNNING_SERVER_STR)) {
+ int runningPort = Integer.parseInt(line.substring(RUNNING_SERVER_STR.length()));
+ System.out.println("Running on port " + runningPort);
+
+ // We already know where the server is running so we just need to get the registry
+ // and return our own instance of ServerMain
+ Registry registry = LocateRegistry.getRegistry(runningPort);
+ return new ServerMain(runningPort, registry) {
+ @Override
+ public void stop() {
+ killServer.run();
+ }
+ };
+ }
+ }
+
+ killServer.run();
+ throw new IOException("Unable to find start string");
+ }
+
+ /**
+ * The server will start looking for ports available for the {@link Registry} until a free one
+ * is found. The port number will be returned.
+ * If no ports are available, a {@link RemoteException} will be thrown.
+ * @param basePort port number to start looking for available ports
+ * @param limit number of ports to check. The last port to be checked will be (basePort + limit)
+ */
+ private static ServerMain startServer(int basePort, int limit) throws RemoteException {
+ RemoteBridgeImpl remoteBridge = new RemoteBridgeImpl();
+ RemoteBridge stub = (RemoteBridge) UnicastRemoteObject.exportObject(remoteBridge, 0);
+
+ RemoteException lastException = null;
+ for (int port = basePort; port <= basePort + limit; port++) {
+ try {
+ Registry registry = LocateRegistry.createRegistry(port);
+ registry.rebind(RemoteBridge.class.getName(), stub);
+ return new ServerMain(port, registry);
+ } catch (RemoteException e) {
+ lastException = e;
+ }
+ }
+
+ if (lastException == null) {
+ lastException = new RemoteException("Unable to start server");
+ }
+
+ throw lastException;
+ }
+
+ 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;
+
+ ServerMain server = startServer(basePort, limit);
+ System.out.println(RUNNING_SERVER_STR + server.getPort());
+ }
+}
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
new file mode 100644
index 0000000..b5c040e
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteActionBarCallbackAdapter.java
@@ -0,0 +1,86 @@
+/*
+ * 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.ActionBarCallback;
+import com.android.layout.remote.api.RemoteActionBarCallback;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.util.List;
+
+public class RemoteActionBarCallbackAdapter extends ActionBarCallback {
+ private final RemoteActionBarCallback mDelegate;
+
+ public RemoteActionBarCallbackAdapter(@NotNull RemoteActionBarCallback remote) {
+ mDelegate = remote;
+ }
+
+ @Override
+ public List<String> getMenuIdNames() {
+ try {
+ return mDelegate.getMenuIdNames();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean getSplitActionBarWhenNarrow() {
+ try {
+ return mDelegate.getSplitActionBarWhenNarrow();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int getNavigationMode() {
+ try {
+ return mDelegate.getNavigationMode();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getSubTitle() {
+ try {
+ return mDelegate.getSubTitle();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public HomeButtonStyle getHomeButtonStyle() {
+ try {
+ return mDelegate.getHomeButtonStyle();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isOverflowPopupNeeded() {
+ try {
+ return mDelegate.isOverflowPopupNeeded();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteAssetRepositoryAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteAssetRepositoryAdapter.java
new file mode 100644
index 0000000..490f829
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteAssetRepositoryAdapter.java
@@ -0,0 +1,50 @@
+/*
+ * 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.AssetRepository;
+import com.android.layout.remote.api.RemoteAssetRepository;
+import com.android.layout.remote.util.StreamUtil;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.rmi.RemoteException;
+
+public class RemoteAssetRepositoryAdapter extends AssetRepository {
+ private final RemoteAssetRepository mDelgate;
+
+ public RemoteAssetRepositoryAdapter(@NotNull RemoteAssetRepository remote) {
+ mDelgate = remote;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+ @Override
+ public InputStream openAsset(String path, int mode) throws IOException, RemoteException {
+ return StreamUtil.getInputStream(mDelgate.openAsset(path, mode));
+ }
+
+ @Override
+ public InputStream openNonAsset(int cookie, String path, int mode)
+ throws IOException, RemoteException {
+ return StreamUtil.getInputStream(mDelgate.openNonAsset(cookie, path, mode));
+ }
+}
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
new file mode 100644
index 0000000..b717feb
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteILayoutPullParserAdapter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.ILayoutPullParser;
+import com.android.layout.remote.api.RemoteILayoutPullParser;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+
+public class RemoteILayoutPullParserAdapter extends RemoteXmlPullParserAdapter
+ implements ILayoutPullParser {
+ public RemoteILayoutPullParserAdapter(@NotNull RemoteILayoutPullParser delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public Object getViewCookie() {
+ try {
+ return ((RemoteILayoutPullParser) mDelegate).getViewCookie();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public ILayoutPullParser getParser(String layoutName) {
+ throw new UnsupportedOperationException();
+ }
+
+
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutLogAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutLogAdapter.java
new file mode 100644
index 0000000..6878d46
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutLogAdapter.java
@@ -0,0 +1,68 @@
+/*
+ * 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.LayoutLog;
+import com.android.layout.remote.api.RemoteLayoutLog;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+
+public class RemoteLayoutLogAdapter extends LayoutLog {
+ private final RemoteLayoutLog mLog;
+
+ public RemoteLayoutLogAdapter(@NotNull RemoteLayoutLog log) {
+ mLog = log;
+ }
+
+ @Override
+ public void warning(String tag, String message, Object data) {
+ try {
+ mLog.warning(tag, message, null);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void fidelityWarning(String tag, String message, Throwable throwable, Object viewCookie,
+ Object data) {
+ try {
+ mLog.fidelityWarning(tag, message, throwable, viewCookie, data);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void error(String tag, String message, Object data) {
+ try {
+ mLog.error(tag, message, null);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void error(String tag, String message, Throwable throwable, Object data) {
+ try {
+ mLog.error(tag, message, throwable, null);
+ } 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
new file mode 100644
index 0000000..b685098
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -0,0 +1,305 @@
+/*
+ * 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.ActionBarCallback;
+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;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.rmi.RemoteException;
+import java.util.HashMap;
+import java.util.function.Function;
+
+public class RemoteLayoutlibCallbackAdapter extends LayoutlibCallback {
+ private final RemoteLayoutlibCallback mDelegate;
+ private final PathClassLoader mPathClassLoader;
+
+ public RemoteLayoutlibCallbackAdapter(@NotNull RemoteLayoutlibCallback remote) {
+ mDelegate = remote;
+
+ // Views requested to this callback need to be brought from the "client" side.
+ // We transform any loadView into two operations:
+ // - First we ask to where the class is located on disk via findClassPath
+ // - Second, we instantiate the class in the "server" side
+ HashMap<String, Path> nameToPathCache = new HashMap<>();
+ Function<String, Path> getPathFromName = cacheName -> nameToPathCache.computeIfAbsent(
+ cacheName,
+ name -> {
+ try {
+ return mDelegate.findClassPath(name);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ mPathClassLoader = new PathClassLoader(getPathFromName);
+ }
+
+ @NotNull
+ private Object createNewInstance(@NotNull Class<?> clazz,
+ @Nullable Class<?>[] constructorSignature, @Nullable Object[] constructorParameters,
+ boolean isView)
+ throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException,
+ IllegalAccessException, InstantiationException {
+ Constructor<?> constructor = null;
+
+ try {
+ constructor = clazz.getConstructor(constructorSignature);
+ } catch (NoSuchMethodException e) {
+ if (!isView) {
+ throw e;
+ }
+
+ // View class has 1-parameter, 2-parameter and 3-parameter constructors
+
+ final int paramsCount = constructorSignature != null ? constructorSignature.length : 0;
+ if (paramsCount == 0) {
+ throw e;
+ }
+ assert constructorParameters != null;
+
+ for (int i = 3; i >= 1; i--) {
+ if (i == paramsCount) {
+ continue;
+ }
+
+ final int k = paramsCount < i ? paramsCount : i;
+
+ final Class[] sig = new Class[i];
+ System.arraycopy(constructorSignature, 0, sig, 0, k);
+
+ final Object[] params = new Object[i];
+ System.arraycopy(constructorParameters, 0, params, 0, k);
+
+ for (int j = k + 1; j <= i; j++) {
+ if (j == 2) {
+ sig[j - 1] = findClass("android.util.AttributeSet");
+ params[j - 1] = null;
+ } else if (j == 3) {
+ // parameter 3: int defstyle
+ sig[j - 1] = int.class;
+ params[j - 1] = 0;
+ }
+ }
+
+ constructorSignature = sig;
+ constructorParameters = params;
+
+ try {
+ constructor = clazz.getConstructor(constructorSignature);
+ if (constructor != null) {
+ if (constructorSignature.length < 2) {
+ // TODO: Convert this to remote
+// LOG.info("wrong_constructor: Custom view " +
+// clazz.getSimpleName() +
+// " is not using the 2- or 3-argument " +
+// "View constructors; XML attributes will not work");
+// mDelegate.warning("wrongconstructor", //$NON-NLS-1$
+// String.format(
+// "Custom view %1$s is not using the 2- or 3-argument
+// View constructors; XML attributes will not work",
+// clazz.getSimpleName()), null, null);
+ }
+ break;
+ }
+ } catch (NoSuchMethodException ignored) {
+ }
+ }
+
+ if (constructor == null) {
+ throw e;
+ }
+ }
+
+ constructor.setAccessible(true);
+ return constructor.newInstance(constructorParameters);
+ }
+
+ @Override
+ public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
+ throws Exception {
+ Class<?> viewClass = MockView.class;
+ try {
+ viewClass = findClass(name);
+ } catch (ClassNotFoundException ignore) {
+ // MockView will be used instead
+ }
+ return createNewInstance(viewClass, constructorSignature, constructorArgs, true);
+ }
+
+ @Override
+ public String getNamespace() {
+ try {
+ return mDelegate.getNamespace();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @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) {
+ try {
+ return mDelegate.resolveResourceId(id);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Integer getResourceId(ResourceType type, String name) {
+ try {
+ return mDelegate.getResourceId(type, name);
+ } 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));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
+ ResourceReference itemRef, int fullPosition, int positionPerType,
+ int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
+ ViewAttribute viewAttribute, Object defaultValue) {
+ return null;
+ }
+
+ @Override
+ public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
+ Object viewObject) {
+ return null;
+ }
+
+ @Override
+ public ActionBarCallback getActionBarCallback() {
+ try {
+ return new RemoteActionBarCallbackAdapter(mDelegate.getActionBarCallback());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Object loadClass(String name, Class[] constructorSignature, Object[] constructorArgs)
+ throws ClassNotFoundException {
+ return super.loadClass(name, constructorSignature, constructorArgs);
+ }
+
+ @Override
+ public boolean supports(int ideFeature) {
+ try {
+ return mDelegate.supports(ideFeature);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public <T> T getFlag(Key<T> key) {
+ return super.getFlag(key);
+ }
+
+ @Override
+ public ParserFactory getParserFactory() {
+ try {
+ return new RemoteParserFactoryAdapter(mDelegate.getParserFactory());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ return mPathClassLoader.loadClass(name);
+ }
+
+ @Override
+ public XmlPullParser getXmlFileParser(String fileName) {
+ try {
+ return new RemoteXmlPullParserAdapter(mDelegate.getXmlFileParser(fileName));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Simple class loaders that loads classes from Paths
+ */
+ private static class PathClassLoader extends ClassLoader {
+ private final Function<String, Path> mGetPath;
+
+ private PathClassLoader(@NotNull Function<String, Path> getUrl) {
+ mGetPath = getUrl;
+ }
+
+ @Override
+ protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
+ Path path = mGetPath.apply(name);
+
+ if (path != null) {
+ try {
+ byte[] content = Files.readAllBytes(path);
+ return defineClass(name, content, 0, content.length);
+ } catch (IOException ignore) {
+ }
+ }
+
+ throw new ClassNotFoundException(name);
+ }
+ }
+}
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
new file mode 100644
index 0000000..f4ce421
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteParserFactoryAdapter.java
@@ -0,0 +1,44 @@
+/*
+ * 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
new file mode 100644
index 0000000..3f261df
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderResourcesAdapter.java
@@ -0,0 +1,192 @@
+/*
+ * 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.LayoutLog;
+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.StyleResourceValue;
+import com.android.layout.remote.api.RemoteRenderResources;
+import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.util.List;
+
+public class RemoteRenderResourcesAdapter extends RenderResources {
+ private final RemoteRenderResources mDelegate;
+
+ public RemoteRenderResourcesAdapter(@NotNull RemoteRenderResources remoteRenderResources) {
+ mDelegate = remoteRenderResources;
+ }
+
+ @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();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
+ try {
+ mDelegate.applyStyle(theme, useAsPrimary);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void clearStyles() {
+ try {
+ mDelegate.clearStyles();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @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);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public ResourceValue findItemInTheme(ResourceReference attr) {
+ try {
+ return mDelegate.findItemInTheme(attr);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public ResourceValue findItemInStyle(StyleResourceValue style, ResourceReference attr) {
+ try {
+ return mDelegate.findItemInStyle(style, attr);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public ResourceValue dereference(ResourceValue resourceValue) {
+ try {
+ return mDelegate.dereference(resourceValue);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Returns a resource by namespace, type and name. The returned resource is unresolved. */
+ @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);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public ResourceValue resolveResValue(ResourceValue value) {
+ try {
+ return mDelegate.resolveValue(value);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public StyleResourceValue getParent(StyleResourceValue style) {
+ try {
+ return mDelegate.getParent(style);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderSessionAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderSessionAdapter.java
new file mode 100644
index 0000000..9d292e7
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteRenderSessionAdapter.java
@@ -0,0 +1,78 @@
+/*
+ * 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.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.layout.remote.api.RemoteRenderSession;
+import com.android.layout.remote.util.SerializableImage;
+import com.android.layout.remote.util.SerializableImageImpl;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteRenderSessionAdapter implements RemoteRenderSession {
+ private final RenderSession mDelegate;
+
+ private RemoteRenderSessionAdapter(@NotNull RenderSession delegate) {
+ mDelegate = delegate;
+ }
+
+ public static RemoteRenderSession create(@NotNull RenderSession delegate)
+ throws RemoteException {
+ return (RemoteRenderSession) UnicastRemoteObject.exportObject(
+ new RemoteRenderSessionAdapter(delegate), 0);
+ }
+
+ @NotNull
+ @Override
+ public Result getResult() throws RemoteException {
+ return mDelegate.getResult();
+ }
+
+ @Override
+ public Result render(long timeout, boolean forceMeasure) {
+ return mDelegate.render(timeout, forceMeasure);
+ }
+
+ @NotNull
+ @Override
+ public SerializableImage getSerializableImage() throws RemoteException {
+ return new SerializableImageImpl(mDelegate.getImage());
+ }
+
+ @Override
+ public void setSystemTimeNanos(long nanos) {
+ mDelegate.setSystemTimeNanos(nanos);
+ }
+
+ @Override
+ public void setSystemBootTimeNanos(long nanos) {
+ mDelegate.setSystemBootTimeNanos(nanos);
+ }
+
+ @Override
+ public void setElapsedFrameTimeNanos(long nanos) {
+ mDelegate.setElapsedFrameTimeNanos(nanos);
+ }
+
+ @Override
+ public void dispose() {
+ mDelegate.dispose();
+ }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteXmlPullParserAdapter.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteXmlPullParserAdapter.java
new file mode 100644
index 0000000..6de03bb
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/RemoteXmlPullParserAdapter.java
@@ -0,0 +1,353 @@
+/*
+ * 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.layout.remote.api.RemoteXmlPullParser;
+import com.android.layout.remote.util.RemoteInputStreamAdapter;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.rmi.RemoteException;
+
+class RemoteXmlPullParserAdapter implements XmlPullParser {
+ protected RemoteXmlPullParser mDelegate;
+
+ RemoteXmlPullParserAdapter(@NotNull RemoteXmlPullParser remote) {
+ mDelegate = remote;
+ }
+
+ @Override
+ public void setFeature(String name, boolean state) throws XmlPullParserException {
+ try {
+ mDelegate.setFeature(name, state);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean getFeature(String name) {
+ try {
+ return mDelegate.getFeature(name);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void setProperty(String name, Object value) throws XmlPullParserException {
+ try {
+ mDelegate.setProperty(name, value);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ try {
+ return mDelegate.getProperty(name);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void setInput(Reader in) throws XmlPullParserException {
+ try {
+ mDelegate.setInput(in);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void setInput(InputStream inputStream, String inputEncoding)
+ throws XmlPullParserException {
+ try {
+ mDelegate.setInput(RemoteInputStreamAdapter.create(inputStream), inputEncoding);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getInputEncoding() {
+ try {
+ return mDelegate.getInputEncoding();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void defineEntityReplacementText(String entityName, String replacementText)
+ throws XmlPullParserException {
+ try {
+ mDelegate.defineEntityReplacementText(entityName, replacementText);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int getNamespaceCount(int depth) throws XmlPullParserException {
+ try {
+ return mDelegate.getNamespaceCount(depth);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getNamespacePrefix(int pos) throws XmlPullParserException {
+ try {
+ return mDelegate.getNamespacePrefix(pos);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getNamespaceUri(int pos) throws XmlPullParserException {
+ try {
+ return mDelegate.getNamespaceUri(pos);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getNamespace(String prefix) {
+ try {
+ return mDelegate.getNamespace(prefix);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int getDepth() {
+ try {
+ return mDelegate.getDepth();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getPositionDescription() {
+ try {
+ return mDelegate.getPositionDescription();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int getLineNumber() {
+ try {
+ return mDelegate.getLineNumber();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int getColumnNumber() {
+ try {
+ return mDelegate.getColumnNumber();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isWhitespace() throws XmlPullParserException {
+ try {
+ return mDelegate.isWhitespace();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getText() {
+ try {
+ return mDelegate.getText();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public char[] getTextCharacters(int[] holderForStartAndLength) {
+ try {
+ return mDelegate.getTextCharacters(holderForStartAndLength);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getNamespace() {
+ try {
+ return mDelegate.getNamespace();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getName() {
+ try {
+ return mDelegate.getName();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getPrefix() {
+ try {
+ return mDelegate.getPrefix();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isEmptyElementTag() throws XmlPullParserException {
+ try {
+ return mDelegate.isEmptyElementTag();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int getAttributeCount() {
+ try {
+ return mDelegate.getAttributeCount();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getAttributeNamespace(int index) {
+ try {
+ return mDelegate.getAttributeNamespace(index);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getAttributeName(int index) {
+ try {
+ return mDelegate.getAttributeName(index);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getAttributePrefix(int index) {
+ try {
+ return mDelegate.getAttributePrefix(index);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getAttributeType(int index) {
+ try {
+ return mDelegate.getAttributeType(index);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isAttributeDefault(int index) {
+ try {
+ return mDelegate.isAttributeDefault(index);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getAttributeValue(int index) {
+ try {
+ return mDelegate.getAttributeValue(index);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String getAttributeValue(String namespace, String name) {
+ try {
+ return mDelegate.getAttributeValue(namespace, name);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int getEventType() throws XmlPullParserException {
+ try {
+ return mDelegate.getEventType();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int next() throws XmlPullParserException, IOException {
+ return mDelegate.next();
+ }
+
+ @Override
+ public int nextToken() throws XmlPullParserException, IOException {
+ return mDelegate.nextToken();
+ }
+
+ @Override
+ public void require(int type, String namespace, String name)
+ throws XmlPullParserException, IOException {
+ mDelegate.require(type, namespace, name);
+ }
+
+ @Override
+ public String nextText() throws XmlPullParserException, IOException {
+ return mDelegate.nextText();
+ }
+
+ @Override
+ public int nextTag() throws XmlPullParserException, IOException {
+ return mDelegate.nextTag();
+ }
+}
diff --git a/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/package-info.java b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/package-info.java
new file mode 100644
index 0000000..2491759
--- /dev/null
+++ b/remote/server/src/com/android/layoutlib/bridge/remote/server/adapters/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Package containing all the server side adapters. These adapters have the mission of receiving
+ * local calls and translating them into the remote API.
+ */
+package com.android.layoutlib.bridge.remote.server.adapters;
\ No newline at end of file
diff --git a/remote/tests/out/failures/activity.png b/remote/tests/out/failures/activity.png
new file mode 100644
index 0000000..2920b7d
--- /dev/null
+++ b/remote/tests/out/failures/activity.png
Binary files differ
diff --git a/remote/tests/out/failures/delta-activity.png b/remote/tests/out/failures/delta-activity.png
new file mode 100644
index 0000000..32d9415
--- /dev/null
+++ b/remote/tests/out/failures/delta-activity.png
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
new file mode 100644
index 0000000..ddb7f64
--- /dev/null
+++ b/remote/tests/out/failures/delta-remote_component_load.png
Binary files differ
diff --git a/remote/tests/out/failures/remote_component_load.png b/remote/tests/out/failures/remote_component_load.png
new file mode 100644
index 0000000..0ed85d1
--- /dev/null
+++ b/remote/tests/out/failures/remote_component_load.png
Binary files differ
diff --git a/remote/tests/remote tests.iml b/remote/tests/remote tests.iml
new file mode 100644
index 0000000..de0a53a
--- /dev/null
+++ b/remote/tests/remote tests.iml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="remote client" scope="TEST" />
+ <orderEntry type="module" module-name="bridge" scope="TEST" />
+ <orderEntry type="library" scope="TEST" name="layoutlib_api-prebuilt" level="project" />
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/sdk-common/sdk-common.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/sdk-common/sdk-common-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <ANNOTATIONS>
+ <root url="file://$MODULE_DIR$/../.." />
+ </ANNOTATIONS>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <ANNOTATIONS>
+ <root url="file://$MODULE_DIR$/../.." />
+ </ANNOTATIONS>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/sdk-common/sdk-common.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/sdk-common/sdk-common-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="library" scope="TEST" name="framework.jar" level="project" />
+ <orderEntry type="module-library" scope="TEST">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="file://$MODULE_DIR$/../../../../libcore/xml/src/main/java" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="library" scope="TEST" name="junit" level="project" />
+ <orderEntry type="module" module-name="remote server" scope="TEST" />
+ <orderEntry type="module" module-name="remote common" scope="TEST" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/remote/tests/src/CustomComponent.java b/remote/tests/src/CustomComponent.java
new file mode 100644
index 0000000..6d6272d
--- /dev/null
+++ b/remote/tests/src/CustomComponent.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+@SuppressWarnings("unused") // Used by test
+public class CustomComponent extends TextView {
+ public CustomComponent(Context context) {
+ super(context);
+
+ init();
+ }
+
+ public CustomComponent(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ init();
+ }
+
+ public CustomComponent(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ init();
+ }
+
+ public CustomComponent(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ init();
+ }
+
+ private void init() {
+ setText("CustomComponent text");
+ setBackgroundColor(Color.RED);
+ setTextColor(Color.WHITE);
+ setTextSize(40f);
+ }
+}
diff --git a/remote/tests/src/RemoteBridgeTest.java b/remote/tests/src/RemoteBridgeTest.java
new file mode 100644
index 0000000..e0b0610
--- /dev/null
+++ b/remote/tests/src/RemoteBridgeTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+import com.android.ide.common.rendering.api.Bridge;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.layoutlib.bridge.intensive.RenderResult;
+import com.android.layoutlib.bridge.intensive.RenderTestBase;
+import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator;
+import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback;
+import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser;
+import com.android.layoutlib.bridge.intensive.util.ImageUtils;
+import com.android.layoutlib.bridge.remote.client.RemoteBridgeClient;
+import com.android.layoutlib.bridge.remote.server.ServerMain;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.rmi.NotBoundException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+
+public class RemoteBridgeTest extends RenderTestBase {
+ private ServerMain mServerMain;
+ private RemoteBridgeClient mClient;
+
+ /**
+ * Copy of RenderTestBase.renderAndVerify that allows using a different Bridge. TODO: Merge back
+ * into RenderTestBase
+ */
+ protected static RenderResult renderAndVerify(Bridge bridge, SessionParams params,
+ String goldenFileName, long frameTimeNanos) throws ClassNotFoundException {
+ RenderResult result = RenderTestBase.render(bridge, params, frameTimeNanos);
+ try {
+ String goldenImagePath = APP_TEST_DIR + "/golden/" + goldenFileName;
+ assertNotNull(result.getImage());
+ ImageUtils.requireSimilar(goldenImagePath, result.getImage());
+ } catch (IOException e) {
+ getLogger().error(e, e.getMessage());
+ }
+
+ return result;
+ }
+
+ @Before
+ public void setupServer() throws IOException, NotBoundException, InterruptedException {
+ mServerMain = ServerMain.forkAndStartServer(ServerMain.REGISTRY_BASE_PORT, 10);
+ mClient = RemoteBridgeClient.getRemoteBridge(mServerMain.getPort());
+
+ File data_dir = new File(PLATFORM_DIR, "data");
+ File res = new File(data_dir, "res");
+ File fontLocation = new File(data_dir, "fonts");
+ File buildProp = new File(PLATFORM_DIR, "build.prop");
+ File attrs = new File(res, "values" + File.separator + "attrs.xml");
+
+ mClient.init(ConfigGenerator.loadProperties(buildProp), fontLocation,
+ ConfigGenerator.getEnumMap(attrs), getLayoutLog());
+ }
+
+ @After
+ public void stopServer() {
+ mClient.dispose();
+ mServerMain.stop();
+ }
+
+ /**
+ * Same test as RenderTest#testActivity but using the remote bridge
+ */
+ @Test
+ public void testActivity() throws IOException, ClassNotFoundException {
+ SessionParams params = createSessionParams("activity.xml", ConfigGenerator.NEXUS_5);
+ RenderResult result = renderAndVerify(mClient, params, "activity.png", 250);
+ assertEquals(Result.Status.SUCCESS, result.getResult().getStatus());
+ if (result.getResult().getException() != null) {
+ result.getResult().getException().printStackTrace();
+ fail("Unexpected exception");
+ }
+ }
+
+ /**
+ * Same test as RenderTest#testActivity but using the remote bridge
+ */
+ @Test
+ public void testCustomClassLoading() throws ClassNotFoundException {
+ LayoutLibTestCallback layoutLibCallback =
+ new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+ layoutLibCallback.initResources();
+
+ LayoutPullParser parser = LayoutPullParser.createFromString(
+ "<CustomComponent xmlns:android=\"http://schemas" +
+ ".android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n>" +
+ "</CustomComponent>");
+ SessionParams params =
+ getSessionParamsBuilder().setParser(parser).setCallback(layoutLibCallback).setTheme(
+ "Theme.NoTitleBar", false).build();
+
+ RenderResult result = renderAndVerify(mClient, params, "remote_component_load.png", 250);
+ assertEquals(Result.Status.SUCCESS, result.getResult().getStatus());
+ if (result.getResult().getException() != null) {
+ result.getResult().getException().printStackTrace();
+ fail("Unexpected exception");
+ }
+
+ parser = LayoutPullParser.createFromString(
+ "<MissingCustomComponent xmlns:android=\"http://schemas" +
+ ".android.com/apk/res/android\"\n" +
+ " android:layout_width=\"match_parent\"\n" +
+ " android:layout_height=\"match_parent\"\n>" +
+ "</MissingCustomComponent>");
+ params =
+ getSessionParamsBuilder().setParser(parser).setCallback(layoutLibCallback).setTheme(
+ "Theme.NoTitleBar", false).build();
+ result = renderAndVerify(mClient, params, "remote_component_load_fail.png", 250);
+ assertEquals(Result.Status.SUCCESS, result.getResult().getStatus());
+ if (result.getResult().getException() != null) {
+ result.getResult().getException().printStackTrace();
+ fail("Unexpected exception");
+ }
+ }
+}
\ No newline at end of file
diff --git a/rename_font/build_font_single.py b/rename_font/build_font_single.py
index 4245cdc..4678a4f 100755
--- a/rename_font/build_font_single.py
+++ b/rename_font/build_font_single.py
@@ -17,8 +17,8 @@
"""
Rename the PS name of the input font.
-OpenType fonts (*.otf) are not currently supported. They are copied to the destination without renaming.
-XML files are also copied in case they are passed there by mistake.
+OpenType fonts (*.otf) and TrueType Collections (*.ttc) are not currently supported. They are copied to the destination
+without renaming. XML files are also copied in case they are passed there by mistake.
Usage: build_font_single.py /path/to/input_font.ttf /path/to/output_font.ttf
@@ -63,7 +63,7 @@
NAMEID_VERSION = 5
# A list of extensions to process.
-EXTENSIONS = ['.ttf', '.otf', '.xml']
+EXTENSIONS = ['.ttf', '.ttc', '.otf', '.xml']
def main(argv):
if len(argv) < 2: