diff options
Diffstat (limited to 'tools/layoutlib')
236 files changed, 7149 insertions, 2731 deletions
diff --git a/tools/layoutlib/.gitignore b/tools/layoutlib/.gitignore index c5e82d7..eb52b64 100644 --- a/tools/layoutlib/.gitignore +++ b/tools/layoutlib/.gitignore @@ -1 +1,3 @@ -bin
\ No newline at end of file +bin +/.idea/workspace.xml +/out diff --git a/tools/layoutlib/.idea/.name b/tools/layoutlib/.idea/.name new file mode 100644 index 0000000..10eb5c1 --- /dev/null +++ b/tools/layoutlib/.idea/.name @@ -0,0 +1 @@ +layoutlib
\ No newline at end of file diff --git a/tools/layoutlib/.idea/codeStyleSettings.xml b/tools/layoutlib/.idea/codeStyleSettings.xml new file mode 100644 index 0000000..b324213 --- /dev/null +++ b/tools/layoutlib/.idea/codeStyleSettings.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectCodeStyleSettingsManager"> + <option name="PER_PROJECT_SETTINGS"> + <value> + <option name="FIELD_NAME_PREFIX" value="m" /> + <option name="STATIC_FIELD_NAME_PREFIX" value="s" /> + <option name="USE_FQ_CLASS_NAMES_IN_JAVADOC" value="false" /> + <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="RIGHT_MARGIN" value="100" /> + <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" /> + <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" /> + <option name="WRAP_COMMENTS" value="true" /> + <XML> + <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> + </XML> + <codeStyleSettings language="JAVA"> + <option name="INDENT_CASE_FROM_SWITCH" 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" /> + <arrangement> + <groups> + <group> + <type>GETTERS_AND_SETTERS</type> + <order>KEEP</order> + </group> + <group> + <type>OVERRIDDEN_METHODS</type> + <order>KEEP</order> + </group> + </groups> + </arrangement> + </codeStyleSettings> + </value> + </option> + <option name="USE_PER_PROJECT_SETTINGS" value="true" /> + </component> +</project> + diff --git a/tools/layoutlib/.idea/compiler.xml b/tools/layoutlib/.idea/compiler.xml new file mode 100644 index 0000000..5aaaf18 --- /dev/null +++ b/tools/layoutlib/.idea/compiler.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="CompilerConfiguration"> + <option name="DEFAULT_COMPILER" value="Javac" /> + <excludeFromCompile> + <directory url="file://$PROJECT_DIR$/create/tests/mock_data" includeSubdirectories="true" /> + </excludeFromCompile> + <resourceExtensions /> + <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> + <annotationProcessing> + <profile default="true" name="Default" enabled="false"> + <processorPath useClasspath="true" /> + </profile> + </annotationProcessing> + <bytecodeTargetLevel target="1.6" /> + </component> +</project> + diff --git a/tools/layoutlib/.idea/copyright/Android.xml b/tools/layoutlib/.idea/copyright/Android.xml new file mode 100644 index 0000000..d81d75d --- /dev/null +++ b/tools/layoutlib/.idea/copyright/Android.xml @@ -0,0 +1,9 @@ +<component name="CopyrightManager"> + <copyright> + <option name="notice" value="Copyright (C) &#36;today.year 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." /> + <option name="keyword" value="Copyright" /> + <option name="allowReplaceKeyword" value="" /> + <option name="myName" value="Android" /> + <option name="myLocal" value="true" /> + </copyright> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/copyright/profiles_settings.xml b/tools/layoutlib/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..20145de --- /dev/null +++ b/tools/layoutlib/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ +<component name="CopyrightManager"> + <settings default="Android" /> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/tools/layoutlib/.idea/encodings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" /> +</project> + diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..0ac7a44 --- /dev/null +++ b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,11 @@ +<component name="InspectionProjectProfileManager"> + <profile version="1.0" is_locked="false"> + <option name="myName" value="Project Default" /> + <option name="myLocal" value="false" /> + <inspection_tool class="DefaultFileTemplate" enabled="false" level="WARNING" enabled_by_default="false"> + <option name="CHECK_FILE_HEADER" value="true" /> + <option name="CHECK_TRY_CATCH_SECTION" value="true" /> + <option name="CHECK_METHOD_BODY" value="true" /> + </inspection_tool> + </profile> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/inspectionProfiles/profiles_settings.xml b/tools/layoutlib/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/tools/layoutlib/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ +<component name="InspectionProjectProfileManager"> + <settings> + <option name="PROJECT_PROFILE" value="Project Default" /> + <option name="USE_PROJECT_PROFILE" value="true" /> + <version value="1.0" /> + </settings> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/asm_4_0.xml b/tools/layoutlib/.idea/libraries/asm_4_0.xml new file mode 100644 index 0000000..7df287f --- /dev/null +++ b/tools/layoutlib/.idea/libraries/asm_4_0.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="asm-4.0"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/asm/asm-4.0.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/asm/src.zip!/" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/framework_jar.xml b/tools/layoutlib/.idea/libraries/framework_jar.xml new file mode 100644 index 0000000..6695a36 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/framework_jar.xml @@ -0,0 +1,13 @@ +<component name="libraryTable"> + <library name="framework.jar"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$PROJECT_DIR$/../../core/java" /> + <root url="file://$PROJECT_DIR$/../../graphics/java" /> + <root url="file://$PROJECT_DIR$/../../../../libcore/luni/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/guava.xml b/tools/layoutlib/.idea/libraries/guava.xml new file mode 100644 index 0000000..d47fc06 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/guava.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="guava"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/guavalib_intermediates/javalib.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$PROJECT_DIR$/../../../../external/guava/guava/src" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/icu4j.xml b/tools/layoutlib/.idea/libraries/icu4j.xml new file mode 100644 index 0000000..dbe0bd7 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/icu4j.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="icu4j"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/icu4j/icu4j.jar!/" /> + </CLASSES> + <JAVADOC> + <root url="http://icu-project.org/apiref/icu4j50rc/" /> + </JAVADOC> + <SOURCES /> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml b/tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml new file mode 100644 index 0000000..2a65050 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="kxml2-2.3.0"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$PROJECT_DIR$/../../../../libcore/xml/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/layoutlib_api_prebuilt.xml b/tools/layoutlib/.idea/libraries/layoutlib_api_prebuilt.xml new file mode 100644 index 0000000..5952002 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/layoutlib_api_prebuilt.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="layoutlib_api-prebuilt"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$ANDROID_SRC$/tools/base/layoutlib-api/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml b/tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml new file mode 100644 index 0000000..f34f7dd --- /dev/null +++ b/tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="ninepatch-prebuilt"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$ANDROID_SRC$/tools/base/ninepatch/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml b/tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml new file mode 100644 index 0000000..b325ad4 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml @@ -0,0 +1,14 @@ +<component name="libraryTable"> + <library name="tools-common-prebuilt"> + <ANNOTATIONS> + <root url="file://$PROJECT_DIR$" /> + </ANNOTATIONS> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$ANDROID_SRC$/tools/base/common/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/misc.xml b/tools/layoutlib/.idea/misc.xml new file mode 100644 index 0000000..fd63e6c --- /dev/null +++ b/tools/layoutlib/.idea/misc.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="EntryPointsManager"> + <entry_points version="2.0" /> + <list size="1"> + <item index="0" class="java.lang.String" itemvalue="com.android.tools.layoutlib.annotations.LayoutlibDelegate" /> + </list> + </component> + <component name="FrameworkDetectionExcludesConfiguration"> + <type id="android" /> + </component> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/out" /> + </component> +</project> + diff --git a/tools/layoutlib/.idea/modules.xml b/tools/layoutlib/.idea/modules.xml new file mode 100644 index 0000000..684f4fd --- /dev/null +++ b/tools/layoutlib/.idea/modules.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/bridge/bridge.iml" filepath="$PROJECT_DIR$/bridge/bridge.iml" /> + <module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" /> + </modules> + </component> +</project> + diff --git a/tools/layoutlib/.idea/runConfigurations/All_in_bridge.xml b/tools/layoutlib/.idea/runConfigurations/All_in_bridge.xml new file mode 100644 index 0000000..f965ba7 --- /dev/null +++ b/tools/layoutlib/.idea/runConfigurations/All_in_bridge.xml @@ -0,0 +1,31 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="All in bridge" type="JUnit" factoryName="JUnit" singleton="true" nameIsGenerated="true"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <module name="bridge" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" value="" /> + <option name="PACKAGE_NAME" value="" /> + <option name="MAIN_CLASS_NAME" value="" /> + <option name="METHOD_NAME" value="" /> + <option name="TEST_OBJECT" value="package" /> + <option name="VM_PARAMETERS" value="-ea -Dtest_res.dir="$PROJECT_DIR$/bridge/tests/res"" /> + <option name="PARAMETERS" value="" /> + <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <option name="TEST_SEARCH_SCOPE"> + <value defaultName="singleModule" /> + </option> + <envs /> + <patterns /> + <RunnerSettings RunnerId="Debug"> + <option name="DEBUG_PORT" value="" /> + <option name="TRANSPORT" value="0" /> + <option name="LOCAL" value="true" /> + </RunnerSettings> + <RunnerSettings RunnerId="Run" /> + <ConfigurationWrapper RunnerId="Debug" /> + <ConfigurationWrapper RunnerId="Run" /> + <method /> + </configuration> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/runConfigurations/All_in_create.xml b/tools/layoutlib/.idea/runConfigurations/All_in_create.xml new file mode 100644 index 0000000..b9cd419 --- /dev/null +++ b/tools/layoutlib/.idea/runConfigurations/All_in_create.xml @@ -0,0 +1,31 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="All in create" type="JUnit" factoryName="JUnit" singleton="false" nameIsGenerated="true"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <module name="create" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" value="" /> + <option name="PACKAGE_NAME" value="" /> + <option name="MAIN_CLASS_NAME" value="" /> + <option name="METHOD_NAME" value="" /> + <option name="TEST_OBJECT" value="package" /> + <option name="VM_PARAMETERS" value="-ea" /> + <option name="PARAMETERS" value="" /> + <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <option name="TEST_SEARCH_SCOPE"> + <value defaultName="singleModule" /> + </option> + <envs /> + <patterns /> + <RunnerSettings RunnerId="Debug"> + <option name="DEBUG_PORT" value="" /> + <option name="TRANSPORT" value="0" /> + <option name="LOCAL" value="true" /> + </RunnerSettings> + <RunnerSettings RunnerId="Run" /> + <ConfigurationWrapper RunnerId="Debug" /> + <ConfigurationWrapper RunnerId="Run" /> + <method /> + </configuration> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/runConfigurations/Create.xml b/tools/layoutlib/.idea/runConfigurations/Create.xml new file mode 100644 index 0000000..fb0b866 --- /dev/null +++ b/tools/layoutlib/.idea/runConfigurations/Create.xml @@ -0,0 +1,25 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="Create" type="Application" factoryName="Application" singleton="true"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <option name="MAIN_CLASS_NAME" value="com.android.tools.layoutlib.create.Main" /> + <option name="VM_PARAMETERS" value="" /> + <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar" /> + <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/../../../../" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" /> + <option name="ALTERNATIVE_JRE_PATH" value="1.6" /> + <option name="ENABLE_SWING_INSPECTOR" value="false" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <module name="create" /> + <envs /> + <RunnerSettings RunnerId="Debug"> + <option name="DEBUG_PORT" value="" /> + <option name="TRANSPORT" value="0" /> + <option name="LOCAL" value="true" /> + </RunnerSettings> + <RunnerSettings RunnerId="Run" /> + <ConfigurationWrapper RunnerId="Debug" /> + <ConfigurationWrapper RunnerId="Run" /> + <method /> + </configuration> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/scopes/scope_settings.xml b/tools/layoutlib/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/tools/layoutlib/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ +<component name="DependencyValidationManager"> + <state> + <option name="SKIP_IMPORT_STATEMENTS" value="false" /> + </state> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/uiDesigner.xml b/tools/layoutlib/.idea/uiDesigner.xml new file mode 100644 index 0000000..3b00020 --- /dev/null +++ b/tools/layoutlib/.idea/uiDesigner.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Palette2"> + <group name="Swing"> + <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> + </item> + <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true"> + <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> + <initial-values> + <property name="text" value="Button" /> + </initial-values> + </item> + <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="RadioButton" /> + </initial-values> + </item> + <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="CheckBox" /> + </initial-values> + </item> + <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="Label" /> + </initial-values> + </item> + <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> + <preferred-size width="-1" height="20" /> + </default-constraints> + </item> + <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> + </item> + </group> + </component> +</project> + diff --git a/tools/layoutlib/.idea/vcs.xml b/tools/layoutlib/.idea/vcs.xml new file mode 100644 index 0000000..9ab281a --- /dev/null +++ b/tools/layoutlib/.idea/vcs.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="$PROJECT_DIR$/../.." vcs="Git" /> + </component> +</project> + diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk index 5d03842..9300401 100644 --- a/tools/layoutlib/Android.mk +++ b/tools/layoutlib/Android.mk @@ -16,6 +16,8 @@ LOCAL_PATH := $(my-dir) include $(CLEAR_VARS) +LOCAL_JAVACFLAGS := -source 6 -target 6 + # # Define rules to build temp_layoutlib.jar, which contains a subset of # the classes in framework.jar. The layoutlib_create tool is used to @@ -25,8 +27,8 @@ include $(CLEAR_VARS) # We need to process the framework classes.jar file, but we can't # depend directly on it (private vars won't be inherited correctly). # So, we depend on framework's BUILT file. -built_framework_dep := $(call java-lib-deps,framework-base) -built_framework_classes := $(call java-lib-files,framework-base) +built_framework_dep := $(call java-lib-deps,framework) +built_framework_classes := $(call java-lib-files,framework) built_core_dep := $(call java-lib-deps,core-libart) built_core_classes := $(call java-lib-files,core-libart) @@ -53,12 +55,13 @@ include $(BUILD_SYSTEM)/base_rules.mk $(LOCAL_BUILT_MODULE): $(built_core_dep) \ $(built_framework_dep) \ $(built_ext_dep) \ + $(built_ext_data) \ $(built_layoutlib_create_jar) $(hide) echo "host layoutlib_create: $@" $(hide) mkdir -p $(dir $@) $(hide) rm -f $@ $(hide) ls -l $(built_framework_classes) - $(hide) java -jar $(built_layoutlib_create_jar) \ + $(hide) java -ea -jar $(built_layoutlib_create_jar) \ $@ \ $(built_core_classes) \ $(built_framework_classes) \ diff --git a/tools/layoutlib/bridge/.classpath b/tools/layoutlib/bridge/.classpath index 2e4274d..9c4160c 100644 --- a/tools/layoutlib/bridge/.classpath +++ b/tools/layoutlib/bridge/.classpath @@ -1,12 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry excluding="org/kxml2/io/" kind="src" path="src"/> + <classpathentry kind="src" path="tests/src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> - <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar" sourcepath="/ANDROID_SRC/tools/base/layoutlib-api/src/main"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/tools-common/tools-common-prebuilt.jar"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/icu4j/icu4j.jar"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/sdk-common/sdk-common.jar"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/guavalib_intermediates/javalib.jar"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk index e3d48fc..cfd597e 100644 --- a/tools/layoutlib/bridge/Android.mk +++ b/tools/layoutlib/bridge/Android.mk @@ -18,6 +18,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_JAVA_RESOURCE_DIRS := resources +LOCAL_JAVACFLAGS := -source 6 -target 6 LOCAL_JAVA_LIBRARIES := \ diff --git a/tools/layoutlib/bridge/bridge.iml b/tools/layoutlib/bridge/bridge.iml new file mode 100644 index 0000000..0f96916 --- /dev/null +++ b/tools/layoutlib/bridge/bridge.iml @@ -0,0 +1,48 @@ +<?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$/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/tests/res" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/tests/src" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/.settings" /> + <excludeFolder url="file://$MODULE_DIR$/bin" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/.gradle" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/.idea" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/generated" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/assets" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/dependency-cache" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/incremental" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/libs" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/manifests" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/res" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/rs" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/symbols" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/gradle" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="icu4j" level="project" /> + <orderEntry type="library" name="kxml2-2.3.0" level="project" /> + <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" /> + <orderEntry type="library" name="ninepatch-prebuilt" level="project" /> + <orderEntry type="library" name="tools-common-prebuilt" level="project" /> + <orderEntry type="library" name="framework.jar" level="project" /> + <orderEntry type="library" scope="TEST" name="guava" 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="library" scope="TEST" name="JUnit4" level="application" /> + </component> +</module> + diff --git a/tools/layoutlib/bridge/resources/bars/README b/tools/layoutlib/bridge/resources/bars/README new file mode 100644 index 0000000..c84ef80 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/README @@ -0,0 +1,8 @@ +The directory contains the resources for StatusBar and Navigation Bar. + +The resources are not arranged as per the standard resources configuration. +They are stored per API. However, to prevent duplication of resources, each API +resource directory is used as a backup for all earlier API levels. + +For example, for the back icon for ICS, we search first in v18, where we don't +find it, and then in v19. diff --git a/tools/layoutlib/bridge/resources/bars/action_bar.xml b/tools/layoutlib/bridge/resources/bars/action_bar.xml deleted file mode 100644 index 7adc5af..0000000 --- a/tools/layoutlib/bridge/resources/bars/action_bar.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<merge xmlns:android="http://schemas.android.com/apk/res/android"> - <include layout="@android:layout/action_bar_home" /> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> -</merge> diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..f17189a --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png Binary files differdeleted file mode 100644 index 829378e..0000000 --- a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differdeleted file mode 100644 index 931daed..0000000 --- a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png Binary files differdeleted file mode 100644 index a4be298..0000000 --- a/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..2a9757d --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png Binary files differdeleted file mode 100644 index 2773a70..0000000 --- a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differdeleted file mode 100644 index 6e1ac91..0000000 --- a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png Binary files differdeleted file mode 100644 index eb7c1a4..0000000 --- a/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/status_bar.xml b/tools/layoutlib/bridge/resources/bars/status_bar.xml index 51b474d..04571e1 100644 --- a/tools/layoutlib/bridge/resources/bars/status_bar.xml +++ b/tools/layoutlib/bridge/resources/bars/status_bar.xml @@ -1,17 +1,27 @@ <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="1"/> - <ImageView - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layout_marginTop="1dp"/> - <ImageView - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layout_marginLeft="3dp" - android:layout_marginRight="5dp" - android:layout_marginTop="1dp"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"/> + <!-- The exact size of the wifi icon is specified in order to scale it properly. + Without scaling, it appeared huge. This is currently, 70% of the actual size. --> + <ImageView + android:layout_height="22.4dp" + android:layout_width="20.65dp" + android:layout_marginTop="1dp"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_marginLeft="3dp" + android:layout_marginRight="5dp" + android:layout_marginTop="4dp"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:layout_marginRight="5dp" + android:gravity="center_vertical" + android:textSize="16dp" + android:fontFamily="sans-serif-medium"/> </merge> diff --git a/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..c920ec4 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..6248cfd --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..943332e --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..441de0c --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..36c61e1 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..459a1a2 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_back.png Binary files differindex 84e6bc8..84e6bc8 100644 --- a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_home.png Binary files differindex 38e4f45..38e4f45 100644 --- a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png +++ b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_recent.png Binary files differindex bf9f300..bf9f300 100644 --- a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..6248cfd --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/hdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_back.png Binary files differindex 782ebfe..782ebfe 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_recent.png Binary files differindex 677b471..677b471 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_back.png Binary files differindex a1b8062..a1b8062 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_recent.png Binary files differindex fcdbefe..fcdbefe 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_back.png Binary files differindex 633d864..633d864 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_recent.png Binary files differindex 4665e2a..4665e2a 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_back.png Binary files differindex a00bc5b..a00bc5b 100644 --- a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_home.png Binary files differindex dc3183b..dc3183b 100644 --- a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png +++ b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_recent.png Binary files differindex b07f611..b07f611 100644 --- a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..441de0c --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_back.png Binary files differindex bd60cd6..bd60cd6 100644 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_home.png Binary files differindex c5bc5c9..c5bc5c9 100644 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png +++ b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_recent.png Binary files differindex f621d9c..f621d9c 100644 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..459a1a2 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..79cfcee --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 0000000..64f6a22 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..6e0b071 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..494b005 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..b28624f --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 0000000..3f3e288 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..06dcd20 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..f17189a --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..e464347 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..1b578a6 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..373e84a --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..6b19593 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..f878093 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 0000000..8e9583b --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..e2a89c3 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..2a9757d --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..ec2951d --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 0000000..254f757 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..8a8e941 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..555bcd9 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_wifi_signal_4_fully.xml b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_wifi_signal_4_fully.xml new file mode 100644 index 0000000..0498b6c --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_wifi_signal_4_fully.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32.0dp" + android:height="29.5dp" + android:viewportWidth="26.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/> +</vector> diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 0000000..77969b8 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 0000000..d60229f --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 0000000..a261f85 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..6474aad --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..754cdf6 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..b5326d2 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/ldpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v9/ldpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..7023ea7 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/ldpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..17a955d --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 0000000..19165ab --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 0000000..555bcd9 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png Binary files differdeleted file mode 100644 index c7fd719..0000000 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png Binary files differdeleted file mode 100644 index 625c61d..0000000 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png +++ /dev/null diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java index 224eac6..4603b63 100644 --- a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java +++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java @@ -48,6 +48,20 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; } @LayoutlibDelegate + /*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName, + int numParams) { + // TODO: return the right thing. + return 0; + } + + @LayoutlibDelegate + /*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName, + int numParams) { + // TODO: return the right thing. + return 0; + } + + @LayoutlibDelegate /*package*/ static void nCallIntMethod(Object target, long methodID, int arg) { // do nothing } @@ -56,4 +70,40 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; /*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) { // do nothing } + + @LayoutlibDelegate + /*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1, + int arg2) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1, + int arg2, int arg3, int arg4) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallMultipleIntMethod(Object target, long methodID, + int[] args) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1, + float arg2) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1, + float arg2, float arg3, float arg4) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallMultipleFloatMethod(Object target, long methodID, + float[] args) { + // do nothing + } } diff --git a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java new file mode 100644 index 0000000..914a359 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.res; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate used to provide implementation of a select few native methods of {@link AssetManager} + * <p/> + * Through the layoutlib_create tool, the original native methods of AssetManager have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class AssetManager_Delegate { + + @LayoutlibDelegate + /*package*/ static long newTheme(AssetManager manager) { + return Resources_Theme_Delegate.getDelegateManager() + .addNewDelegate(new Resources_Theme_Delegate()); + } + + @LayoutlibDelegate + /*package*/ static void deleteTheme(AssetManager manager, long theme) { + Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme); + } + + @LayoutlibDelegate + /*package*/ static void applyThemeStyle(long theme, int styleRes, boolean force) { + Resources_Theme_Delegate.getDelegateManager().getDelegate(theme).force = force; + } +} diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java index a953918..93814b2 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java @@ -35,7 +35,7 @@ public class BridgeAssetManager extends AssetManager { // Note that AssetManager() creates a system AssetManager and we override it // with our BridgeAssetManager. AssetManager.sSystem = new BridgeAssetManager(); - AssetManager.sSystem.makeStringBlocks(false); + AssetManager.sSystem.makeStringBlocks(null); } return AssetManager.sSystem; } diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java index 8794452..dd573be 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java @@ -51,6 +51,7 @@ public final class BridgeResources extends Resources { private BridgeContext mContext; private IProjectCallback mProjectCallback; private boolean[] mPlatformResourceFlag = new boolean[1]; + private TypedValue mTmpValue = new TypedValue(); /** * Simpler wrapper around FileInputStream. This is used when the input stream represent @@ -154,6 +155,11 @@ public final class BridgeResources extends Resources { @Override public Drawable getDrawable(int id) throws NotFoundException { + return getDrawable(id, null); + } + + @Override + public Drawable getDrawable(int id, Theme theme) { Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java index 446d139..28a109d 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -23,7 +23,6 @@ import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.internal.util.XmlUtils; import com.android.layoutlib.bridge.Bridge; -import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.impl.ParserFactory; @@ -52,13 +51,13 @@ public final class BridgeTypedArray extends TypedArray { private final BridgeContext mContext; private final boolean mPlatformFile; - private ResourceValue[] mResourceData; - private String[] mNames; - private boolean[] mIsFramework; + private final ResourceValue[] mResourceData; + private final String[] mNames; + private final boolean[] mIsFramework; public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, boolean platformFile) { - super(null, null, null, 0); + super(resources, null, null, 0); mBridgeResources = resources; mContext = context; mPlatformFile = platformFile; @@ -81,8 +80,8 @@ public final class BridgeTypedArray extends TypedArray { } /** - * Seals the array after all calls to {@link #bridgeSetValue(int, String, ResourceValue)} have - * been done. + * Seals the array after all calls to + * {@link #bridgeSetValue(int, String, boolean, ResourceValue)} have been done. * <p/>This allows to compute the list of non default values, permitting * {@link #getIndexCount()} to return the proper value. */ @@ -90,9 +89,16 @@ public final class BridgeTypedArray extends TypedArray { // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt // first count the array size int count = 0; - for (ResourceValue data : mResourceData) { + for (int i = 0; i < mResourceData.length; i++) { + ResourceValue data = mResourceData[i]; if (data != null) { - count++; + if (RenderResources.REFERENCE_NULL.equals(data.getValue())) { + // No need to store this resource value. This saves needless checking for + // "@null" every time an attribute is requested. + mResourceData[i] = null; + } else { + count++; + } } } @@ -135,16 +141,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence getText(int index) { - if (index < 0 || index >= mResourceData.length) { - return null; - } - - if (mResourceData[index] != null) { - // FIXME: handle styled strings! - return mResourceData[index].getValue(); - } - - return null; + // FIXME: handle styled strings! + return getString(index); } /** @@ -157,15 +155,14 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public String getString(int index) { - if (index < 0 || index >= mResourceData.length) { + if (!hasValue(index)) { return null; } - - if (mResourceData[index] != null) { - return mResourceData[index].getValue(); - } - - return null; + // As unfortunate as it is, it's possible to use enums with all attribute formats, + // not just integers/enums. So, we need to search the enums always. In case + // enums are used, the returned value is an integer. + Integer v = resolveEnumAttribute(index); + return v == null ? mResourceData[index].getValue() : String.valueOf((int) v); } /** @@ -178,20 +175,9 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getBoolean(int index, boolean defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } + String s = getString(index); + return s == null ? defValue : XmlUtils.convertValueToBoolean(s, defValue); - String s = mResourceData[index].getValue(); - if (s != null) { - return XmlUtils.convertValueToBoolean(s, defValue); - } - - return defValue; } /** @@ -204,65 +190,18 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getInt(int index, int defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } - - String s = mResourceData[index].getValue(); - - if (RenderResources.REFERENCE_NULL.equals(s)) { - return defValue; - } - - if (s == null || s.length() == 0) { - return defValue; - } - + String s = getString(index); try { - return XmlUtils.convertValueToInt(s, defValue); - } catch (NumberFormatException e) { - // pass - } - - // Field is not null and is not an integer. - // Check for possible constants and try to find them. - // Get the map of attribute-constant -> IntegerValue - Map<String, Integer> map = null; - if (mIsFramework[index]) { - map = Bridge.getEnumValues(mNames[index]); - } else { - // get the styleable matching the resolved name - RenderResources res = mContext.getRenderResources(); - ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]); - if (attr instanceof AttrResourceValue) { - map = ((AttrResourceValue) attr).getAttributeValues(); + if (s != null) { + return XmlUtils.convertValueToInt(s, defValue); } + } catch (NumberFormatException e) { + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format("\"%1$s\" in attribute \"%2$s\" is not a valid integer", + s, mNames[index]), + null); + return defValue; } - - if (map != null) { - // accumulator to store the value of the 1+ constants. - int result = 0; - - // split the value in case this is a mix of several flags. - String[] keywords = s.split("\\|"); - for (String keyword : keywords) { - Integer i = map.get(keyword.trim()); - if (i != null) { - result |= i.intValue(); - } else { - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%s\" in attribute \"%2$s\" is not a valid value", - keyword, mNames[index]), null /*data*/); - } - } - return result; - } - return defValue; } @@ -275,27 +214,16 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFloat(int index, float defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } - - String s = mResourceData[index].getValue(); - - if (s != null) { - try { - return Float.parseFloat(s); - } catch (NumberFormatException e) { - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%s\" in attribute \"%2$s\" cannot be converted to float.", - s, mNames[index]), null /*data*/); - - // we'll return the default value below. + String s = getString(index); + try { + if (s != null) { + return Float.parseFloat(s); } + } catch (NumberFormatException e) { + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format("\"%1$s\" in attribute \"%2$s\" cannot be converted to float.", + s, mNames[index]), + null); } return defValue; } @@ -342,11 +270,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public ColorStateList getColorStateList(int index) { - if (index < 0 || index >= mResourceData.length) { - return null; - } - - if (mResourceData[index] == null) { + if (!hasValue(index)) { return null; } @@ -357,10 +281,6 @@ public final class BridgeTypedArray extends TypedArray { return null; } - if (RenderResources.REFERENCE_NULL.equals(value)) { - return null; - } - // let the framework inflate the ColorStateList from the XML file. File f = new File(value); if (f.isFile()) { @@ -376,13 +296,13 @@ public final class BridgeTypedArray extends TypedArray { } } catch (XmlPullParserException e) { Bridge.getLog().error(LayoutLog.TAG_BROKEN, - "Failed to configure parser for " + value, e, null /*data*/); + "Failed to configure parser for " + value, e, null); return null; } 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*/); + "Failed to parse file " + value, e, null); return null; } @@ -392,7 +312,7 @@ public final class BridgeTypedArray extends TypedArray { int color = ResourceHelper.getColor(value); return ColorStateList.valueOf(color); } catch (NumberFormatException e) { - Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null /*data*/); + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null); } return null; @@ -430,37 +350,24 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getDimension(int index, float defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } - - String s = mResourceData[index].getValue(); - + String s = getString(index); if (s == null) { return defValue; - } else if (s.equals(BridgeConstants.MATCH_PARENT) || - s.equals(BridgeConstants.FILL_PARENT)) { - return LayoutParams.MATCH_PARENT; - } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { - return LayoutParams.WRAP_CONTENT; - } else if (RenderResources.REFERENCE_NULL.equals(s)) { - return defValue; + } + // Check if the value is a magic constant that doesn't require a unit. + try { + int i = Integer.parseInt(s); + if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) { + return i; + } + } catch (NumberFormatException ignored) { + // pass } - if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) { + if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) { return mValue.getDimension(mBridgeResources.getDisplayMetrics()); } - // looks like we were unable to resolve the dimension value - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%1$s\" in attribute \"%2$s\" is not a valid format.", - s, mNames[index]), null /*data*/); - return defValue; } @@ -509,16 +416,13 @@ public final class BridgeTypedArray extends TypedArray { try { return getDimension(index); } catch (RuntimeException e) { - if (mResourceData[index] != null) { - String s = mResourceData[index].getValue(); - - if (s != null) { - // looks like we were unable to resolve the dimension value - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%1$s\" in attribute \"%2$s\" is not a valid format.", - s, mNames[index]), null /*data*/); - } + String s = getString(index); + + if (s != null) { + // looks like we were unable to resolve the dimension value + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format("\"%1$s\" in attribute \"%2$s\" is not a valid format.", + s, mNames[index]), null); } return defValue; @@ -561,24 +465,20 @@ public final class BridgeTypedArray extends TypedArray { } private int getDimension(int index) { - if (mResourceData[index] == null) { - throw new RuntimeException(); - } - - String s = mResourceData[index].getValue(); - + String s = getString(index); if (s == null) { throw new RuntimeException(); - } else if (s.equals(BridgeConstants.MATCH_PARENT) || - s.equals(BridgeConstants.FILL_PARENT)) { - return LayoutParams.MATCH_PARENT; - } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { - return LayoutParams.WRAP_CONTENT; - } else if (RenderResources.REFERENCE_NULL.equals(s)) { - throw new RuntimeException(); } - - if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) { + // Check if the value is a magic constant that doesn't require a unit. + try { + int i = Integer.parseInt(s); + if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) { + return i; + } + } catch (NumberFormatException ignored) { + // pass + } + if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) { float f = mValue.getDimension(mBridgeResources.getDisplayMetrics()); final int res = (int)(f+0.5f); @@ -607,29 +507,20 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFraction(int index, int base, int pbase, float defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } - - String value = mResourceData[index].getValue(); + String value = getString(index); if (value == null) { return defValue; } - if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, - false /*requireUnit*/)) { + if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, false)) { return mValue.getFraction(base, pbase); } // looks like we were unable to resolve the fraction value Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, String.format( - "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.", - value, mNames[index]), null /*data*/); + "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.", + value, mNames[index]), null); return defValue; } @@ -668,10 +559,6 @@ public final class BridgeTypedArray extends TypedArray { return mContext.getDynamicIdByStyle((StyleResourceValue)resValue); } - if (RenderResources.REFERENCE_NULL.equals(resValue.getValue())) { - return defValue; - } - // if the attribute was a reference to a resource, and not a declaration of an id (@+id), // then the xml attribute value was "resolved" which leads us to a ResourceValue with a // valid getType() and getName() returning a resource name. @@ -731,7 +618,7 @@ public final class BridgeTypedArray extends TypedArray { } // not a direct id valid reference? resolve it - Integer idValue = null; + Integer idValue; if (resValue.isFramework()) { idValue = Bridge.getResourceId(resValue.getResourceType(), @@ -742,7 +629,7 @@ public final class BridgeTypedArray extends TypedArray { } if (idValue != null) { - return idValue.intValue(); + return idValue; } Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE, @@ -753,6 +640,12 @@ public final class BridgeTypedArray extends TypedArray { return defValue; } + @Override + public int getThemeAttributeId(int index, int defValue) { + // TODO: Get the right Theme Attribute ID to enable caching of the drawables. + return defValue; + } + /** * Retrieve the Drawable for the attribute at <var>index</var>. This * gets the resource ID of the selected attribute, and uses @@ -765,20 +658,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public Drawable getDrawable(int index) { - if (index < 0 || index >= mResourceData.length) { - return null; - } - - if (mResourceData[index] == null) { + if (!hasValue(index)) { return null; } ResourceValue value = mResourceData[index]; - String stringValue = value.getValue(); - if (stringValue == null || RenderResources.REFERENCE_NULL.equals(stringValue)) { - return null; - } - return ResourceHelper.getDrawable(value, mContext); } @@ -795,31 +679,28 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence[] getTextArray(int index) { - if (index < 0 || index >= mResourceData.length) { - return null; - } - - if (mResourceData[index] == null) { - return null; - } - - String value = mResourceData[index].getValue(); + String value = getString(index); if (value != null) { - if (RenderResources.REFERENCE_NULL.equals(value)) { - return null; - } - return new CharSequence[] { value }; } - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - String.format("Unknown value for getTextArray(%d) => %s", //DEBUG - index, mResourceData[index].getName())), null /*data*/); + return null; + } + @Override + public int[] extractThemeAttrs() { + // The drawables are always inflated with a Theme and we don't care about caching. So, + // just return. return null; } + @Override + public int getChangingConfigurations() { + // We don't care about caching. Any change in configuration is a fresh render. So, + // just return. + return 0; + } + /** * Retrieve the raw TypedValue for the attribute at <var>index</var>. * @@ -831,18 +712,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getValue(int index, TypedValue outValue) { - if (index < 0 || index >= mResourceData.length) { - return false; - } - - if (mResourceData[index] == null) { - return false; - } - - String s = mResourceData[index].getValue(); - - return ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, - false /*requireUnit*/); + String s = getString(index); + return s != null && ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, false); } /** @@ -854,11 +725,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean hasValue(int index) { - if (index < 0 || index >= mResourceData.length) { - return false; - } - - return mResourceData[index] != null; + return index >= 0 && index < mResourceData.length && mResourceData[index] != null; } /** @@ -905,4 +772,55 @@ public final class BridgeTypedArray extends TypedArray { public String toString() { return Arrays.toString(mResourceData); } - } + + /** + * Searches for the string in the attributes (flag or enums) and returns the integer. + * If found, it will return an integer matching the value. + * + * @param index Index of attribute to retrieve. + * + * @return Attribute int value, or null if not defined. + */ + private Integer resolveEnumAttribute(int index) { + // Get the map of attribute-constant -> IntegerValue + Map<String, Integer> map = null; + if (mIsFramework[index]) { + map = Bridge.getEnumValues(mNames[index]); + } else { + // get the styleable matching the resolved name + RenderResources res = mContext.getRenderResources(); + ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]); + if (attr instanceof AttrResourceValue) { + map = ((AttrResourceValue) attr).getAttributeValues(); + } + } + + if (map != null) { + // accumulator to store the value of the 1+ constants. + int result = 0; + boolean found = false; + + // split the value in case this is a mix of several flags. + String[] keywords = mResourceData[index].getValue().split("\\|"); + for (String keyword : keywords) { + Integer i = map.get(keyword.trim()); + if (i != null) { + result |= i; + found = true; + } + // TODO: We should act smartly and log a warning for incorrect keywords. However, + // this method is currently called even if the resourceValue is not an enum. + } + if (found) { + return result; + } + } + + return null; + } + + static TypedArray obtain(Resources res, int len) { + return res instanceof BridgeResources ? + new BridgeTypedArray(((BridgeResources) res), null, len, true) : null; + } +} diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java index c9d615c..f4a9f52 100644 --- a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java @@ -16,7 +16,13 @@ package android.content.res; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.resources.ResourceType; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.res.Resources.NotFoundException; @@ -25,7 +31,7 @@ import android.util.AttributeSet; import android.util.TypedValue; /** - * Delegate used to provide new implementation of a select few methods of {@link Resources$Theme} + * Delegate used to provide new implementation of a select few methods of {@link Resources.Theme} * * Through the layoutlib_create tool, the original methods of Theme have been replaced * by calls to methods of the same name in this delegate class. @@ -33,11 +39,28 @@ import android.util.TypedValue; */ public class Resources_Theme_Delegate { + // Whether to use the Theme.mThemeResId as primary theme. + boolean force; + + // ---- delegate manager ---- + + private static final DelegateManager<Resources_Theme_Delegate> sManager = + new DelegateManager<Resources_Theme_Delegate>(Resources_Theme_Delegate.class); + + public static DelegateManager<Resources_Theme_Delegate> getDelegateManager() { + return sManager; + } + + // ---- delegate methods. ---- + @LayoutlibDelegate /*package*/ static TypedArray obtainStyledAttributes( Resources thisResources, Theme thisTheme, int[] attrs) { - return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs); + boolean changed = setupResources(thisTheme); + TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs); + restoreResources(changed); + return ta; } @LayoutlibDelegate @@ -45,15 +68,21 @@ public class Resources_Theme_Delegate { Resources thisResources, Theme thisTheme, int resid, int[] attrs) throws NotFoundException { - return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs); + boolean changed = setupResources(thisTheme); + TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs); + restoreResources(changed); + return ta; } @LayoutlibDelegate /*package*/ static TypedArray obtainStyledAttributes( Resources thisResources, Theme thisTheme, AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { - return RenderSessionImpl.getCurrentContext().obtainStyledAttributes( - set, attrs, defStyleAttr, defStyleRes); + boolean changed = setupResources(thisTheme); + TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set, attrs, + defStyleAttr, defStyleRes); + restoreResources(changed); + return ta; } @LayoutlibDelegate @@ -61,7 +90,52 @@ public class Resources_Theme_Delegate { Resources thisResources, Theme thisTheme, int resid, TypedValue outValue, boolean resolveRefs) { - return RenderSessionImpl.getCurrentContext().resolveThemeAttribute( + boolean changed = setupResources(thisTheme); + boolean found = RenderSessionImpl.getCurrentContext().resolveThemeAttribute( resid, outValue, resolveRefs); + restoreResources(changed); + return found; + } + + @LayoutlibDelegate + /*package*/ static TypedArray resolveAttributes(Resources thisResources, Theme thisTheme, + int[] values, int[] attrs) { + // FIXME + return null; + } + + // ---- private helper methods ---- + + private static boolean setupResources(Theme thisTheme) { + Resources_Theme_Delegate themeDelegate = sManager.getDelegate(thisTheme.getNativeTheme()); + StyleResourceValue style = resolveStyle(thisTheme.getAppliedStyleResId()); + if (style != null) { + RenderSessionImpl.getCurrentContext().getRenderResources() + .applyStyle(style, themeDelegate.force); + return true; + } + return false; + } + + private static void restoreResources(boolean changed) { + if (changed) { + RenderSessionImpl.getCurrentContext().getRenderResources().clearStyles(); + } + } + + @Nullable + private static StyleResourceValue resolveStyle(int nativeResid) { + if (nativeResid == 0) { + return null; + } + BridgeContext context = RenderSessionImpl.getCurrentContext(); + ResourceReference theme = context.resolveId(nativeResid); + if (theme.isFramework()) { + return (StyleResourceValue) context.getRenderResources() + .getFrameworkResource(ResourceType.STYLE, theme.getName()); + } else { + return (StyleResourceValue) context.getRenderResources() + .getProjectResource(ResourceType.STYLE, theme.getName()); + } } } diff --git a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java index 0a7899a..faa8852 100644 --- a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java @@ -27,4 +27,9 @@ public class TypedArray_Delegate { // pass return false; } + + @LayoutlibDelegate + /*package*/ static TypedArray obtain(Resources res, int len) { + return BridgeTypedArray.obtain(res, len); + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java index 802cf1c..a4a3b7d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java +++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java @@ -16,16 +16,23 @@ package android.graphics; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; + import java.awt.Font; import java.awt.Graphics2D; +import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.Rectangle2D; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import com.ibm.icu.lang.UScript; import com.ibm.icu.lang.UScriptRun; +import com.ibm.icu.text.Bidi; +import com.ibm.icu.text.BidiRun; import android.graphics.Paint_Delegate.FontInfo; @@ -36,12 +43,12 @@ import android.graphics.Paint_Delegate.FontInfo; @SuppressWarnings("deprecation") public class BidiRenderer { - /* package */ static class ScriptRun { + private static class ScriptRun { int start; int limit; boolean isRtl; int scriptCode; - FontInfo font; + Font font; public ScriptRun(int start, int limit, boolean isRtl) { this.start = start; @@ -51,9 +58,12 @@ public class BidiRenderer { } } - private Graphics2D mGraphics; - private Paint_Delegate mPaint; + private final Graphics2D mGraphics; + private final Paint_Delegate mPaint; private char[] mText; + // This List can contain nulls. A null font implies that the we weren't able to load the font + // properly. So, if we encounter a situation where we try to use that font, log a warning. + private List<Font> mFonts; // Bounds of the text drawn so far. private RectF mBounds; private float mBaseline; @@ -63,18 +73,58 @@ public class BidiRenderer { * @param paint The Paint to use to get the fonts. Should not be null. * @param text Unidirectional text. Should not be null. */ - /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) { + public BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) { assert (paint != null); mGraphics = graphics; mPaint = paint; mText = text; + mFonts = new ArrayList<Font>(paint.getFonts().size()); + for (FontInfo fontInfo : paint.getFonts()) { + if (fontInfo == null) { + mFonts.add(null); + continue; + } + mFonts.add(fontInfo.mFont); + } + mBounds = new RectF(); } /** - * Render unidirectional text. * - * This method can also be used to measure the width of the text without actually drawing it. + * @param x The x-coordinate of the left edge of where the text should be drawn on the given + * graphics. + * @param y The y-coordinate at which to draw the text on the given mGraphics. * + */ + public BidiRenderer setRenderLocation(float x, float y) { + mBounds = new RectF(x, y, x, y); + mBaseline = y; + return this; + } + + /** + * Perform Bidi Analysis on the text and then render it. + * <p/> + * To skip the analysis and render unidirectional text, see {@link + * #renderText(int, int, boolean, float[], int, boolean)} + */ + 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)); + for (int i = 0; i < bidi.countRuns(); i++) { + BidiRun visualRun = bidi.getVisualRun(i); + boolean isRtl = visualRun.getDirection() == Bidi.RTL; + renderText(visualRun.getStart(), visualRun.getLimit(), isRtl, advances, + advancesIndex, draw); + } + return mBounds; + } + + /** + * Render unidirectional text. + * <p/> + * This method can also be used to measure the width of the text without actually drawing it. + * <p/> * @param start index of the first character * @param limit index of the first character that should not be rendered. * @param isRtl is the text right-to-left @@ -83,18 +133,13 @@ public class BidiRenderer { * @param advancesIndex index into advances from where the advances need to be filled. * @param draw If true and {@code graphics} is not null, draw the rendered text on the graphics * at the given co-ordinates - * @param x The x-coordinate of the left edge of where the text should be drawn on the given - * graphics. - * @param y The y-coordinate at which to draw the text on the given mGraphics. * @return A rectangle specifying the bounds of the text drawn. */ - /* package */ RectF renderText(int start, int limit, boolean isRtl, float[] advances, - int advancesIndex, boolean draw, float x, float y) { + public RectF renderText(int start, int limit, boolean isRtl, float[] advances, + int advancesIndex, boolean draw) { // We break the text into scripts and then select font based on it and then render each of // the script runs. - mBounds = new RectF(x, y, x, y); - mBaseline = y; - for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mPaint.getFonts())) { + for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mFonts)) { int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT; flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT; renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw); @@ -108,16 +153,15 @@ public class BidiRenderer { * much as possible. This also implements a fallback mechanism to render characters that cannot * be drawn using the preferred font. */ - private void renderScript(int start, int limit, FontInfo preferredFont, int flag, + private void renderScript(int start, int limit, Font preferredFont, int flag, float[] advances, int advancesIndex, boolean draw) { - List<FontInfo> fonts = mPaint.getFonts(); - if (fonts == null || preferredFont == null) { + if (mFonts.size() == 0 || preferredFont == null) { return; } while (start < limit) { boolean foundFont = false; - int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(mText, start, limit); + int canDisplayUpTo = preferredFont.canDisplayUpTo(mText, start, limit); if (canDisplayUpTo == -1) { // We can draw all characters in the text. render(start, limit, preferredFont, flag, advances, advancesIndex, draw); @@ -133,8 +177,12 @@ public class BidiRenderer { // The current character cannot be drawn with the preferred font. Cycle through all the // fonts to check which one can draw it. int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1; - for (FontInfo font : fonts) { - canDisplayUpTo = font.mFont.canDisplayUpTo(mText, start, start + charCount); + for (Font font : mFonts) { + if (font == null) { + logFontWarning(); + continue; + } + canDisplayUpTo = font.canDisplayUpTo(mText, start, start + charCount); if (canDisplayUpTo == -1) { render(start, start+charCount, font, flag, advances, advancesIndex, draw); start += charCount; @@ -156,19 +204,29 @@ public class BidiRenderer { } } + 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); + } + /** * Renders the text to the right of the bounds with the given font. * @param font The font to render the text with. */ - private void render(int start, int limit, FontInfo font, int flag, float[] advances, + private void render(int start, int limit, Font font, int flag, float[] advances, int advancesIndex, boolean draw) { - // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with - // the anti-aliasing set. - FontRenderContext f = font.mMetrics.getFontRenderContext(); - FontRenderContext frc = new FontRenderContext(f.getTransform(), mPaint.isAntiAliased(), - f.usesFractionalMetrics()); - GlyphVector gv = font.mFont.layoutGlyphVector(frc, mText, start, limit, flag); + FontRenderContext frc; + if (mGraphics != null) { + frc = mGraphics.getFontRenderContext(); + } else { + frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext(); + // Metrics obtained this way don't have anti-aliasing set. So, + // we create a new FontRenderContext with anti-aliasing set. + frc = new FontRenderContext(font.getTransform(), mPaint.isAntiAliased(), 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) { @@ -206,7 +264,7 @@ public class BidiRenderer { } /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit, - boolean isRtl, List<FontInfo> fonts) { + boolean isRtl, List<Font> fonts) { LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>(); int count = limit - start; @@ -225,13 +283,35 @@ public class BidiRenderer { // TODO: Replace this method with one which returns the font based on the scriptCode. private static void setScriptFont(char[] text, ScriptRun run, - List<FontInfo> fonts) { - for (FontInfo fontInfo : fonts) { - if (fontInfo.mFont.canDisplayUpTo(text, run.start, run.limit) == -1) { - run.font = fontInfo; + List<Font> fonts) { + for (Font font : fonts) { + if (font == null) { + logFontWarning(); + continue; + } + if (font.canDisplayUpTo(text, run.start, run.limit) == -1) { + run.font = font; return; } } run.font = fonts.get(0); } + + private static int getIcuFlags(int bidiFlag) { + switch (bidiFlag) { + case Paint.BIDI_LTR: + case Paint.BIDI_FORCE_LTR: + return Bidi.DIRECTION_LEFT_TO_RIGHT; + case Paint.BIDI_RTL: + case Paint.BIDI_FORCE_RTL: + return Bidi.DIRECTION_RIGHT_TO_LEFT; + case Paint.BIDI_DEFAULT_LTR: + return Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; + case Paint.BIDI_DEFAULT_RTL: + return Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT; + default: + assert false; + return Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; + } + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java index 06673c1..9cf777d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java @@ -16,6 +16,7 @@ package android.graphics; +import com.android.annotations.Nullable; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.ninepatch.NinePatchChunk; @@ -48,7 +49,7 @@ import java.util.Set; @LayoutlibDelegate /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage, - Rect padding, Options opts) { + @Nullable Rect padding, @Nullable Options opts) { Bitmap bm = null; Density density = Density.MEDIUM; @@ -77,18 +78,20 @@ import java.util.Set; // put the chunk in the bitmap bm.setNinePatchChunk(NinePatch_Delegate.serialize(chunk)); - // read the padding - int[] paddingarray = chunk.getPadding(); - padding.left = paddingarray[0]; - padding.top = paddingarray[1]; - padding.right = paddingarray[2]; - padding.bottom = paddingarray[3]; + if (padding != null) { + // read the padding + int[] paddingArray = chunk.getPadding(); + padding.left = paddingArray[0]; + padding.top = paddingArray[1]; + padding.right = paddingArray[2]; + padding.bottom = paddingArray[3]; + } } else { // load the bitmap directly. bm = Bitmap_Delegate.createBitmap(is, bitmapCreateFlags, density); } } catch (IOException e) { - Bridge.getLog().error(null,"Failed to load image" , e, null); + Bridge.getLog().error(null, "Failed to load image", e, null); } return bm; diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java index cdbbe46..610c867 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java @@ -79,13 +79,6 @@ public class BitmapShader_Delegate extends Shader_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate(long native_shader, long native_bitmap, - int shaderTileModeX, int shaderTileModeY) { - // pass, not needed. - return 0; - } - // ---- Private delegate/helper methods ---- private BitmapShader_Delegate(java.awt.image.BufferedImage image, diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index 89d7e23..f4282ad 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -53,6 +53,7 @@ import javax.imageio.ImageIO; */ public final class Bitmap_Delegate { + public enum BitmapCreateFlags { PREMULTIPLIED, MUTABLE } @@ -68,6 +69,7 @@ public final class Bitmap_Delegate { private BufferedImage mImage; private boolean mHasAlpha = true; private boolean mHasMipMap = false; // TODO: check the default. + private boolean mIsPremultiplied = true; private int mGenerationId = 0; @@ -315,7 +317,7 @@ public final class Bitmap_Delegate { @LayoutlibDelegate /*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height, - int config, int allocSize) { + int config, int allocSize, boolean isPremultiplied) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Bitmap.reconfigure() is not supported", null /*data*/); } @@ -393,21 +395,19 @@ public final class Bitmap_Delegate { } @LayoutlibDelegate - /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y, - boolean isPremultiplied) { + /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return 0; } - // TODO: Support isPremultiplied. return delegate.mImage.getRGB(x, y); } @LayoutlibDelegate /*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset, - int stride, int x, int y, int width, int height, boolean isPremultiplied) { + int stride, int x, int y, int width, int height) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; @@ -418,8 +418,7 @@ public final class Bitmap_Delegate { @LayoutlibDelegate - /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color, - boolean isPremultiplied) { + /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; @@ -430,7 +429,7 @@ public final class Bitmap_Delegate { @LayoutlibDelegate /*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset, - int stride, int x, int y, int width, int height, boolean isPremultiplied) { + int stride, int x, int y, int width, int height) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; @@ -518,7 +517,27 @@ public final class Bitmap_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha) { + /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + return delegate != null && delegate.mIsPremultiplied; + + } + + @LayoutlibDelegate + /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.mIsPremultiplied = isPremul; + } + + @LayoutlibDelegate + /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha, + boolean isPremul) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index 73d274c..be75dde 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -20,6 +20,7 @@ import com.android.ide.common.rendering.api.LayoutLog; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.layoutlib.bridge.impl.GcSnapshot; +import com.android.layoutlib.bridge.impl.PorterDuffUtility; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.graphics.Bitmap.Config; @@ -55,23 +56,26 @@ public final class Canvas_Delegate { private static final DelegateManager<Canvas_Delegate> sManager = new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class); + // ---- delegate helper data ---- private final static boolean[] sBoolOut = new boolean[1]; + // ---- delegate data ---- private Bitmap_Delegate mBitmap; private GcSnapshot mSnapshot; private DrawFilter_Delegate mDrawFilter = null; + // ---- Public Helper methods ---- /** * Returns the native delegate associated to a given {@link Canvas} object. */ public static Canvas_Delegate getDelegate(Canvas canvas) { - return sManager.getDelegate(canvas.mNativeCanvas); + return sManager.getDelegate(canvas.getNativeCanvasWrapper()); } /** @@ -100,134 +104,82 @@ public final class Canvas_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static boolean isOpaque(Canvas thisCanvas) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { - return false; - } - - return canvasDelegate.mBitmap.getConfig() == Config.RGB_565; + /*package*/ static void freeCaches() { + // nothing to be done here. } @LayoutlibDelegate - /*package*/ static int getWidth(Canvas thisCanvas) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { - return 0; - } - - return canvasDelegate.mBitmap.getImage().getWidth(); + /*package*/ static void freeTextLayoutCaches() { + // nothing to be done here yet. } @LayoutlibDelegate - /*package*/ static int getHeight(Canvas thisCanvas) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { - return 0; - } + /*package*/ static long initRaster(long nativeBitmapOrZero) { + if (nativeBitmapOrZero > 0) { + // get the Bitmap from the int + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero); - return canvasDelegate.mBitmap.getImage().getHeight(); - } + // create a new Canvas_Delegate with the given bitmap and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate); - @LayoutlibDelegate - /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { - return; + return sManager.addNewDelegate(newDelegate); } - canvasDelegate.getSnapshot().translate(dx, dy); + // create a new Canvas_Delegate and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(); + + return sManager.addNewDelegate(newDelegate); } @LayoutlibDelegate - /*package*/ static void rotate(Canvas thisCanvas, float degrees) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { + /*package*/ + static void native_setBitmap(long canvas, long bitmap, boolean copyState) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (canvasDelegate == null || bitmapDelegate==null) { return; } - - canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees)); + canvasDelegate.mBitmap = bitmapDelegate; + canvasDelegate.mSnapshot = GcSnapshot.createDefaultSnapshot(bitmapDelegate); } @LayoutlibDelegate - /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) { + /*package*/ static boolean native_isOpaque(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return; + return false; } - canvasDelegate.getSnapshot().scale(sx, sy); + return canvasDelegate.mBitmap.getConfig() == Config.RGB_565; } @LayoutlibDelegate - /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) { + /*package*/ static int native_getWidth(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return; + return 0; } - // get the current top graphics2D object. - GcSnapshot g = canvasDelegate.getSnapshot(); - - // get its current matrix - AffineTransform currentTx = g.getTransform(); - // get the AffineTransform for the given skew. - float[] mtx = Matrix_Delegate.getSkew(kx, ky); - AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx); - - // combine them so that the given matrix is applied after. - currentTx.preConcatenate(matrixTx); - - // give it to the graphics2D as a new matrix replacing all previous transform - g.setTransform(currentTx); - } - - @LayoutlibDelegate - /*package*/ static boolean clipRect(Canvas thisCanvas, RectF rect) { - return clipRect(thisCanvas, rect.left, rect.top, rect.right, rect.bottom); - } - - @LayoutlibDelegate - /*package*/ static boolean clipRect(Canvas thisCanvas, Rect rect) { - return clipRect(thisCanvas, (float) rect.left, (float) rect.top, - (float) rect.right, (float) rect.bottom); + return canvasDelegate.mBitmap.getImage().getWidth(); } @LayoutlibDelegate - /*package*/ static boolean clipRect(Canvas thisCanvas, float left, float top, float right, - float bottom) { + /*package*/ static int native_getHeight(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return false; + return 0; } - return canvasDelegate.clipRect(left, top, right, bottom, Region.Op.INTERSECT.nativeInt); - } - - @LayoutlibDelegate - /*package*/ static boolean clipRect(Canvas thisCanvas, int left, int top, int right, - int bottom) { - - return clipRect(thisCanvas, (float) left, (float) top, (float) right, (float) bottom); - } - - @LayoutlibDelegate - /*package*/ static int save(Canvas thisCanvas) { - return save(thisCanvas, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG); + return canvasDelegate.mBitmap.getImage().getHeight(); } @LayoutlibDelegate - /*package*/ static int save(Canvas thisCanvas, int saveFlags) { + /*package*/ static int native_save(long nativeCanvas, int saveFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -236,175 +188,126 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void restore(Canvas thisCanvas) { + /*package*/ static int native_saveLayer(long nativeCanvas, float l, + float t, float r, float b, + long paint, int layerFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return; + return 0; } - canvasDelegate.restore(); + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); + if (paintDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayer(new RectF(l, t, r, b), + paintDelegate, layerFlags); } @LayoutlibDelegate - /*package*/ static int getSaveCount(Canvas thisCanvas) { + /*package*/ static int native_saveLayerAlpha(long nativeCanvas, float l, + float t, float r, float b, + int alpha, int layerFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } - return canvasDelegate.getSnapshot().size(); + return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags); } @LayoutlibDelegate - /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) { + /*package*/ static void native_restore(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } - canvasDelegate.restoreTo(saveCount); - } - - @LayoutlibDelegate - /*package*/ static void drawPoints(Canvas thisCanvas, float[] pts, int offset, int count, - Paint paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPoint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - /*package*/ static void drawPoint(Canvas thisCanvas, float x, float y, Paint paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPoint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - /*package*/ static void drawLines(Canvas thisCanvas, - final float[] pts, final int offset, final int count, - Paint paint) { - draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/, - false /*forceSrcMode*/, new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - for (int i = 0 ; i < count ; i += 4) { - graphics.drawLine((int)pts[i + offset], (int)pts[i + offset + 1], - (int)pts[i + offset + 2], (int)pts[i + offset + 3]); - } - } - }); - } - - @LayoutlibDelegate - /*package*/ static void freeCaches() { - // nothing to be done here. - } - - @LayoutlibDelegate - /*package*/ static void freeTextLayoutCaches() { - // nothing to be done here yet. - } - - @LayoutlibDelegate - /*package*/ static long initRaster(long nativeBitmapOrZero) { - if (nativeBitmapOrZero > 0) { - // get the Bitmap from the int - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero); - - // create a new Canvas_Delegate with the given bitmap and return its new native int. - Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate); - - return sManager.addNewDelegate(newDelegate); - } - - // create a new Canvas_Delegate and return its new native int. - Canvas_Delegate newDelegate = new Canvas_Delegate(); - - return sManager.addNewDelegate(newDelegate); + canvasDelegate.restore(); } @LayoutlibDelegate - /*package*/ static void copyNativeCanvasState(long srcCanvas, long dstCanvas) { + /*package*/ static void native_restoreToCount(long nativeCanvas, int saveCount) { // get the delegate from the native int. - Canvas_Delegate srcCanvasDelegate = sManager.getDelegate(srcCanvas); - if (srcCanvasDelegate == null) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { return; } - // get the delegate from the native int. - Canvas_Delegate dstCanvasDelegate = sManager.getDelegate(dstCanvas); - if (dstCanvasDelegate == null) { - return; - } - // TODO: actually copy the canvas state. + canvasDelegate.restoreTo(saveCount); } @LayoutlibDelegate - /*package*/ static long native_saveLayer(long nativeCanvas, RectF bounds, - long paint, int layerFlags) { + /*package*/ static int native_getSaveCount(long nativeCanvas) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); - if (paintDelegate == null) { - return 0; - } - - return canvasDelegate.saveLayer(bounds, paintDelegate, layerFlags); + return canvasDelegate.getSnapshot().size(); } @LayoutlibDelegate - /*package*/ static long native_saveLayer(long nativeCanvas, float l, - float t, float r, float b, - long paint, int layerFlags) { + /*package*/ static void native_translate(long nativeCanvas, float dx, float dy) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return 0; - } - - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); - if (paintDelegate == null) { - return 0; + return; } - return canvasDelegate.saveLayer(new RectF(l, t, r, b), - paintDelegate, layerFlags); + canvasDelegate.getSnapshot().translate(dx, dy); } @LayoutlibDelegate - /*package*/ static long native_saveLayerAlpha(long nativeCanvas, - RectF bounds, int alpha, - int layerFlags) { + /*package*/ static void native_scale(long nativeCanvas, float sx, float sy) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().scale(sx, sy); + } + + @LayoutlibDelegate + /*package*/ static void native_rotate(long nativeCanvas, float degrees) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return 0; + return; } - return canvasDelegate.saveLayerAlpha(bounds, alpha, layerFlags); + canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees)); } @LayoutlibDelegate - /*package*/ static long native_saveLayerAlpha(long nativeCanvas, float l, - float t, float r, float b, - int alpha, int layerFlags) { + /*package*/ static void native_skew(long nativeCanvas, float kx, float ky) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return 0; + return; } - return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags); - } + // get the current top graphics2D object. + GcSnapshot g = canvasDelegate.getSnapshot(); + // get its current matrix + AffineTransform currentTx = g.getTransform(); + // get the AffineTransform for the given skew. + float[] mtx = Matrix_Delegate.getSkew(kx, ky); + AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx); + + // combine them so that the given matrix is applied after. + currentTx.preConcatenate(matrixTx); + + // give it to the graphics2D as a new matrix replacing all previous transform + g.setTransform(currentTx); + } @LayoutlibDelegate /*package*/ static void native_concat(long nCanvas, long nMatrix) { @@ -469,7 +372,6 @@ public final class Canvas_Delegate { float left, float top, float right, float bottom, int regionOp) { - // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); if (canvasDelegate == null) { @@ -568,15 +470,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_quickReject(long nativeCanvas, - RectF rect) { - // FIXME properly implement quickReject - return false; - } - - @LayoutlibDelegate - /*package*/ static boolean native_quickReject(long nativeCanvas, - long path) { + /*package*/ static boolean native_quickReject(long nativeCanvas, long path) { // FIXME properly implement quickReject return false; } @@ -590,24 +484,6 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawRGB(long nativeCanvas, int r, int g, int b) { - native_drawColor(nativeCanvas, 0xFF000000 | r << 16 | (g&0xFF) << 8 | (b&0xFF), - PorterDuff.Mode.SRC_OVER.nativeInt); - - } - - @LayoutlibDelegate - /*package*/ static void native_drawARGB(long nativeCanvas, int a, int r, int g, int b) { - native_drawColor(nativeCanvas, a << 24 | (r&0xFF) << 16 | (g&0xFF) << 8 | (b&0xFF), - PorterDuff.Mode.SRC_OVER.nativeInt); - } - - @LayoutlibDelegate - /*package*/ static void native_drawColor(long nativeCanvas, int color) { - native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt); - } - - @LayoutlibDelegate /*package*/ static void native_drawColor(long nativeCanvas, final int color, final int mode) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); @@ -627,8 +503,8 @@ public final class Canvas_Delegate { // set the color graphics.setColor(new Color(color, true /*alpha*/)); - Composite composite = PorterDuffXfermode_Delegate.getComposite( - PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF); + Composite composite = PorterDuffUtility.getComposite( + PorterDuffUtility.getPorterDuffMode(mode), 0xFF); if (composite != null) { graphics.setComposite(composite); } @@ -646,10 +522,25 @@ public final class Canvas_Delegate { } @LayoutlibDelegate + /*package*/ static void native_drawPoint(long nativeCanvas, float x, float y, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPoints(long nativeCanvas, float[] pts, int offset, int count, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate /*package*/ static void native_drawLine(long nativeCanvas, final float startX, final float startY, final float stopX, final float stopY, long paint) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -660,8 +551,19 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawRect(long nativeCanvas, RectF rect, long paint) { - native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint); + /*package*/ static void native_drawLines(long nativeCanvas, + final float[] pts, final int offset, final int count, + long nativePaint) { + draw(nativeCanvas, nativePaint, false /*compositeOnly*/, + false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override + public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { + for (int i = 0; i < count; i += 4) { + graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1], + (int) pts[i + offset + 2], (int) pts[i + offset + 3]); + } + } + }); } @LayoutlibDelegate @@ -691,8 +593,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawOval(long nativeCanvas, final RectF oval, long paint) { - if (oval.right > oval.left && oval.bottom > oval.top) { + /*package*/ static void native_drawOval(long nativeCanvas, final float left, + final float top, final float right, final float bottom, long paint) { + if (right > left && bottom > top) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -702,14 +605,14 @@ public final class Canvas_Delegate { // draw if (style == Paint.Style.FILL.nativeInt || style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fillOval((int)oval.left, (int)oval.top, - (int)oval.width(), (int)oval.height()); + graphics.fillOval((int)left, (int)top, + (int)(right - left), (int)(bottom - top)); } if (style == Paint.Style.STROKE.nativeInt || style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.drawOval((int)oval.left, (int)oval.top, - (int)oval.width(), (int)oval.height()); + graphics.drawOval((int)left, (int)top, + (int)(right - left), (int)(bottom - top)); } } }); @@ -720,15 +623,16 @@ public final class Canvas_Delegate { /*package*/ static void native_drawCircle(long nativeCanvas, float cx, float cy, float radius, long paint) { native_drawOval(nativeCanvas, - new RectF(cx - radius, cy - radius, cx + radius, cy + radius), + cx - radius, cy - radius, cx + radius, cy + radius, paint); } @LayoutlibDelegate /*package*/ static void native_drawArc(long nativeCanvas, - final RectF oval, final float startAngle, final float sweep, + final float left, final float top, final float right, final float bottom, + final float startAngle, final float sweep, final boolean useCenter, long paint) { - if (oval.right > oval.left && oval.bottom > oval.top) { + if (right > left && bottom > top) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -736,7 +640,7 @@ public final class Canvas_Delegate { int style = paintDelegate.getStyle(); Arc2D.Float arc = new Arc2D.Float( - oval.left, oval.top, oval.width(), oval.height(), + left, top, right - left, bottom - top, -startAngle, -sweep, useCenter ? Arc2D.PIE : Arc2D.OPEN); @@ -757,8 +661,8 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void native_drawRoundRect(long nativeCanvas, - final RectF rect, final float rx, final float ry, long paint) { - + final float left, final float top, final float right, final float bottom, + final float rx, final float ry, long paint) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -769,16 +673,16 @@ public final class Canvas_Delegate { if (style == Paint.Style.FILL.nativeInt || style == Paint.Style.FILL_AND_STROKE.nativeInt) { graphics.fillRoundRect( - (int)rect.left, (int)rect.top, - (int)rect.width(), (int)rect.height(), + (int)left, (int)top, + (int)(right - left), (int)(bottom - top), (int)rx, (int)ry); } if (style == Paint.Style.STROKE.nativeInt || style == Paint.Style.FILL_AND_STROKE.nativeInt) { graphics.drawRoundRect( - (int)rect.left, (int)rect.top, - (int)rect.width(), (int)rect.height(), + (int)left, (int)top, + (int)(right - left), (int)(bottom - top), (int)rx, (int)ry); } } @@ -836,52 +740,18 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, long bitmap, - Rect src, RectF dst, - long nativePaintOrZero, - int screenDensity, - int bitmapDensity) { + float srcLeft, float srcTop, float srcRight, float srcBottom, + float dstLeft, float dstTop, float dstRight, float dstBottom, + long nativePaintOrZero, int screenDensity, int bitmapDensity) { // get the delegate from the native int. Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); if (bitmapDelegate == null) { return; } - BufferedImage image = bitmapDelegate.getImage(); - - if (src == null) { - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - 0, 0, image.getWidth(), image.getHeight(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); - } else { - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - src.left, src.top, src.width(), src.height(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); - } - } - - @LayoutlibDelegate - /*package*/ static void native_drawBitmap(long nativeCanvas, long bitmap, - Rect src, Rect dst, - long nativePaintOrZero, - int screenDensity, - int bitmapDensity) { - // get the delegate from the native int. - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); - if (bitmapDelegate == null) { - return; - } - - BufferedImage image = bitmapDelegate.getImage(); - - if (src == null) { - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - 0, 0, image.getWidth(), image.getHeight(), - dst.left, dst.top, dst.right, dst.bottom); - } else { - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - src.left, src.top, src.width(), src.height(), - dst.left, dst.top, dst.right, dst.bottom); - } + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + (int)srcLeft, (int)srcTop, (int)srcRight, (int)srcBottom, + (int)dstLeft, (int)dstTop, (int)dstRight, (int)dstBottom); } @LayoutlibDelegate @@ -890,7 +760,6 @@ public final class Canvas_Delegate { final float y, int width, int height, boolean hasAlpha, long nativePaintOrZero) { - // create a temp BufferedImage containing the content. final BufferedImage image = new BufferedImage(width, height, hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); @@ -973,82 +842,39 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawText(long nativeCanvas, - final char[] text, final int index, final int count, - final float startX, final float startY, final int flags, long paint) { - - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - // WARNING: the logic in this method is similar to Paint_Delegate.measureText. - // Any change to this method should be reflected in Paint.measureText - // Paint.TextAlign indicates how the text is positioned relative to X. - // LEFT is the default and there's nothing to do. - float x = startX; - int limit = index + count; - boolean isRtl = flags == Canvas.DIRECTION_RTL; - if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { - RectF bounds = paintDelegate.measureText(text, index, count, isRtl); - float m = bounds.right - bounds.left; - if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { - x -= m / 2; - } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { - x -= m; - } - } - - new BidiRenderer(graphics, paintDelegate, text).renderText( - index, limit, isRtl, null, 0, true, x, startY); - } - }); + /*package*/ static void native_drawText(long nativeCanvas, char[] text, int index, int count, + float startX, float startY, int flags, long paint, long typeface) { + drawText(nativeCanvas, text, index, count, startX, startY, flags == Canvas.DIRECTION_RTL, + paint, typeface); } @LayoutlibDelegate /*package*/ static void native_drawText(long nativeCanvas, String text, - int start, int end, float x, float y, final int flags, long paint) { + int start, int end, float x, float y, final int flags, long paint, + long typeface) { int count = end - start; char[] buffer = TemporaryBuffer.obtain(count); TextUtils.getChars(text, start, end, buffer, 0); - native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint); + native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface); } @LayoutlibDelegate /*package*/ static void native_drawTextRun(long nativeCanvas, String text, int start, int end, int contextStart, int contextEnd, - float x, float y, int flags, long paint) { + float x, float y, boolean isRtl, long paint, long typeface) { int count = end - start; char[] buffer = TemporaryBuffer.obtain(count); TextUtils.getChars(text, start, end, buffer, 0); - native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint); + drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface); } @LayoutlibDelegate /*package*/ static void native_drawTextRun(long nativeCanvas, char[] text, int start, int count, int contextStart, int contextCount, - float x, float y, int flags, long paint) { - native_drawText(nativeCanvas, text, start, count, x, y, flags, paint); - } - - @LayoutlibDelegate - /*package*/ static void native_drawPosText(long nativeCanvas, - char[] text, int index, - int count, float[] pos, - long paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPosText is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - /*package*/ static void native_drawPosText(long nativeCanvas, - String text, float[] pos, - long paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPosText is not supported.", null, null /*data*/); + float x, float y, boolean isRtl, long paint, long typeface) { + drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface); } @LayoutlibDelegate @@ -1057,7 +883,7 @@ public final class Canvas_Delegate { int count, long path, float hOffset, float vOffset, int bidiFlags, - long paint) { + long paint, long typeface) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Canvas.drawTextOnPath is not supported.", null, null /*data*/); @@ -1068,7 +894,8 @@ public final class Canvas_Delegate { String text, long path, float hOffset, float vOffset, - int flags, long paint) { + int bidiFlags, long paint, + long typeface) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Canvas.drawTextOnPath is not supported.", null, null /*data*/); @@ -1088,12 +915,13 @@ public final class Canvas_Delegate { sManager.removeJavaReferenceFor(nativeCanvas); } + // ---- Private delegate/helper methods ---- /** * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, int, int)}. + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. */ private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, GcSnapshot.Drawable drawable) { @@ -1113,7 +941,7 @@ public final class Canvas_Delegate { * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, int, int)}. + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. */ private static void draw(long nCanvas, GcSnapshot.Drawable drawable) { // get the delegate from the native int. @@ -1125,6 +953,41 @@ public final class Canvas_Delegate { canvasDelegate.mSnapshot.draw(drawable); } + private static void drawText(long nativeCanvas, final char[] text, final int index, + final int count, final float startX, final float startY, final boolean isRtl, + long paint, final long typeface) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + @Override + public void draw(Graphics2D graphics, Paint_Delegate 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; + int limit = index + count; + if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { + RectF bounds = paintDelegate.measureText(text, index, count, null, 0, + isRtl); + float m = bounds.right - bounds.left; + if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { + x -= m / 2; + } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { + x -= m; + } + } + + new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, startY) + .renderText(index, limit, isRtl, null, 0, true); + } + }); + } + private Canvas_Delegate(Bitmap_Delegate bitmap) { mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); } @@ -1186,12 +1049,6 @@ public final class Canvas_Delegate { return mSnapshot.clipRect(left, top, right, bottom, regionOp); } - private void setBitmap(Bitmap_Delegate bitmap) { - mBitmap = bitmap; - assert mSnapshot.size() == 1; - mSnapshot.setBitmap(mBitmap); - } - private static void drawBitmap( long nativeCanvas, Bitmap_Delegate bitmap, diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java index d6b3da1..bd934d0 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java @@ -19,6 +19,8 @@ package android.graphics; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import java.awt.Graphics2D; + /** * Delegate implementing the native methods of android.graphics.ColorFilter * @@ -50,13 +52,21 @@ public abstract class ColorFilter_Delegate { return sManager.getDelegate(nativeShader); } - public abstract boolean isSupported(); public abstract String getSupportMessage(); + public boolean isSupported() { + return false; + } + + public void applyFilter(Graphics2D g, int width, int height) { + // This should never be called directly. If supported, the sub class should override this. + assert false; + } + // ---- native methods ---- @LayoutlibDelegate - /*package*/ static void finalizer(long native_instance, long nativeColorFilter) { + /*package*/ static void destroyFilter(long native_instance) { sManager.removeJavaReferenceFor(native_instance); } diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java index ca8f450..6739484 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java @@ -43,11 +43,6 @@ public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate { // ---- Public Helper methods ---- @Override - public boolean isSupported() { - return false; - } - - @Override public String getSupportMessage() { return "ColorMatrix Color Filters are not supported."; } @@ -60,11 +55,5 @@ public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nColorMatrixFilter(long nativeFilter, float[] array) { - // pass - return 0; - } - // ---- Private delegate/helper methods ---- } diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java index fae8aef..59ddcc6 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java @@ -78,19 +78,6 @@ public class ComposeShader_Delegate extends Shader_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, long native_mode) { - // pass, not needed. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, int porterDuffMode) { - // pass, not needed. - return 0; - } // ---- Private delegate/helper methods ---- diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java new file mode 100644 index 0000000..bef5181 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.res.AssetManager; + +import java.awt.Font; +import java.awt.FontFormatException; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; +import java.util.Set; + +import static android.graphics.Typeface_Delegate.SYSTEM_FONTS; + +/** + * Delegate implementing the native methods of android.graphics.FontFamily + * + * Through the layoutlib_create tool, the original native methods of FontFamily have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original FontFamily class. + * + * @see DelegateManager + */ +public class FontFamily_Delegate { + + public static final int DEFAULT_FONT_WEIGHT = 400; + public static final int BOLD_FONT_WEIGHT_DELTA = 300; + public static final int BOLD_FONT_WEIGHT = 700; + + // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked + // separately. + private static final String FONT_SUFFIX_ITALIC = "Italic.ttf"; + private static final String FN_ALL_FONTS_LIST = "fontsInSdk.txt"; + + /** + * A class associating {@link Font} with its metadata. + */ + private static final class FontInfo { + @Nullable + Font mFont; + int mWeight; + boolean mIsItalic; + } + + // ---- delegate manager ---- + private static final DelegateManager<FontFamily_Delegate> sManager = + new DelegateManager<FontFamily_Delegate>(FontFamily_Delegate.class); + + // ---- delegate helper data ---- + private static String sFontLocation; + private static final List<FontFamily_Delegate> sPostInitDelegate = new + ArrayList<FontFamily_Delegate>(); + private static Set<String> SDK_FONTS; + + + // ---- delegate data ---- + private List<FontInfo> mFonts = new ArrayList<FontInfo>(); + + /** + * The variant of the Font Family - compact or elegant. + * <p/> + * 0 is unspecified, 1 is compact and 2 is elegant. This needs to be kept in sync with values in + * android.graphics.FontFamily + * + * @see Paint#setElegantTextHeight(boolean) + */ + private FontVariant mVariant; + // List of runnables to process fonts after sFontLoader is initialized. + private List<Runnable> mPostInitRunnables = new ArrayList<Runnable>(); + /** @see #isValid() */ + private boolean mValid = false; + + + // ---- Public helper class ---- + + public enum FontVariant { + // The order needs to be kept in sync with android.graphics.FontFamily. + NONE, COMPACT, ELEGANT + } + + // ---- Public Helper methods ---- + + public static FontFamily_Delegate getDelegate(long nativeFontFamily) { + return sManager.getDelegate(nativeFontFamily); + } + + public static synchronized void setFontLocation(String fontLocation) { + sFontLocation = fontLocation; + // init list of bundled fonts. + 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); + Scanner scanner = null; + try { + scanner = new Scanner(allFonts); + while (scanner.hasNext()) { + String name = scanner.next(); + // Skip font configuration files. + if (!name.endsWith(".xml")) { + allFontsList.add(name); + } + } + } catch (FileNotFoundException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Unable to load the list of fonts. Try re-installing the SDK Platform from the SDK Manager.", + e, null); + } finally { + if (scanner != null) { + scanner.close(); + } + } + SDK_FONTS = Collections.unmodifiableSet(allFontsList); + for (FontFamily_Delegate fontFamily : sPostInitDelegate) { + fontFamily.init(); + } + sPostInitDelegate.clear(); + } + + @Nullable + public Font getFont(int desiredWeight, boolean isItalic) { + FontInfo desiredStyle = new FontInfo(); + desiredStyle.mWeight = desiredWeight; + desiredStyle.mIsItalic = isItalic; + FontInfo bestFont = null; + int bestMatch = Integer.MAX_VALUE; + for (FontInfo font : mFonts) { + int match = computeMatch(font, desiredStyle); + if (match < bestMatch) { + bestMatch = match; + bestFont = font; + } + } + 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); + return desiredStyle.mFont; + } + + public FontVariant getVariant() { + return mVariant; + } + + /** + * Returns if the FontFamily should contain any fonts. If this returns true and + * {@link #getFont(int, boolean)} returns an empty list, it means that an error occurred while + * loading the fonts. However, some fonts are deliberately skipped, for example they are not + * bundled with the SDK. In such a case, this method returns false. + */ + public boolean isValid() { + return mValid; + } + + /*package*/ static Font loadFont(String path) { + if (path.startsWith(SYSTEM_FONTS) ) { + String relativePath = path.substring(SYSTEM_FONTS.length()); + File f = new File(sFontLocation, relativePath); + + try { + return Font.createFont(Font.TRUETYPE_FONT, f); + } catch (Exception e) { + if (path.endsWith(".otf") && e instanceof FontFormatException) { + // If we aren't able to load an Open Type font, don't log a warning just yet. + // We wait for a case where font is being used. Only then we try to log the + // warning. + return null; + } + Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, + String.format("Unable to load font %1$s", relativePath), + e, null); + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Only platform fonts located in " + SYSTEM_FONTS + "can be loaded.", + null, null); + } + + return null; + } + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static long nCreateFamily(String lang, int variant) { + // TODO: support lang. This is required for japanese locale. + FontFamily_Delegate delegate = new FontFamily_Delegate(); + // variant can be 0, 1 or 2. + assert variant < 3; + delegate.mVariant = FontVariant.values()[variant]; + if (sFontLocation != null) { + delegate.init(); + } else { + sPostInitDelegate.add(delegate); + } + return sManager.addNewDelegate(delegate); + } + + @LayoutlibDelegate + /*package*/ static void nUnrefFamily(long nativePtr) { + // Removing the java reference for the object doesn't mean that it's freed for garbage + // collection. Typeface_Delegate may still hold a reference for it. + sManager.removeJavaReferenceFor(nativePtr); + } + + @LayoutlibDelegate + /*package*/ static boolean nAddFont(long nativeFamily, final String path) { + final FontFamily_Delegate delegate = getDelegate(nativeFamily); + if (delegate != null) { + if (sFontLocation == null) { + delegate.mPostInitRunnables.add(new Runnable() { + @Override + public void run() { + delegate.addFont(path); + } + }); + return true; + } + return delegate.addFont(path); + } + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, final String path, + final int weight, final boolean isItalic) { + final FontFamily_Delegate delegate = getDelegate(nativeFamily); + if (delegate != null) { + if (sFontLocation == null) { + delegate.mPostInitRunnables.add(new Runnable() { + @Override + public void run() { + delegate.addFont(path, weight, isItalic); + } + }); + return true; + } + return delegate.addFont(path, weight, isItalic); + } + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "FontFamily.addFontFromAsset is not supported.", null, null); + return false; + } + + + // ---- private helper methods ---- + + private void init() { + for (Runnable postInitRunnable : mPostInitRunnables) { + postInitRunnable.run(); + } + mPostInitRunnables = null; + } + + private boolean addFont(@NonNull String path) { + return addFont(path, DEFAULT_FONT_WEIGHT, path.endsWith(FONT_SUFFIX_ITALIC)); + } + + private boolean addFont(@NonNull String path, int weight, boolean isItalic) { + if (path.startsWith(SYSTEM_FONTS) && + !SDK_FONTS.contains(path.substring(SYSTEM_FONTS.length()))) { + return mValid = false; + } + // Set valid to true, even if the font fails to load. + mValid = true; + Font font = loadFont(path); + if (font == null) { + return false; + } + FontInfo fontInfo = new FontInfo(); + fontInfo.mFont = font; + fontInfo.mWeight = weight; + fontInfo.mIsItalic = isItalic; + addFont(fontInfo); + return true; + } + + 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. + for (FontInfo font : mFonts) { + if (font.mWeight == weight && font.mIsItalic == isItalic) { + return false; + } + } + mFonts.add(fontInfo); + return true; + } + + /** + * Compute matching metric between two styles - 0 is an exact match. + */ + private static int computeMatch(@NonNull FontInfo font1, @NonNull FontInfo font2) { + int score = Math.abs(font1.mWeight - font2.mWeight); + if (font1.mIsItalic != font2.mIsItalic) { + score += 200; + } + return score; + } + + /** + * Try to derive a font from {@code srcFont} for the style in {@code outFont}. + * <p/> + * {@code outFont} is updated to reflect the style of the derived font. + * @param srcFont the source font + * @param outFont contains the desired font style. Updated to contain the derived font and + * its style + * @return outFont + */ + @NonNull + private FontInfo deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) { + int desiredWeight = outFont.mWeight; + int srcWeight = srcFont.mWeight; + Font derivedFont = srcFont.mFont; + // Embolden the font if required. + if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) { + derivedFont = derivedFont.deriveFont(Font.BOLD); + srcWeight += BOLD_FONT_WEIGHT_DELTA; + } + // Italicize the font if required. + if (outFont.mIsItalic && !srcFont.mIsItalic) { + derivedFont = derivedFont.deriveFont(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; + } + outFont.mFont = derivedFont; + outFont.mWeight = srcWeight; + // No need to update mIsItalics, as it's already been handled above. + return outFont; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java index defaac3..0dd9703 100644 --- a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java @@ -43,11 +43,6 @@ public class LightingColorFilter_Delegate extends ColorFilter_Delegate { // ---- Public Helper methods ---- @Override - public boolean isSupported() { - return false; - } - - @Override public String getSupportMessage() { return "Lighting Color Filters are not supported."; } @@ -60,11 +55,5 @@ public class LightingColorFilter_Delegate extends ColorFilter_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static int nCreateLightingFilter(long nativeFilter, int mul, int add) { - // pass - return 0; - } - // ---- Private delegate/helper methods ---- } diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java index ac77377..55c4b98 100644 --- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java @@ -71,22 +71,6 @@ public final class LinearGradient_Delegate extends Gradient_Delegate { tileMode); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(LinearGradient thisGradient, - long native_shader, float x0, float y0, float x1, float y1, - int colors[], float positions[], int tileMode) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(LinearGradient thisGradient, - long native_shader, float x0, float y0, float x1, float y1, - int color0, int color1, int tileMode) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java index e9b5211..1105c7b 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java @@ -203,6 +203,16 @@ public final class Matrix_Delegate { } @LayoutlibDelegate + /*package*/ static boolean native_isAffine(long native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return true; + } + + return (d.computeTypeMask() & kPerspective_Mask) == 0; + } + + @LayoutlibDelegate /*package*/ static boolean native_rectStaysRect(long native_object) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { @@ -355,227 +365,162 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_setConcat(long native_object, long a, long b) { + /*package*/ static void native_setConcat(long native_object, long a, long b) { if (a == native_object) { - return native_preConcat(native_object, b); + native_preConcat(native_object, b); + return; } else if (b == native_object) { - return native_postConcat(native_object, a); + native_postConcat(native_object, a); + return; } Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; - } - Matrix_Delegate a_mtx = sManager.getDelegate(a); - if (a_mtx == null) { - return false; - } - Matrix_Delegate b_mtx = sManager.getDelegate(b); - if (b_mtx == null) { - return false; + if (d != null && a_mtx != null && b_mtx != null) { + multiply(d.mValues, a_mtx.mValues, b_mtx.mValues); } - - multiply(d.mValues, a_mtx.mValues, b_mtx.mValues); - - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preTranslate(long native_object, float dx, float dy) { + /*package*/ static void native_preTranslate(long native_object, float dx, float dy) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getTranslate(dx, dy)); } - - d.preTransform(getTranslate(dx, dy)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preScale(long native_object, float sx, float sy, + /*package*/ static void native_preScale(long native_object, float sx, float sy, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getScale(sx, sy, px, py)); } - - d.preTransform(getScale(sx, sy, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preScale(long native_object, float sx, float sy) { + /*package*/ static void native_preScale(long native_object, float sx, float sy) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getScale(sx, sy)); } - - d.preTransform(getScale(sx, sy)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preRotate(long native_object, float degrees, + /*package*/ static void native_preRotate(long native_object, float degrees, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getRotate(degrees, px, py)); } - - d.preTransform(getRotate(degrees, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preRotate(long native_object, float degrees) { + /*package*/ static void native_preRotate(long native_object, float degrees) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; - } + if (d != null) { - double rad = Math.toRadians(degrees); - float sin = (float)Math.sin(rad); - float cos = (float)Math.cos(rad); + double rad = Math.toRadians(degrees); + float sin = (float) Math.sin(rad); + float cos = (float) Math.cos(rad); - d.preTransform(getRotate(sin, cos)); - return true; + d.preTransform(getRotate(sin, cos)); + } } @LayoutlibDelegate - /*package*/ static boolean native_preSkew(long native_object, float kx, float ky, + /*package*/ static void native_preSkew(long native_object, float kx, float ky, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getSkew(kx, ky, px, py)); } - - d.preTransform(getSkew(kx, ky, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preSkew(long native_object, float kx, float ky) { + /*package*/ static void native_preSkew(long native_object, float kx, float ky) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getSkew(kx, ky)); } - - d.preTransform(getSkew(kx, ky)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preConcat(long native_object, long other_matrix) { + /*package*/ static void native_preConcat(long native_object, long other_matrix) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; - } - Matrix_Delegate other = sManager.getDelegate(other_matrix); - if (other == null) { - return false; + if (d != null && other != null) { + d.preTransform(other.mValues); } - - d.preTransform(other.mValues); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postTranslate(long native_object, float dx, float dy) { + /*package*/ static void native_postTranslate(long native_object, float dx, float dy) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getTranslate(dx, dy)); } - - d.postTransform(getTranslate(dx, dy)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postScale(long native_object, float sx, float sy, + /*package*/ static void native_postScale(long native_object, float sx, float sy, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getScale(sx, sy, px, py)); } - - d.postTransform(getScale(sx, sy, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postScale(long native_object, float sx, float sy) { + /*package*/ static void native_postScale(long native_object, float sx, float sy) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getScale(sx, sy)); } - - d.postTransform(getScale(sx, sy)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postRotate(long native_object, float degrees, + /*package*/ static void native_postRotate(long native_object, float degrees, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getRotate(degrees, px, py)); } - - d.postTransform(getRotate(degrees, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postRotate(long native_object, float degrees) { + /*package*/ static void native_postRotate(long native_object, float degrees) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getRotate(degrees)); } - - d.postTransform(getRotate(degrees)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postSkew(long native_object, float kx, float ky, + /*package*/ static void native_postSkew(long native_object, float kx, float ky, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getSkew(kx, ky, px, py)); } - - d.postTransform(getSkew(kx, ky, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postSkew(long native_object, float kx, float ky) { + /*package*/ static void native_postSkew(long native_object, float kx, float ky) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getSkew(kx, ky)); } - - d.postTransform(getSkew(kx, ky)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postConcat(long native_object, long other_matrix) { + /*package*/ static void native_postConcat(long native_object, long other_matrix) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; - } - Matrix_Delegate other = sManager.getDelegate(other_matrix); - if (other == null) { - return false; + if (d != null && other != null) { + d.postTransform(other.mValues); } - - d.postTransform(other.mValues); - return true; } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java index 74b2893..e16dbda 100644 --- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java @@ -215,9 +215,9 @@ public final class NinePatch_Delegate { if (c == null) { // not a 9-patch? BufferedImage image = bitmap_delegate.getImage(); - Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance, - new Rect(0, 0, image.getWidth(), image.getHeight()), - new Rect(left, top, right, bottom), + Canvas_Delegate.native_drawBitmap(null, canvas_instance, bitmap_instance, + 0f, 0f, (float)image.getWidth(), (float)image.getHeight(), + (float)left, (float)top, (float)right, (float)bottom, paint_instance_or_null, destDensity, srcDensity); return; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 7007b71..7b07404 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.graphics.FontFamily_Delegate.FontVariant; import android.graphics.Paint.FontMetrics; import android.graphics.Paint.FontMetricsInt; import android.text.TextUtils; @@ -30,7 +31,6 @@ import java.awt.Font; import java.awt.Shape; import java.awt.Stroke; import java.awt.Toolkit; -import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.util.ArrayList; import java.util.Collections; @@ -53,7 +53,7 @@ import java.util.Locale; public class Paint_Delegate { /** - * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. + * Class associating a {@link Font} and its {@link java.awt.FontMetrics}. */ /*package*/ static final class FontInfo { Font mFont; @@ -65,9 +65,9 @@ public class Paint_Delegate { new DelegateManager<Paint_Delegate>(Paint_Delegate.class); // ---- delegate helper data ---- + + // This list can contain null elements. private List<FontInfo> mFonts; - private final FontRenderContext mFontContext = new FontRenderContext( - new AffineTransform(), true, true); // ---- delegate data ---- private int mFlags; @@ -83,6 +83,8 @@ public class Paint_Delegate { private float mTextScaleX; private float mTextSkewX; private int mHintingMode = Paint.HINTING_ON; + // Variant of the font. A paint's variant can only be compact or elegant. + private FontVariant mFontVariant = FontVariant.COMPACT; private Xfermode_Delegate mXfermode; private ColorFilter_Delegate mColorFilter; @@ -93,6 +95,8 @@ public class Paint_Delegate { private Locale mLocale = Locale.getDefault(); + // Used only to assert invariants. + public long mNativeTypeface; // ---- Public Helper methods ---- @@ -101,8 +105,7 @@ public class Paint_Delegate { } /** - * Returns the list of {@link Font} objects. The first item is the main font, the rest - * are fall backs for characters not present in the main font. + * Returns the list of {@link Font} objects. */ public List<FontInfo> getFonts() { return mFonts; @@ -420,7 +423,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetShadowLayer(Paint thisPaint, float radius, float dx, float dy, + /*package*/ static void native_setShadowLayer(long paint, float radius, float dx, float dy, int color) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -428,6 +431,32 @@ public class Paint_Delegate { } @LayoutlibDelegate + /*package*/ static boolean native_hasShadowLayer(long paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.hasShadowLayer is not supported.", null, null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean isElegantTextHeight(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT; + } + + @LayoutlibDelegate + /*package*/ static void setElegantTextHeight(Paint thisPaint, boolean elegant) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mFontVariant = elegant ? FontVariant.ELEGANT : FontVariant.COMPACT; + } + + @LayoutlibDelegate /*package*/ static float getTextSize(Paint thisPaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); @@ -575,7 +604,7 @@ public class Paint_Delegate { return 0; } - RectF bounds = delegate.measureText(text, index, count, isRtl(bidiFlags)); + RectF bounds = delegate.measureText(text, index, count, null, 0, bidiFlags); return bounds.right - bounds.left; } @@ -591,11 +620,11 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_breakText(Paint thisPaint, char[] text, int index, int count, - float maxWidth, int bidiFlags, float[] measuredWidth) { + /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, char[] text, + int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; } @@ -603,7 +632,6 @@ public class Paint_Delegate { int inc = count > 0 ? 1 : -1; int measureIndex = 0; - float measureAcc = 0; for (int i = index; i != index + count; i += inc, measureIndex++) { int start, end; if (i < index) { @@ -615,14 +643,13 @@ public class Paint_Delegate { } // measure from start to end - RectF bounds = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags)); + RectF bounds = delegate.measureText(text, start, end - start + 1, null, 0, bidiFlags); float res = bounds.right - bounds.left; if (measuredWidth != null) { measuredWidth[measureIndex] = res; } - measureAcc += res; if (res > maxWidth) { // we should not return this char index, but since it's 0-based // and we need to return a count, we simply return measureIndex; @@ -635,10 +662,11 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_breakText(Paint thisPaint, String text, boolean measureForwards, + /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, String text, + boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth) { - return native_breakText(thisPaint, text.toCharArray(), 0, text.length(), maxWidth, - bidiFlags, measuredWidth); + return native_breakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(), + maxWidth, bidiFlags, measuredWidth); } @LayoutlibDelegate @@ -688,7 +716,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getStyle(long native_object) { + /*package*/ static int native_getStyle(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -710,7 +738,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getStrokeCap(long native_object) { + /*package*/ static int native_getStrokeCap(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -732,7 +760,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getStrokeJoin(long native_object) { + /*package*/ static int native_getStrokeJoin(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -800,10 +828,10 @@ public class Paint_Delegate { return filter; } - delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);; + delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter); - // since none of those are supported, display a fidelity warning right away - if (delegate.mColorFilter != null && delegate.mColorFilter.isSupported() == false) { + // Log warning if it's not supported. + if (delegate.mColorFilter != null && !delegate.mColorFilter.isSupported()) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER, delegate.mColorFilter.getSupportMessage(), null, null /*data*/); } @@ -848,7 +876,7 @@ public class Paint_Delegate { delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter); // since none of those are supported, display a fidelity warning right away - if (delegate.mMaskFilter != null && delegate.mMaskFilter.isSupported() == false) { + if (delegate.mMaskFilter != null && !delegate.mMaskFilter.isSupported()) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, delegate.mMaskFilter.getSupportMessage(), null, null /*data*/); } @@ -865,6 +893,7 @@ public class Paint_Delegate { } delegate.mTypeface = Typeface_Delegate.getDelegate(typeface); + delegate.mNativeTypeface = typeface; delegate.updateFontObject(); return typeface; } @@ -880,7 +909,7 @@ public class Paint_Delegate { delegate.mRasterizer = Rasterizer_Delegate.getDelegate(rasterizer); // since none of those are supported, display a fidelity warning right away - if (delegate.mRasterizer != null && delegate.mRasterizer.isSupported() == false) { + if (delegate.mRasterizer != null && !delegate.mRasterizer.isSupported()) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_RASTERIZER, delegate.mRasterizer.getSupportMessage(), null, null /*data*/); } @@ -889,7 +918,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getTextAlign(long native_object) { + /*package*/ static int native_getTextAlign(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -922,97 +951,81 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getTextWidths(long native_object, char[] text, int index, - int count, int bidiFlags, float[] widths) { + /*package*/ static int native_getTextWidths(long native_object, long native_typeface, + char[] text, int index, int count, int bidiFlags, float[] widths) { + + if (widths != null) { + for (int i = 0; i< count; i++) { + widths[i]=0; + } + } // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { return 0; } - if (delegate.mFonts.size() > 0) { - // FIXME: handle multi-char characters (see measureText) - float totalAdvance = 0; - for (int i = 0; i < count; i++) { - char c = text[i + index]; - boolean found = false; - for (FontInfo info : delegate.mFonts) { - if (info.mFont.canDisplay(c)) { - float adv = info.mMetrics.charWidth(c); - totalAdvance += adv; - if (widths != null) { - widths[i] = adv; - } - - found = true; - break; - } - } - - if (found == false) { - // no advance for this char. - if (widths != null) { - widths[i] = 0.f; - } - } - } - - return (int) totalAdvance; - } + // 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); - return 0; + RectF bounds = delegate.measureText(text, index, count, widths, 0, bidiFlags); + return ((int) (bounds.right - bounds.left)); } @LayoutlibDelegate - /*package*/ static long native_getTextWidths(long native_object, String text, int start, - int end, int bidiFlags, float[] widths) { - return native_getTextWidths(native_object, text.toCharArray(), start, end - start, - bidiFlags, widths); + /*package*/ static int native_getTextWidths(long native_object, long native_typeface, + String text, int start, int end, int bidiFlags, float[] widths) { + return native_getTextWidths(native_object, native_typeface, text.toCharArray(), start, + end - start, bidiFlags, widths); } @LayoutlibDelegate - /* package */static long native_getTextGlyphs(long native_object, String text, int start, + /* package */static int native_getTextGlyphs(long native_object, String text, int start, int end, int contextStart, int contextEnd, int flags, char[] glyphs) { // FIXME return 0; } @LayoutlibDelegate - /*package*/ static float native_getTextRunAdvances(long native_object, + /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface, char[] text, int index, int count, int contextIndex, int contextCount, - int flags, float[] advances, int advancesIndex) { + boolean isRtl, float[] advances, int advancesIndex) { if (advances != null) for (int i = advancesIndex; i< advancesIndex+count; i++) advances[i]=0; // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); - if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { + if (delegate == null) { return 0.f; } - boolean isRtl = isRtl(flags); - int limit = index + count; - RectF bounds = new BidiRenderer(null, delegate, text).renderText( - index, limit, isRtl, advances, advancesIndex, false, 0, 0); + // 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, isRtl); return bounds.right - bounds.left; } @LayoutlibDelegate - /*package*/ static float native_getTextRunAdvances(long native_object, + /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface, String text, int start, int end, int contextStart, int contextEnd, - int flags, float[] advances, int advancesIndex) { + boolean isRtl, 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 native_getTextRunAdvances(native_object, buffer, 0, count, contextStart, - contextEnd - contextStart, flags, advances, advancesIndex); + return native_getTextRunAdvances(native_object, native_typeface, buffer, 0, count, + contextStart, contextEnd - contextStart, isRtl, advances, advancesIndex); } @LayoutlibDelegate - /*package*/ static long native_getTextRunCursor(Paint thisPaint, long native_object, char[] text, + /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, char[] text, int contextStart, int contextLength, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1021,7 +1034,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getTextRunCursor(Paint thisPaint, long native_object, String text, + /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, String text, int contextStart, int contextEnd, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1030,38 +1043,42 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_getTextPath(long native_object, int bidiFlags, - char[] text, int index, int count, float x, float y, long path) { + /*package*/ static void native_getTextPath(long native_object, long native_typeface, + 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 native_getTextPath(long native_object, int bidiFlags, - String text, int start, int end, float x, float y, long path) { + /*package*/ static void native_getTextPath(long native_object, long native_typeface, + 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 nativeGetStringBounds(long nativePaint, String text, int start, - int end, int bidiFlags, Rect bounds) { - nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bidiFlags, - bounds); + /*package*/ static void nativeGetStringBounds(long nativePaint, long native_typeface, + String text, int start, int end, int bidiFlags, Rect bounds) { + nativeGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start, + end - start, bidiFlags, bounds); } @LayoutlibDelegate - /*package*/ static void nativeGetCharArrayBounds(long nativePaint, char[] text, int index, - int count, int bidiFlags, Rect bounds) { + /*package*/ static void nativeGetCharArrayBounds(long nativePaint, long native_typeface, + char[] text, int index, int count, int bidiFlags, Rect bounds) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); - if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { + if (delegate == null) { return; } - delegate.measureText(text, index, count, isRtl(bidiFlags)).roundOut(bounds); + + // 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); } @LayoutlibDelegate @@ -1069,6 +1086,22 @@ public class Paint_Delegate { sManager.removeJavaReferenceFor(nativePaint); } + @LayoutlibDelegate + /*package*/ static float native_getLetterSpacing(long nativePaint) { + // TODO: throw a fidelity warning. + return 0; + } + + @LayoutlibDelegate + /*package*/ static void native_setLetterSpacing(long nativePaint, float letterSpacing) { + // pass. + } + + @LayoutlibDelegate + /*package*/ static void native_setFontFeatureSettings(long nativePaint, String settings) { + // pass. + } + // ---- Private delegate/helper methods ---- /*package*/ Paint_Delegate() { @@ -1087,6 +1120,7 @@ public class Paint_Delegate { mJoin = paint.mJoin; mTextAlign = paint.mTextAlign; mTypeface = paint.mTypeface; + mNativeTypeface = paint.mNativeTypeface; mStrokeWidth = paint.mStrokeWidth; mStrokeMiter = paint.mStrokeMiter; mTextSize = paint.mTextSize; @@ -1110,6 +1144,7 @@ public class Paint_Delegate { mJoin = Paint.Join.MITER.nativeInt; mTextAlign = 0; mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance); + mNativeTypeface = 0; mStrokeWidth = 1.f; mStrokeMiter = 4.f; mTextSize = 20.f; @@ -1132,12 +1167,18 @@ public class Paint_Delegate { private void updateFontObject() { if (mTypeface != null) { // Get the fonts from the TypeFace object. - List<Font> fonts = mTypeface.getFonts(); + List<Font> fonts = mTypeface.getFonts(mFontVariant); // create new font objects as well as FontMetrics, based on the current text size // and skew info. ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); for (Font font : fonts) { + 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) { @@ -1155,9 +1196,16 @@ public class Paint_Delegate { } } - /*package*/ RectF measureText(char[] text, int index, int count, boolean isRtl) { - return new BidiRenderer(null, this, text).renderText( - index, index + count, isRtl, null, 0, false, 0, 0); + /*package*/ RectF measureText(char[] text, int index, int count, float[] advances, + int advancesIndex, int bidiFlags) { + return new BidiRenderer(null, this, text) + .renderText(index, index + count, bidiFlags, advances, advancesIndex, false); + } + + /*package*/ RectF measureText(char[] text, int index, int count, float[] advances, + int advancesIndex, boolean isRtl) { + return new BidiRenderer(null, this, text) + .renderText(index, index + count, isRtl, advances, advancesIndex, false); } private float getFontMetrics(FontMetrics metrics) { @@ -1195,15 +1243,4 @@ public class Paint_Delegate { delegate.mFlags &= ~flagMask; } } - - private static boolean isRtl(int flag) { - switch(flag) { - case Paint.BIDI_RTL: - case Paint.BIDI_FORCE_RTL: - case Paint.BIDI_DEFAULT_RTL: - return true; - default: - return false; - } - } } diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java index 6f6ef20..776398f 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -142,7 +142,14 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getFillType(long nPath) { + /*package*/ static boolean native_isConvex(long nPath) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.isConvex is not supported.", null, null); + return true; + } + + @LayoutlibDelegate + /*package*/ static int native_getFillType(long nPath) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return 0; @@ -290,14 +297,15 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_arcTo(long nPath, RectF oval, + /*package*/ static void native_arcTo(long nPath, float left, float top, float right, + float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return; } - pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo); + pathDelegate.arcTo(left, top, right, bottom, startAngle, sweepAngle, forceMoveTo); } @LayoutlibDelegate @@ -311,16 +319,6 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_addRect(long nPath, RectF rect, int dir) { - Path_Delegate pathDelegate = sManager.getDelegate(nPath); - if (pathDelegate == null) { - return; - } - - pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir); - } - - @LayoutlibDelegate /*package*/ static void native_addRect(long nPath, float left, float top, float right, float bottom, int dir) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); @@ -332,14 +330,15 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_addOval(long nPath, RectF oval, int dir) { + /*package*/ static void native_addOval(long nPath, float left, float top, float right, + float bottom, int dir) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return; } pathDelegate.mPath.append(new Ellipse2D.Float( - oval.left, oval.top, oval.width(), oval.height()), false); + left, top, right - left, bottom - top), false); } @LayoutlibDelegate @@ -355,8 +354,8 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_addArc(long nPath, RectF oval, - float startAngle, float sweepAngle) { + /*package*/ static void native_addArc(long nPath, float left, float top, float right, + float bottom, float startAngle, float sweepAngle) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return; @@ -364,13 +363,13 @@ public final class Path_Delegate { // because x/y is the center of the circle, need to offset this by the radius pathDelegate.mPath.append(new Arc2D.Float( - oval.left, oval.top, oval.width(), oval.height(), + left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN), false); } @LayoutlibDelegate - /*package*/ static void native_addRoundRect( - long nPath, RectF rect, float rx, float ry, int dir) { + /*package*/ static void native_addRoundRect(long nPath, float left, float top, float right, + float bottom, float rx, float ry, int dir) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { @@ -378,14 +377,15 @@ public final class Path_Delegate { } pathDelegate.mPath.append(new RoundRectangle2D.Float( - rect.left, rect.top, rect.width(), rect.height(), rx * 2, ry * 2), false); + left, top, right - left, bottom - top, rx * 2, ry * 2), false); } @LayoutlibDelegate - /*package*/ static void native_addRoundRect(long nPath, RectF rect, float[] radii, int dir) { + /*package*/ static void native_addRoundRect(long nPath, float left, float top, float right, + float bottom, float[] radii, int dir) { // Java2D doesn't support different rounded corners in each corner, so just use the // first value. - native_addRoundRect(nPath, rect, radii[0], radii[1], dir); + native_addRoundRect(nPath, left, top, right, bottom, radii[0], radii[1], dir); // there can be a case where this API is used but with similar values for all corners, so // in that case we don't warn. @@ -484,6 +484,11 @@ public final class Path_Delegate { sManager.removeJavaReferenceFor(nPath); } + @LayoutlibDelegate + /*package*/ static float[] native_approximate(long nPath, float error) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.approximate() not supported", null); + return new float[0]; + } // ---- Private helper methods ---- @@ -603,6 +608,9 @@ public final class Path_Delegate { * @param y The y-coordinate of the end of a line */ private void lineTo(float x, float y) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } mPath.lineTo(mLastX = x, mLastY = y); } @@ -707,14 +715,19 @@ public final class Path_Delegate { * start of the arc. However, if the path is empty, then we call moveTo() * with the first point of the arc. The sweep angle is tread mod 360. * - * @param oval The bounds of oval defining shape and size of the arc + * @param left The left of oval defining shape and size of the arc + * @param top The top of oval defining shape and size of the arc + * @param right The right of oval defining shape and size of the arc + * @param bottom The bottom of oval defining shape and size of the arc * @param startAngle Starting angle (in degrees) where the arc begins * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated * mod 360. * @param forceMoveTo If true, always begin a new contour with the arc */ - private void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) { - Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), -startAngle, + private void arcTo(float left, float top, float right, float bottom, float startAngle, + float sweepAngle, + boolean forceMoveTo) { + Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN); mPath.append(arc, true /*connect*/); diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java index 6049919..4ac376c 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java @@ -19,6 +19,14 @@ package android.graphics; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.graphics.PorterDuff.Mode; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getComposite; +import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode; + /** * Delegate implementing the native methods of android.graphics.PorterDuffColorFilter * @@ -40,32 +48,89 @@ public class PorterDuffColorFilter_Delegate extends ColorFilter_Delegate { // ---- delegate data ---- + private final int mSrcColor; + private final Mode mMode; + + // ---- Public Helper methods ---- @Override public boolean isSupported() { - return false; + return true; } @Override public String getSupportMessage() { - return "PorterDuff Color Filters are not supported."; + return "PorterDuff Color Filter is not supported for mode: " + mMode.name() + "."; + } + + @Override + public void applyFilter(Graphics2D g, int width, int height) { + BufferedImage image = createFilterImage(width, height); + g.setComposite(getComposite(mMode, 0xFF)); + g.drawImage(image, 0, 0, null); } // ---- native methods ---- @LayoutlibDelegate /*package*/ static long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) { - PorterDuffColorFilter_Delegate newDelegate = new PorterDuffColorFilter_Delegate(); + PorterDuffColorFilter_Delegate newDelegate = + new PorterDuffColorFilter_Delegate(srcColor, porterDuffMode); return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nCreatePorterDuffFilter(long nativeFilter, int srcColor, - int porterDuffMode) { - // pass - return 0; - } // ---- Private delegate/helper methods ---- + + private PorterDuffColorFilter_Delegate(int srcColor, int mode) { + mSrcColor = srcColor; + mMode = getCompatibleMode(getPorterDuffMode(mode)); + } + + private BufferedImage createFilterImage(int width, int height) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = image.createGraphics(); + try { + graphics.setColor(new java.awt.Color(mSrcColor, true /* hasAlpha */)); + graphics.fillRect(0, 0, width, height); + } finally { + graphics.dispose(); + } + return image; + } + + // For filtering the colors, the src image should contain the "color" only for pixel values + // which are not transparent in the target image. But, we are using a simple rectangular image + // completely filled with color. Hence some Composite rules do not apply as intended. However, + // in such cases, they can usually be mapped to some other mode, which produces an + // equivalent result. + private Mode getCompatibleMode(Mode mode) { + Mode m = mode; + // Modes that are directly supported: + // CLEAR, DST, SRC_IN, DST_IN, DST_OUT, SRC_ATOP, DARKEN, LIGHTEN, MULTIPLY, SCREEN, + // ADD, OVERLAY + switch (mode) { + // Modes that can be mapped to one of the supported modes. + case SRC: + m = Mode.SRC_IN; + break; + case SRC_OVER: + m = Mode.SRC_ATOP; + break; + case DST_OVER: + m = Mode.DST; + break; + case SRC_OUT: + m = Mode.CLEAR; + break; + case DST_ATOP: + m = Mode.DST_IN; + break; + case XOR: + m = Mode.DST_OUT; + break; + } + return m; + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java index a89fd57..8825f84 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java @@ -16,14 +16,16 @@ package android.graphics; -import com.android.ide.common.rendering.api.LayoutLog; -import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.PorterDuffUtility; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -import java.awt.AlphaComposite; +import android.graphics.PorterDuff.Mode; + import java.awt.Composite; +import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode; + /** * Delegate implementing the native methods of android.graphics.PorterDuffXfermode * @@ -43,17 +45,17 @@ public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { // ---- delegate data ---- - private final int mMode; + private final Mode mMode; // ---- Public Helper methods ---- - public PorterDuff.Mode getMode() { - return getPorterDuffMode(mMode); + public Mode getMode() { + return mMode; } @Override public Composite getComposite(int alpha) { - return getComposite(getPorterDuffMode(mMode), alpha); + return PorterDuffUtility.getComposite(mMode, alpha); } @Override @@ -67,62 +69,6 @@ public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { return null; } - public static PorterDuff.Mode getPorterDuffMode(int mode) { - for (PorterDuff.Mode m : PorterDuff.Mode.values()) { - if (m.nativeInt == mode) { - return m; - } - } - - Bridge.getLog().error(LayoutLog.TAG_BROKEN, - String.format("Unknown PorterDuff.Mode: %d", mode), null /*data*/); - assert false; - return PorterDuff.Mode.SRC_OVER; - } - - public static Composite getComposite(PorterDuff.Mode mode, int alpha) { - float falpha = alpha != 0xFF ? (float)alpha / 255.f : 1.f; - switch (mode) { - case CLEAR: - return AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha); - case DARKEN: - break; - case DST: - return AlphaComposite.getInstance(AlphaComposite.DST, falpha); - case DST_ATOP: - return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha); - case DST_IN: - return AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha); - case DST_OUT: - return AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha); - case DST_OVER: - return AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha); - case LIGHTEN: - break; - case MULTIPLY: - break; - case SCREEN: - break; - case SRC: - return AlphaComposite.getInstance(AlphaComposite.SRC, falpha); - case SRC_ATOP: - return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha); - case SRC_IN: - return AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha); - case SRC_OUT: - return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha); - case SRC_OVER: - return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); - case XOR: - return AlphaComposite.getInstance(AlphaComposite.XOR, falpha); - } - - Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, - String.format("Unsupported PorterDuff Mode: %s", mode.name()), - null, null /*data*/); - - return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); - } // ---- native methods ---- @@ -135,6 +81,7 @@ public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { // ---- Private delegate/helper methods ---- private PorterDuffXfermode_Delegate(int mode) { - mMode = mode; + mMode = getPorterDuffMode(mode); } + } diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java index b640ece..eb29835 100644 --- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java @@ -68,20 +68,6 @@ public class RadialGradient_Delegate extends Gradient_Delegate { tileMode); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, float x, float y, float radius, - int colors[], float positions[], int tileMode) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, float x, float y, float radius, - int color0, int color1, int tileMode) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java index ea23649..edb7025 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java @@ -275,21 +275,20 @@ public class Region_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nativeSetRegion(long native_dst, long native_src) { + /*package*/ static void nativeSetRegion(long native_dst, long native_src) { Region_Delegate dstRegion = sManager.getDelegate(native_dst); if (dstRegion == null) { - return true; + return; } Region_Delegate srcRegion = sManager.getDelegate(native_src); if (srcRegion == null) { - return true; + return; } dstRegion.mArea.reset(); dstRegion.mArea.add(srcRegion.mArea); - return true; } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java index 70a0a43..14e9960 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java @@ -76,13 +76,12 @@ public abstract class Shader_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static void nativeDestructor(long native_shader, long native_skiaShader) { + /*package*/ static void nativeDestructor(long native_shader) { sManager.removeJavaReferenceFor(native_shader); } @LayoutlibDelegate - /*package*/ static void nativeSetLocalMatrix(long native_shader, long native_skiaShader, - long matrix_instance) { + /*package*/ static void nativeSetLocalMatrix(long native_shader, long matrix_instance) { // get the delegate from the native int. Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader); if (shaderDelegate == null) { diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java index f2b3e8d..95a57a9 100644 --- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java @@ -62,20 +62,6 @@ public class SweepGradient_Delegate extends Gradient_Delegate { return nativeCreate1(x, y, new int[] { color0, color1 }, null /*positions*/); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, float cx, float cy, - int[] colors, float[] positions) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, float cx, float cy, - int color0, int color1) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index 60cd157..276e134 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -16,13 +16,11 @@ package android.graphics; -import com.android.ide.common.rendering.api.LayoutLog; -import com.android.layoutlib.bridge.Bridge; +import com.android.annotations.NonNull; import com.android.layoutlib.bridge.impl.DelegateManager; -import com.android.layoutlib.bridge.impl.FontLoader; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -import android.content.res.AssetManager; +import android.graphics.FontFamily_Delegate.FontVariant; import java.awt.Font; import java.io.File; @@ -44,136 +42,133 @@ import java.util.List; */ public final class Typeface_Delegate { - private static final String SYSTEM_FONTS = "/system/fonts/"; + public static final String SYSTEM_FONTS = "/system/fonts/"; // ---- delegate manager ---- private static final DelegateManager<Typeface_Delegate> sManager = new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class); // ---- delegate helper data ---- - private static final String DEFAULT_FAMILY = "sans-serif"; - - private static FontLoader sFontLoader; - private static final List<Typeface_Delegate> sPostInitDelegate = - new ArrayList<Typeface_Delegate>(); + private static String sFontLocation; // ---- delegate data ---- - private final String mFamily; - private int mStyle; - private List<Font> mFonts; + @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; // ---- Public Helper methods ---- - - public static synchronized void init(FontLoader fontLoader) { - sFontLoader = fontLoader; - - for (Typeface_Delegate delegate : sPostInitDelegate) { - delegate.init(); - } - sPostInitDelegate.clear(); + public static synchronized void setFontLocation(String fontLocation) { + sFontLocation = fontLocation; + FontFamily_Delegate.setFontLocation(fontLocation); } public static Typeface_Delegate getDelegate(long nativeTypeface) { return sManager.getDelegate(nativeTypeface); } - public static List<Font> getFonts(Typeface typeface) { - return getFonts(typeface.native_instance); - } - - public static List<Font> getFonts(long native_int) { - Typeface_Delegate delegate = sManager.getDelegate(native_int); - if (delegate == null) { - return null; + /** + * 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 + ((mStyle & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA); + if (weight > 900) { + weight = 900; } - - return delegate.getFonts(); - } - - public List<Font> getFonts() { - return mFonts; + 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; } // ---- native methods ---- @LayoutlibDelegate - /*package*/ static synchronized long nativeCreate(String familyName, int style) { - if (familyName == null) { - familyName = DEFAULT_FAMILY; - } - if (style < 0) { - style = Typeface.NORMAL; + /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) { + Typeface_Delegate delegate = sManager.getDelegate(native_instance); + if (delegate == null) { + delegate = sManager.getDelegate(sDefaultTypeface); } - - Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style); - if (sFontLoader != null) { - newDelegate.init(); - } else { - // font loader has not been initialized yet, add the delegate to a list of delegates - // to init when the font loader is initialized. - // There won't be any rendering before this happens anyway. - sPostInitDelegate.add(newDelegate); + if (delegate == null) { + return 0; } - return sManager.addNewDelegate(newDelegate); + return sManager.addNewDelegate(new Typeface_Delegate(delegate.mFontFamilies, style, + delegate.mWeight)); } @LayoutlibDelegate - /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) { + /*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) { Typeface_Delegate delegate = sManager.getDelegate(native_instance); if (delegate == null) { - return 0; + delegate = sManager.getDelegate(sDefaultTypeface); } - - Typeface_Delegate newDelegate = new Typeface_Delegate(delegate.mFamily, style); - if (sFontLoader != null) { - newDelegate.init(); - } else { - // font loader has not been initialized yet, add the delegate to a list of delegates - // to init when the font loader is initialized. - // There won't be any rendering before this happens anyway. - sPostInitDelegate.add(newDelegate); + if (delegate == null) { + return 0; } - - return sManager.addNewDelegate(newDelegate); + Typeface_Delegate weightAlias = + new Typeface_Delegate(delegate.mFontFamilies, delegate.mStyle, weight); + return sManager.addNewDelegate(weightAlias); } @LayoutlibDelegate - /*package*/ static synchronized long nativeCreateFromAsset(AssetManager mgr, String path) { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Typeface.createFromAsset() is not supported.", null /*throwable*/, null /*data*/); - return 0; - } - - @LayoutlibDelegate - /*package*/ static synchronized long nativeCreateFromFile(String path) { - if (path.startsWith(SYSTEM_FONTS) ) { - String relativePath = path.substring(SYSTEM_FONTS.length()); - File f = new File(sFontLoader.getOsFontsLocation(), relativePath); - - try { - Font font = Font.createFont(Font.TRUETYPE_FONT, f); - if (font != null) { - Typeface_Delegate newDelegate = new Typeface_Delegate(font); - return sManager.addNewDelegate(newDelegate); - } - } catch (Exception e) { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, - String.format("Unable to load font %1$s", relativePath), - null /*throwable*/, null /*data*/); - } - } else { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Typeface.createFromFile() can only work with platform fonts located in " + - SYSTEM_FONTS, - null /*throwable*/, null /*data*/); + /*package*/ static synchronized long nativeCreateFromArray(long[] familyArray) { + FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length]; + for (int i = 0; i < familyArray.length; i++) { + fontFamilies[i] = FontFamily_Delegate.getDelegate(familyArray[i]); } - - - // return a copy of the base font - return nativeCreate(null, 0); + Typeface_Delegate delegate = new Typeface_Delegate(fontFamilies, Typeface.NORMAL); + return sManager.addNewDelegate(delegate); } @LayoutlibDelegate @@ -191,24 +186,25 @@ public final class Typeface_Delegate { return delegate.mStyle; } - // ---- Private delegate/helper methods ---- - - private Typeface_Delegate(String family, int style) { - mFamily = family; - mStyle = style; + @LayoutlibDelegate + /*package*/ static void nativeSetDefault(long native_instance) { + sDefaultTypeface = native_instance; } - private Typeface_Delegate(Font font) { - mFamily = font.getFamily(); - mStyle = Typeface.NORMAL; + @LayoutlibDelegate + /*package*/ static File getSystemFontConfigLocation() { + return new File(sFontLocation); + } - mFonts = sFontLoader.getFallbackFonts(mStyle); + // ---- Private delegate/helper methods ---- - // insert the font glyph first. - mFonts.add(0, font); + private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style) { + this(fontFamilies, style, FontFamily_Delegate.DEFAULT_FONT_WEIGHT); } - private void init() { - mFonts = sFontLoader.getFont(mFamily, mStyle); + public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) { + mFontFamilies = fontFamilies; + mStyle = style; + mWeight = weight; } } diff --git a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java new file mode 100644 index 0000000..af0c456 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.util.Map; + +/** + * Delegate implementing the native methods of android.os.SystemProperties + * + * Through the layoutlib_create tool, the original native methods of SystemProperties have been + * replaced by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + */ +public class SystemProperties_Delegate { + + @LayoutlibDelegate + /*package*/ static String native_get(String key) { + return native_get(key, ""); + } + + @LayoutlibDelegate + /*package*/ static String native_get(String key, String def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(key); + if (value != null) { + return value; + } + + return def; + } + @LayoutlibDelegate + /*package*/ static int native_get_int(String key, int def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(key); + if (value != null) { + return Integer.decode(value); + } + + return def; + } + + @LayoutlibDelegate + /*package*/ static long native_get_long(String key, long def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(key); + if (value != null) { + return Long.decode(value); + } + + return def; + } + + /** + * Values 'n', 'no', '0', 'false' or 'off' are considered false. + * Values 'y', 'yes', '1', 'true' or 'on' are considered true. + */ + @LayoutlibDelegate + /*package*/ static boolean native_get_boolean(String key, boolean def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(key); + + if ("n".equals(value) || "no".equals(value) || "0".equals(value) || "false".equals(value) + || "off".equals(value)) { + return false; + } + //noinspection SimplifiableIfStatement + if ("y".equals(value) || "yes".equals(value) || "1".equals(value) || "true".equals(value) + || "on".equals(value)) { + return true; + } + + return def; + } + + @LayoutlibDelegate + /*package*/ static void native_set(String key, String def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + properties.put(key, def); + } + + @LayoutlibDelegate + /*package*/ static void native_add_change_callback() { + // pass. + } +} diff --git a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java index 973fa0e..6247dae 100644 --- a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java @@ -37,14 +37,17 @@ public class AndroidBidi_Delegate { switch (dir) { case 0: // Layout.DIR_REQUEST_LTR + dir = Bidi.LTR; + break; case 1: // Layout.DIR_REQUEST_RTL - break; // No change. - case -1: - dir = Bidi.LEVEL_DEFAULT_LTR; + dir = Bidi.RTL; break; - case -2: + 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); diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java new file mode 100644 index 0000000..5a467b2 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java @@ -0,0 +1,55 @@ +package android.text; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.text.CharacterIterator; +import java.util.Arrays; +import java.util.Locale; + +import com.ibm.icu.lang.UCharacter; +import com.ibm.icu.text.BreakIterator; +import com.ibm.icu.util.ULocale; +import javax.swing.text.Segment; + +/** + * Delegate that provides implementation for native methods in {@link android.text.StaticLayout} + * + * Through the layoutlib_create tool, selected methods of Handler have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class StaticLayout_Delegate { + + /** + * Fills the recycle array with positions that are suitable to break the text at. The array + * must be terminated by '-1'. + */ + @LayoutlibDelegate + /*package*/ static int[] nLineBreakOpportunities(String locale, char[] text, int length, + int[] recycle) { + BreakIterator iterator = BreakIterator.getLineInstance(new ULocale(locale)); + Segment segment = new Segment(text, 0, length); + iterator.setText(segment); + if (recycle == null) { + // Because 42 is the answer to everything. + recycle = new int[42]; + } + int breakOpp = iterator.first(); + recycle[0] = breakOpp; + //noinspection ConstantConditions + assert BreakIterator.DONE == -1; + for (int i = 1; breakOpp != BreakIterator.DONE; ++i) { + if (i >= recycle.length) { + recycle = doubleSize(recycle); + } + assert (i < recycle.length); + breakOpp = iterator.next(); + recycle[i] = breakOpp; + } + return recycle; + } + + private static int[] doubleSize(int[] array) { + return Arrays.copyOf(array, array.length * 2); + } +} diff --git a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java index 320dd0d..ed8498f 100644 --- a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java @@ -39,91 +39,6 @@ public class Time_Delegate { // Format used by toString() private static final String FORMAT = "%1$tY%1$tm%1$tdT%1$tH%1$tM%1$tS<%1$tZ>"; - @LayoutlibDelegate - /*package*/ static long normalize(Time thisTime, boolean ignoreDst) { - long millis = toMillis(thisTime, ignoreDst); - set(thisTime, millis); - return millis; - } - - @LayoutlibDelegate - /*package*/ static void switchTimezone(Time thisTime, String timezone) { - Calendar c = timeToCalendar(thisTime); - c.setTimeZone(TimeZone.getTimeZone(timezone)); - calendarToTime(c, thisTime); - } - - @LayoutlibDelegate - /*package*/ static int nativeCompare(Time a, Time b) { - return timeToCalendar(a).compareTo(timeToCalendar(b)); - } - - @LayoutlibDelegate - /*package*/ static String format1(Time thisTime, String format) { - - try { - // Change the format by adding changing '%' to "%1$t". This is required to tell the - // formatter which argument to use from the argument list. '%%' is left as is. In the - // replacement string, $0 refers to matched pattern. \\1 means '1', written this way to - // separate it from 0. \\$ means '$', written this way to suppress the special meaning - // of $. - return String.format( - p.matcher(format).replaceAll("$0\\1\\$t"), - timeToCalendar(thisTime)); - } catch (UnknownFormatConversionException e) { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_STRFTIME, "Unrecognized format", e, format); - return format; - } - } - - /** - * Return the current time in YYYYMMDDTHHMMSS<tz> format - */ - @LayoutlibDelegate - /*package*/ static String toString(Time thisTime) { - Calendar c = timeToCalendar(thisTime); - return String.format(FORMAT, c); - } - - @LayoutlibDelegate - /*package*/ static boolean nativeParse(Time thisTime, String s) { - Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, - "android.text.format.Time.parse() not supported.", null); - return false; - } - - @LayoutlibDelegate - /*package*/ static boolean nativeParse3339(Time thisTime, String s) { - Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, - "android.text.format.Time.parse3339() not supported.", null); - return false; - } - - @LayoutlibDelegate - /*package*/ static void setToNow(Time thisTime) { - calendarToTime(getCalendarInstance(thisTime), thisTime); - } - - @LayoutlibDelegate - /*package*/ static long toMillis(Time thisTime, boolean ignoreDst) { - // TODO: Respect ignoreDst. - return timeToCalendar(thisTime).getTimeInMillis(); - } - - @LayoutlibDelegate - /*package*/ static void set(Time thisTime, long millis) { - Calendar c = getCalendarInstance(thisTime); - c.setTimeInMillis(millis); - calendarToTime(c,thisTime); - } - - @LayoutlibDelegate - /*package*/ static String format2445(Time thisTime) { - Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, - "android.text.format.Time.format2445() not supported.", null); - return ""; - } - // ---- private helper methods ---- private static Calendar timeToCalendar(Time time) { diff --git a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java index 6ac5b02..691339e 100644 --- a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java +++ b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java @@ -22,6 +22,7 @@ import com.android.internal.util.XmlUtils; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParser; @@ -210,6 +211,9 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { value = r.getValue(); } + if (value.charAt(0) == '#') { + return ResourceHelper.getColor(value); + } return XmlUtils.convertValueToInt(value, defaultValue); } diff --git a/tools/layoutlib/bridge/src/android/os/Build_Delegate.java b/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java index ff82a5e..a193330 100644 --- a/tools/layoutlib/bridge/src/android/os/Build_Delegate.java +++ b/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,35 +14,36 @@ * limitations under the License. */ -package android.os; +package android.util; -import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -import java.util.Map; +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; /** - * Delegate implementing the native methods of android.os.Build + * Delegate overriding some methods of android.util.Xml * - * Through the layoutlib_create tool, the original native methods of Build have been replaced + * Through the layoutlib_create tool, the original methods of Xml have been replaced * by calls to methods of the same name in this delegate class. * * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} * around to map int to instance of the delegate. - * */ -public class Build_Delegate { +public class Xml_Delegate { @LayoutlibDelegate - /*package*/ static String getString(String property) { - Map<String, String> properties = Bridge.getPlatformProperties(); - String value = properties.get(property); - if (value != null) { - return value; + /*package*/ static XmlPullParser newPullParser() { + try { + KXmlParser parser = new KXmlParser(); + // The prebuilt kxml2 library with the IDE doesn't support DOCECL. +// parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + return parser; + } catch (XmlPullParserException e) { + throw new AssertionError(); } - - return Build.UNKNOWN; } - } diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 941f1ce6..36102f1 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -32,10 +32,6 @@ import org.xmlpull.v1.XmlPullParser; import android.content.Context; import android.util.AttributeSet; -import android.view.InflateException; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import java.io.File; @@ -125,10 +121,11 @@ public final class BridgeInflater extends LayoutInflater { } @Override - public View createViewFromTag(View parent, String name, AttributeSet attrs) { + public View createViewFromTag(View parent, String name, AttributeSet attrs, + boolean inheritContext) { View view = null; try { - view = super.createViewFromTag(parent, name, attrs); + view = super.createViewFromTag(parent, name, attrs, inheritContext); } catch (InflateException e) { // try to load the class from using the custom view loader try { @@ -154,6 +151,9 @@ public final class BridgeInflater extends LayoutInflater { @Override public View inflate(int resource, ViewGroup root) { Context context = getContext(); + while (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } if (context instanceof BridgeContext) { BridgeContext bridgeContext = (BridgeContext)context; @@ -216,43 +216,16 @@ public final class BridgeInflater extends LayoutInflater { } private void setupViewInContext(View view, AttributeSet attrs) { - if (getContext() instanceof BridgeContext) { - BridgeContext bc = (BridgeContext) getContext(); - if (attrs instanceof BridgeXmlBlockParser) { - BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs; - - // get the view key - Object viewKey = parser.getViewCookie(); - - if (viewKey == null) { - int currentDepth = parser.getDepth(); - - // test whether we are in an included file or in a adapter binding view. - BridgeXmlBlockParser previousParser = bc.getPreviousParser(); - if (previousParser != null) { - // looks like we inside an embedded layout. - // only apply the cookie of the calling node (<include>) if we are at the - // top level of the embedded layout. If there is a merge tag, then - // skip it and look for the 2nd level - int testDepth = mIsInMerge ? 2 : 1; - if (currentDepth == testDepth) { - viewKey = previousParser.getViewCookie(); - // if we are in a merge, wrap the cookie in a MergeCookie. - if (viewKey != null && mIsInMerge) { - viewKey = new MergeCookie(viewKey); - } - } - } else if (mResourceReference != null && currentDepth == 1) { - // else if there's a resource reference, this means we are in an adapter - // binding case. Set the resource ref as the view cookie only for the top - // level view. - viewKey = mResourceReference; - } - } - - if (viewKey != null) { - bc.addViewKey(view, viewKey); - } + Context context = getContext(); + while (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } + if (context instanceof BridgeContext) { + BridgeContext bc = (BridgeContext) context; + // get the view key + Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge); + if (viewKey != null) { + bc.addViewKey(view, viewKey); } } } @@ -269,4 +242,44 @@ public final class BridgeInflater extends LayoutInflater { public LayoutInflater cloneInContext(Context newContext) { return new BridgeInflater(this, newContext); } + + /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc, + ResourceReference resourceReference, boolean isInMerge) { + + if (!(attrs instanceof BridgeXmlBlockParser)) { + return null; + } + BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs); + + // get the view key + Object viewKey = parser.getViewCookie(); + + if (viewKey == null) { + int currentDepth = parser.getDepth(); + + // test whether we are in an included file or in a adapter binding view. + BridgeXmlBlockParser previousParser = bc.getPreviousParser(); + if (previousParser != null) { + // looks like we are inside an embedded layout. + // only apply the cookie of the calling node (<include>) if we are at the + // top level of the embedded layout. If there is a merge tag, then + // skip it and look for the 2nd level + int testDepth = isInMerge ? 2 : 1; + if (currentDepth == testDepth) { + viewKey = previousParser.getViewCookie(); + // if we are in a merge, wrap the cookie in a MergeCookie. + if (viewKey != null && isInMerge) { + viewKey = new MergeCookie(viewKey); + } + } + } else if (resourceReference != null && currentDepth == 1) { + // else if there's a resource reference, this means we are in an adapter + // binding case. Set the resource ref as the view cookie only for the top + // level view. + viewKey = resourceReference; + } + } + + return viewKey; + } } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index dd2cbc1..c403ce6 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -23,22 +23,13 @@ import com.android.internal.view.IInputMethodClient; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; -import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.util.DisplayMetrics; -import android.view.Display; -import android.view.Gravity; -import android.view.IApplicationToken; -import android.view.IInputFilter; -import android.view.IOnKeyguardExitResult; -import android.view.IRotationWatcher; -import android.view.IWindowManager; -import android.view.IWindowSession; -import java.util.List; +import java.lang.Override; /** * Basic implementation of {@link IWindowManager} so that {@link Display} (and @@ -81,7 +72,7 @@ public class IWindowManagerImpl implements IWindowManager { @Override public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4, - boolean arg5, boolean arg6, int arg7, int arg8) + boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10) throws RemoteException { // TODO Auto-generated method stub @@ -204,8 +195,8 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public IWindowSession openSession(IInputMethodClient arg0, IInputContext arg1) - throws RemoteException { + public IWindowSession openSession(IWindowSessionCallback argn1, IInputMethodClient arg0, + IInputContext arg1) throws RemoteException { // TODO Auto-generated method stub return null; } @@ -230,6 +221,13 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void overridePendingAppTransitionAspectScaledThumb(Bitmap srcThumb, int startX, + int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback, + boolean scaleUp) { + // TODO Auto-generated method stub + } + + @Override public void pauseKeyDispatching(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub @@ -285,6 +283,11 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public float getCurrentAnimatorScale() throws RemoteException { + return 0; + } + + @Override public void setAppGroupId(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub @@ -364,6 +367,11 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void setScreenCaptureDisabled(int userId, boolean disabled) { + // TODO Auto-generated method stub + } + + @Override public void updateRotation(boolean arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub } @@ -428,11 +436,6 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public boolean waitForWindowDrawn(IBinder token, IRemoteCallback callback) { - return false; - } - - @Override public IBinder asBinder() { // TODO Auto-generated method stub return null; @@ -448,54 +451,41 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void lockNow(Bundle options) { - // TODO Auto-generated method stub - } - - @Override - public boolean isSafeModeEnabled() { - return false; + public void keyguardGoingAway(boolean disableWindowAnimations, + boolean keyguardGoingToNotificationShade) throws RemoteException { } @Override - public IBinder getFocusedWindowToken() { + public void lockNow(Bundle options) { // TODO Auto-generated method stub - return null; } @Override - public void setInputFilter(IInputFilter filter) throws RemoteException { - // TODO Auto-generated method stub + public boolean isSafeModeEnabled() { + return false; } @Override - public void getWindowFrame(IBinder token, Rect outFrame) { + public boolean isRotationFrozen() throws RemoteException { // TODO Auto-generated method stub + return false; } @Override - public void setMagnificationCallbacks(IMagnificationCallbacks callbacks) { + public void enableScreenIfNeeded() throws RemoteException { // TODO Auto-generated method stub } @Override - public void setMagnificationSpec(MagnificationSpec spec) { + public boolean clearWindowContentFrameStats(IBinder token) throws RemoteException { // TODO Auto-generated method stub + return false; } @Override - public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) { + public WindowContentFrameStats getWindowContentFrameStats(IBinder token) + throws RemoteException { // TODO Auto-generated method stub return null; } - - @Override - public boolean isRotationFrozen() throws RemoteException { - // TODO Auto-generated method stub - return false; - } - - @Override - public void setTouchExplorationEnabled(boolean enabled) { - } } diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java index 3db3a1b..7a73fae 100644 --- a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java @@ -48,9 +48,9 @@ public class LayoutInflater_Delegate { * This implementation just records the merge status before calling the default implementation. */ @LayoutlibDelegate - /*package*/ static void rInflate(LayoutInflater thisInflater, - XmlPullParser parser, View parent, final AttributeSet attrs, - boolean finishInflate) throws XmlPullParserException, IOException { + /* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser, + View parent, final AttributeSet attrs, boolean finishInflate, boolean inheritContext) + throws XmlPullParserException, IOException { if (finishInflate == false) { // this is a merge rInflate! @@ -61,7 +61,7 @@ public class LayoutInflater_Delegate { // ---- START DEFAULT IMPLEMENTATION. - thisInflater.rInflate_Original(parser, parent, attrs, finishInflate); + thisInflater.rInflate_Original(parser, parent, attrs, finishInflate, inheritContext); // ---- END DEFAULT IMPLEMENTATION. @@ -74,10 +74,8 @@ public class LayoutInflater_Delegate { } @LayoutlibDelegate - public static void parseInclude( - LayoutInflater thisInflater, - XmlPullParser parser, View parent, AttributeSet attrs) - throws XmlPullParserException, IOException { + public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser, View parent, + AttributeSet attrs, boolean inheritContext) throws XmlPullParserException, IOException { int type; @@ -113,9 +111,11 @@ public class LayoutInflater_Delegate { if (TAG_MERGE.equals(childName)) { // Inflate all children. - thisInflater.rInflate(childParser, parent, childAttrs, false); + thisInflater.rInflate(childParser, parent, childAttrs, false, + inheritContext); } else { - final View view = thisInflater.createViewFromTag(parent, childName, childAttrs); + final View view = thisInflater.createViewFromTag(parent, childName, + childAttrs, inheritContext); final ViewGroup group = (ViewGroup) parent; // We try to load the layout params set in the <include /> tag. If @@ -151,7 +151,7 @@ public class LayoutInflater_Delegate { } // Inflate all children. - thisInflater.rInflate(childParser, view, childAttrs, true); + thisInflater.rInflate(childParser, view, childAttrs, true, true); // Attempt to override the included layout's android:id with the // one set on the <include /> tag itself. diff --git a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java new file mode 100644 index 0000000..dafc96b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.content.Context; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.internal.view.menu.BridgeMenuItemImpl; +import com.android.internal.view.menu.MenuView; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.util.AttributeSet; + +/** + * Delegate used to provide new implementation of a select few methods of {@link MenuInflater} + * <p/> + * Through the layoutlib_create tool, the original methods of MenuInflater have been + * replaced by calls to methods of the same name in this delegate class. + * <p/> + * The main purpose of the class is to get the view key from the menu xml parser and add it to + * the menu item. The view key is used by the IDE to match the individual view elements to the + * corresponding xml tag in the menu/layout file. + * <p/> + * For Menus, the views may be reused and the {@link MenuItem} is a better object to hold the + * view key than the {@link MenuView.ItemView}. At the time of computation of the rest of {@link + * 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) { + if (menuItem instanceof BridgeMenuItemImpl) { + Context context = thisInflater.getContext(); + while (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } + if (context instanceof BridgeContext) { + Object viewKey = BridgeInflater.getViewKeyFromParser( + attrs, ((BridgeContext) context), null, false); + ((BridgeMenuItemImpl) menuItem).setViewCookie(viewKey); + 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); + + } + + @LayoutlibDelegate + /*package*/ static void registerMenu(MenuInflater thisInflater, SubMenu subMenu, + AttributeSet parser) { + registerMenu(thisInflater, subMenu.getItem(), parser); + } + +} diff --git a/tools/layoutlib/bridge/src/android/view/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java index 6aa4b3b..1e7dfbe 100644 --- a/tools/layoutlib/bridge/src/android/view/SurfaceView.java +++ b/tools/layoutlib/bridge/src/android/view/SurfaceView.java @@ -45,6 +45,10 @@ public class SurfaceView extends MockView { super(context, attrs, defStyle); } + public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + public SurfaceHolder getHolder() { return mSurfaceHolder; } diff --git a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java index 1fd7836..d5170aa 100644 --- a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java +++ b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java @@ -16,6 +16,8 @@ package android.view.accessibility; +import com.android.annotations.NonNull; + import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; import android.content.pm.ServiceInfo; @@ -31,14 +33,16 @@ import java.util.List; * for example an {@link android.app.Activity} starts, the focus or selection of a * {@link android.view.View} changes etc. Parties interested in handling accessibility * events implement and register an accessibility service which extends - * {@link android.accessibilityservice.AccessibilityService}. + * {@code android.accessibilityservice.AccessibilityService}. * * @see AccessibilityEvent - * @see android.accessibilityservice.AccessibilityService * @see android.content.Context#getSystemService */ +@SuppressWarnings("UnusedDeclaration") public final class AccessibilityManager { - private static AccessibilityManager sInstance = new AccessibilityManager(); + + private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0); + /** * Listener for the accessibility state. @@ -54,9 +58,46 @@ public final class AccessibilityManager { } /** + * Listener for the system touch exploration state. To listen for changes to + * the touch exploration state on the device, implement this interface and + * register it with the system by calling + * {@link #addTouchExplorationStateChangeListener}. + */ + public interface TouchExplorationStateChangeListener { + + /** + * Called when the touch exploration enabled state changes. + * + * @param enabled Whether touch exploration is enabled. + */ + public void onTouchExplorationStateChanged(boolean enabled); + } + + /** + * Listener for the system high text contrast state. To listen for changes to + * the high text contrast state on the device, implement this interface and + * register it with the system by calling + * {@link #addHighTextContrastStateChangeListener}. + */ + public interface HighTextContrastChangeListener { + + /** + * Called when the high text contrast enabled state changes. + * + * @param enabled Whether high text contrast is enabled. + */ + public void onHighTextContrastStateChanged(boolean enabled); + } + + private final IAccessibilityManagerClient.Stub mClient = + new IAccessibilityManagerClient.Stub() { + public void setState(int state) { + } + }; + + /** * Get an AccessibilityManager instance (create one if necessary). * - * @hide */ public static AccessibilityManager getInstance(Context context) { return sInstance; @@ -67,7 +108,11 @@ public final class AccessibilityManager { * * @param context A {@link Context}. */ - private AccessibilityManager() { + public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { + } + + public IAccessibilityManagerClient getClient() { + return mClient; } /** @@ -80,13 +125,28 @@ public final class AccessibilityManager { } /** - * Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not - * enabled the call is a NOOP. + * Returns if the touch exploration in the system is enabled. * - * @param event The {@link AccessibilityEvent}. + * @return True if touch exploration is enabled, false otherwise. + */ + public boolean isTouchExplorationEnabled() { + return true; + } + + /** + * Returns if the high text contrast in the system is enabled. + * <p> + * <strong>Note:</strong> You need to query this only if you application is + * doing its own rendering and does not rely on the platform rendering pipeline. + * </p> * - * @throws IllegalStateException if a client tries to send an {@link AccessibilityEvent} - * while accessibility is not enabled. + */ + public boolean isHighTextContrastEnabled() { + return false; + } + + /** + * Sends an {@link AccessibilityEvent}. */ public void sendAccessibilityEvent(AccessibilityEvent event) { } @@ -102,20 +162,40 @@ public final class AccessibilityManager { * * @return An unmodifiable list with {@link ServiceInfo}s. */ + @Deprecated public List<ServiceInfo> getAccessibilityServiceList() { - // normal implementation does this in some case, so let's do the same - // (unmodifiableList wrapped around null). - List<ServiceInfo> services = null; - return Collections.unmodifiableList(services); + return Collections.emptyList(); } public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { - // normal implementation does this in some case, so let's do the same - // (unmodifiableList wrapped around null). - List<AccessibilityServiceInfo> services = null; - return Collections.unmodifiableList(services); + return Collections.emptyList(); + } + + /** + * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services + * for a given feedback type. + * + * @param feedbackTypeFlags The feedback type flags. + * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. + * + * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE + * @see AccessibilityServiceInfo#FEEDBACK_GENERIC + * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC + * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN + * @see AccessibilityServiceInfo#FEEDBACK_VISUAL + */ + public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( + int feedbackTypeFlags) { + return Collections.emptyList(); } + /** + * Registers an {@link AccessibilityStateChangeListener} for changes in + * the global accessibility state of the system. + * + * @param listener The listener. + * @return True if successfully registered. + */ public boolean addAccessibilityStateChangeListener( AccessibilityStateChangeListener listener) { return true; @@ -126,6 +206,62 @@ public final class AccessibilityManager { return true; } + /** + * Registers a {@link TouchExplorationStateChangeListener} for changes in + * the global touch exploration state of the system. + * + * @param listener The listener. + * @return True if successfully registered. + */ + public boolean addTouchExplorationStateChangeListener( + @NonNull TouchExplorationStateChangeListener listener) { + return true; + } + + /** + * Unregisters a {@link TouchExplorationStateChangeListener}. + * + * @param listener The listener. + * @return True if successfully unregistered. + */ + public boolean removeTouchExplorationStateChangeListener( + @NonNull TouchExplorationStateChangeListener listener) { + return true; + } + + /** + * Registers a {@link HighTextContrastChangeListener} for changes in + * the global high text contrast state of the system. + * + * @param listener The listener. + * @return True if successfully registered. + * + */ + public boolean addHighTextContrastStateChangeListener( + @NonNull HighTextContrastChangeListener listener) { + return true; + } + + /** + * Unregisters a {@link HighTextContrastChangeListener}. + * + * @param listener The listener. + * @return True if successfully unregistered. + * + */ + public boolean removeHighTextContrastStateChangeListener( + @NonNull HighTextContrastChangeListener listener) { + return true; + } + + /** + * Sets the current state and notifies listeners, if necessary. + * + * @param stateFlags The state flags. + */ + private void setStateLocked(int stateFlags) { + } + public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) { return View.NO_ID; diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java new file mode 100644 index 0000000..8d1d0c1 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view.menu; + +import com.android.layoutlib.bridge.android.BridgeContext; + +import android.content.Context; +import android.view.ContextThemeWrapper; +import android.view.View; + +/** + * An extension of the {@link MenuItemImpl} to store the view cookie also. + */ +public class BridgeMenuItemImpl extends MenuItemImpl { + + /** + * An object returned by the IDE that helps mapping each View to the corresponding XML tag in + * the layout. For Menus, we store this cookie here and attach it to the corresponding view + * at the time of rendering. + */ + private Object viewCookie; + private BridgeContext mContext; + + /** + * Instantiates this menu item. + */ + BridgeMenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering, + CharSequence title, int showAsAction) { + super(menu, group, id, categoryOrder, ordering, title, showAsAction); + Context context = menu.getContext(); + while (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } + if (context instanceof BridgeContext) { + mContext = ((BridgeContext) context); + } + } + + public Object getViewCookie() { + return viewCookie; + } + + public void setViewCookie(Object viewCookie) { + // If the menu item has an associated action provider view, + // directly set the cookie in the view to cookie map stored in BridgeContext. + View actionView = getActionView(); + if (actionView != null && mContext != null) { + mContext.addViewKey(actionView, viewCookie); + // We don't need to add the view cookie to the this item now. But there's no harm in + // storing it, in case we need it in the future. + } + this.viewCookie = viewCookie; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java new file mode 100644 index 0000000..505fb81 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view.menu; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate used to provide new implementation of a select few methods of {@link MenuBuilder} + * <p/> + * Through the layoutlib_create tool, the original methods of {@code MenuBuilder} have been + * replaced by calls to methods of the same name in this delegate class. + */ +public class MenuBuilder_Delegate { + /** + * The method overrides the instantiation of the {@link MenuItemImpl} with an instance of + * {@link BridgeMenuItemImpl} so that view cookies may be stored. + */ + @LayoutlibDelegate + /*package*/ static MenuItemImpl createNewMenuItem(MenuBuilder thisMenu, int group, int id, + int categoryOrder, int ordering, CharSequence title, int defaultShowAsAction) { + return new BridgeMenuItemImpl(thisMenu, group, id, categoryOrder, ordering, title, + defaultShowAsAction); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java b/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java new file mode 100644 index 0000000..40b6220 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import android.widget.ActionMenuPresenter; + +/** + * To access non public members of AbsActionBarView + */ +public class ActionBarAccessor { + + /** + * Returns the {@link ActionMenuPresenter} associated with the {@link AbsActionBarView} + */ + public static ActionMenuPresenter getActionMenuPresenter(AbsActionBarView view) { + return view.mActionMenuPresenter; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index ab4be71..3d0e1e8 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -26,7 +26,6 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; -import com.android.layoutlib.bridge.impl.FontLoader; import com.android.layoutlib.bridge.impl.RenderDrawable; import com.android.layoutlib.bridge.impl.RenderSessionImpl; import com.android.layoutlib.bridge.util.DynamicIdMap; @@ -61,7 +60,7 @@ import java.util.concurrent.locks.ReentrantLock; /** * Main entry point of the LayoutLib Bridge. * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call - * {@link #createScene(SceneParams)} + * {@link #createSession(SessionParams)} */ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { @@ -147,8 +146,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { if (getClass() != obj.getClass()) return false; IntArray other = (IntArray) obj; - if (!Arrays.equals(mArray, other.mArray)) return false; - return true; + return Arrays.equals(mArray, other.mArray); } } @@ -215,7 +213,9 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { Capability.ADAPTER_BINDING, Capability.EXTENDED_VIEWINFO, Capability.FIXED_SCALABLE_NINE_PATCH, - Capability.RTL); + Capability.RTL, + Capability.ACTION_BAR, + Capability.SIMULATE_PLATFORM); BridgeAssetManager.initSystem(); @@ -250,14 +250,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { } // load the fonts. - FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath()); - if (fontLoader != null) { - Typeface_Delegate.init(fontLoader); - } else { - log.error(LayoutLog.TAG_BROKEN, - "Failed create FontLoader in layout lib.", null); - return false; - } + Typeface_Delegate.setFontLocation(fontLocation.getAbsolutePath()); // now parse com.android.internal.R (and only this one as android.R is a subset of // the internal version), and put the content in the maps. @@ -297,7 +290,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { if (log != null) { log.error(LayoutLog.TAG_BROKEN, "Failed to load com.android.internal.R from the layout library jar", - throwable); + throwable, null); } return false; } @@ -425,8 +418,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { locale = ""; } ULocale uLocale = new ULocale(locale); - return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL) ? - true : false; + return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL); } /** diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index f9f4b3a..e0f87fd 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -64,6 +64,11 @@ public class BridgeRenderSession extends RenderSession { } @Override + public List<ViewInfo> getSystemRootViews() { + return mSession.getSystemViewInfos(); + } + + @Override public Map<String, String> getDefaultProperties(Object viewObject) { return mSession.getDefaultProperties(viewObject); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java index 3d50b2a..4a9f718 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java @@ -31,7 +31,11 @@ import android.widget.TextView; public class MockView extends TextView { public MockView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + this(context, attrs, defStyle, 0); + } + + public MockView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); setText(this.getClass().getSimpleName()); setTextColor(0xFF000000); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java new file mode 100644 index 0000000..ea5f1ea --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.android; + +import java.util.Locale; + +import com.ibm.icu.util.ULocale; + +/** + * This class provides an alternate implementation for {@code java.util.Locale#toLanguageTag} + * which is only available after Java 6. + * + * The create tool re-writes references to the above mentioned method to this one. Hence it's + * imperative that this class is not deleted unless the create tool is modified. + */ +@SuppressWarnings("UnusedDeclaration") +public class AndroidLocale { + + public static String toLanguageTag(Locale locale) { + return ULocale.forLocale(locale).toLanguageTag(); + } + + public static String adjustLanguageCode(String languageCode) { + String adjusted = languageCode.toLowerCase(Locale.US); + // Map new language codes to the obsolete language + // codes so the correct resource bundles will be used. + if (languageCode.equals("he")) { + adjusted = "iw"; + } else if (languageCode.equals("id")) { + adjusted = "in"; + } else if (languageCode.equals("yi")) { + adjusted = "ji"; + } + + return adjusted; + } + + public static Locale forLanguageTag(String tag) { + return ULocale.forLanguageTag(tag).toLocale(); + } + + public static String getScript(Locale locale) { + return ULocale.forLocale(locale).getScript(); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java index 01740b1..89288bf 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java @@ -135,6 +135,7 @@ public final class BridgeContentProvider implements IContentProvider { return null; } + @Override public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException { return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index b9294ab..04a52ea 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -16,6 +16,7 @@ package com.android.layoutlib.bridge.android; +import com.android.annotations.Nullable; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; @@ -57,6 +58,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; +import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -100,6 +102,7 @@ public final class BridgeContext extends Context { private final ApplicationInfo mApplicationInfo; private final IProjectCallback mProjectCallback; private final WindowManager mWindowManager; + private final DisplayManager mDisplayManager; private Resources.Theme mTheme; @@ -109,7 +112,7 @@ public final class BridgeContext extends Context { // maps for dynamically generated id representing style objects (StyleResourceValue) private Map<Integer, StyleResourceValue> mDynamicIdToStyleMap; private Map<StyleResourceValue, Integer> mStyleToDynamicIdMap; - private int mDynamicIdGenerator = 0x01030000; // Base id for framework R.style + private int mDynamicIdGenerator = 0x02030000; // Base id for R.style in custom namespace // cache for TypedArray generated from IStyleResourceValue object private Map<int[], Map<Integer, TypedArray>> mTypedArrayCache; @@ -148,6 +151,7 @@ public final class BridgeContext extends Context { } mWindowManager = new WindowManagerImpl(mMetrics); + mDisplayManager = new DisplayManager(this); } /** @@ -315,6 +319,11 @@ public final class BridgeContext extends Context { } } + // The base value for R.style is 0x01030000 and the custom style is 0x02030000. + // So, if the second byte is 03, it's probably a style. + if ((id >> 16 & 0xFF) == 0x03) { + return getStyleByDynamicId(id); + } return null; } @@ -449,13 +458,20 @@ public final class BridgeContext extends Context { return new PowerManager(this, new BridgePowerManager(), new Handler()); } + if (DISPLAY_SERVICE.equals(service)) { + return mDisplayManager; + } + throw new UnsupportedOperationException("Unsupported Service: " + service); } @Override public final TypedArray obtainStyledAttributes(int[] attrs) { - return createStyleBasedTypedArray(mRenderResources.getCurrentTheme(), attrs); + // No style is specified here, so create the typed array based on the default theme + // and the styles already applied to it. A null value of style indicates that the default + // theme should be used. + return createStyleBasedTypedArray(null, attrs); } @Override @@ -551,7 +567,7 @@ public final class BridgeContext extends Context { StyleResourceValue customStyleValues = null; if (customStyle != null) { ResourceValue item = mRenderResources.findResValue(customStyle, - false /*forceFrameworkOnly*/); + isPlatformFile /*forceFrameworkOnly*/); // resolve it in case it links to something else item = mRenderResources.resolveResValue(item); @@ -582,8 +598,7 @@ public final class BridgeContext extends Context { if (item != null) { // item is a reference to a style entry. Search for it. - item = mRenderResources.findResValue(item.getValue(), - false /*forceFrameworkOnly*/); + item = mRenderResources.findResValue(item.getValue(), item.isFramework()); if (item instanceof StyleResourceValue) { defStyleValues = (StyleResourceValue)item; @@ -604,38 +619,36 @@ public final class BridgeContext extends Context { } if (value != null) { - if (value.getFirst() == ResourceType.STYLE) { - // look for the style in the current theme, and its parent: - ResourceValue item = mRenderResources.findItemInTheme(value.getSecond(), + if ((value.getFirst() == ResourceType.STYLE)) { + // look for the style in all resources: + StyleResourceValue item = mRenderResources.getStyle(value.getSecond(), isFrameworkRes); if (item != null) { - if (item instanceof StyleResourceValue) { - if (defaultPropMap != null) { - defaultPropMap.put("style", item.getName()); - } - - defStyleValues = (StyleResourceValue)item; + if (defaultPropMap != null) { + defaultPropMap.put("style", item.getName()); } + + defStyleValues = item; } else { Bridge.getLog().error(null, String.format( "Style with id 0x%x (resolved to '%s') does not exist.", defStyleRes, value.getSecond()), - null /*data*/); + null); } } else { Bridge.getLog().error(null, String.format( - "Resouce id 0x%x is not of type STYLE (instead %s)", + "Resource id 0x%x is not of type STYLE (instead %s)", defStyleRes, value.getFirst().toString()), - null /*data*/); + null); } } else { Bridge.getLog().error(null, String.format( "Failed to find style with id 0x%x in current theme", defStyleRes), - null /*data*/); + null); } } @@ -723,11 +736,13 @@ public final class BridgeContext extends Context { /** * Creates a {@link BridgeTypedArray} by filling the values defined by the int[] with the - * values found in the given style. + * values found in the given style. If no style is specified, the default theme, along with the + * styles applied to it are used. + * * @see #obtainStyledAttributes(int, int[]) */ - private BridgeTypedArray createStyleBasedTypedArray(StyleResourceValue style, int[] attrs) - throws Resources.NotFoundException { + private BridgeTypedArray createStyleBasedTypedArray(@Nullable StyleResourceValue style, + int[] attrs) throws Resources.NotFoundException { List<Pair<String, Boolean>> attributes = searchAttrs(attrs); @@ -740,8 +755,14 @@ public final class BridgeContext extends Context { if (attribute != null) { // look for the value in the given style - ResourceValue resValue = mRenderResources.findItemInStyle(style, - attribute.getFirst(), attribute.getSecond()); + ResourceValue resValue; + if (style != null) { + resValue = mRenderResources.findItemInStyle(style, attribute.getFirst(), + attribute.getSecond()); + } else { + resValue = mRenderResources.findItemInTheme(attribute.getFirst(), + attribute.getSecond()); + } if (resValue != null) { // resolve it to make sure there are no references left. @@ -756,7 +777,6 @@ public final class BridgeContext extends Context { return ta; } - /** * The input int[] attrs is a list of attributes. The returns a list of information about * each attributes. The information is (name, isFramework) @@ -947,6 +967,12 @@ public final class BridgeContext extends Context { } @Override + public Context createApplicationContext(ApplicationInfo application, int flags) + throws PackageManager.NameNotFoundException { + return null; + } + + @Override public boolean deleteDatabase(String arg0) { // pass return false; @@ -1022,6 +1048,12 @@ public final class BridgeContext extends Context { } @Override + public File getCodeCacheDir() { + // pass + return null; + } + + @Override public File getExternalCacheDir() { // pass return null; @@ -1060,6 +1092,12 @@ public final class BridgeContext extends Context { } @Override + public File getNoBackupFilesDir() { + // pass + return null; + } + + @Override public File getExternalFilesDir(String type) { // pass return null; @@ -1260,6 +1298,14 @@ public final class BridgeContext extends Context { } @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, int appOp, BroadcastReceiver resultReceiver, + Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + // pass + } + + @Override public void sendStickyBroadcast(Intent arg0) { // pass @@ -1434,4 +1480,10 @@ public final class BridgeContext extends Context { // pass return new File[0]; } + + @Override + public File[] getExternalMediaDirs() { + // pass + return new File[0]; + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java index 3cf5ed5..c44a57c 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java @@ -135,7 +135,6 @@ public class BridgeIInputMethodManager implements IInputMethodManager { @Override public void setImeWindowStatus(IBinder arg0, int arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub - } @Override @@ -197,18 +196,29 @@ public class BridgeIInputMethodManager implements IInputMethodManager { } @Override - public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException { + public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub return false; } @Override - public boolean shouldOfferSwitchingToNextInputMethod(IBinder arg0) throws RemoteException { + 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 diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java index 281337c..22265a3 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java @@ -28,18 +28,28 @@ import android.os.WorkSource; public class BridgePowerManager implements IPowerManager { @Override - public boolean isScreenOn() throws RemoteException { + public boolean isInteractive() throws RemoteException { return true; } @Override + public boolean isPowerSaveMode() throws RemoteException { + return false; + } + + @Override + public boolean setPowerSaveMode(boolean mode) throws RemoteException { + return false; + } + + @Override public IBinder asBinder() { // pass for now. return null; } @Override - public void acquireWakeLock(IBinder arg0, int arg1, String arg2, String arg2_5, WorkSource arg3) + public void acquireWakeLock(IBinder arg0, int arg1, String arg2, String arg2_5, WorkSource arg3, String arg4) throws RemoteException { // pass for now. } @@ -51,12 +61,17 @@ public class BridgePowerManager implements IPowerManager { } @Override + public void powerHint(int hintId, int data) { + // pass for now. + } + + @Override public void crash(String arg0) throws RemoteException { // pass for now. } @Override - public void goToSleep(long arg0, int arg1) throws RemoteException { + public void goToSleep(long arg0, int arg1, int arg2) throws RemoteException { // pass for now. } @@ -111,7 +126,7 @@ public class BridgePowerManager implements IPowerManager { } @Override - public void updateWakeLockWorkSource(IBinder arg0, WorkSource arg1) throws RemoteException { + public void updateWakeLockWorkSource(IBinder arg0, WorkSource arg1, String arg2) throws RemoteException { // pass for now. } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java index df576d2..997b199 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java @@ -47,8 +47,8 @@ public final class BridgeWindow implements IWindow { } @Override - public void resized(Rect arg1, Rect arg1p5, Rect arg2, Rect arg3, - boolean arg4, Configuration arg5) throws RemoteException { + public void resized(Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, boolean b, + Configuration configuration) throws RemoteException { // pass for now. } @@ -58,11 +58,6 @@ public final class BridgeWindow implements IWindow { } @Override - public void dispatchScreenState(boolean on) throws RemoteException { - // pass for now. - } - - @Override public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException { // pass for now. } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 09e6878..0ed6ab1 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -85,10 +85,11 @@ public final class BridgeWindowSession implements IWindowSession { // pass for now. return false; } + @Override - public int relayout(IWindow arg0, int seq, LayoutParams arg1, int arg2, int arg3, int arg4, - int arg4_5, Rect arg5Z, Rect arg5, Rect arg6, Rect arg7, Configuration arg7b, - Surface arg8) throws RemoteException { + 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, + Configuration configuration, Surface surface) throws RemoteException { // pass for now. return 0; } @@ -173,6 +174,11 @@ public final class BridgeWindowSession implements IWindowSession { } @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. @@ -197,7 +203,7 @@ public final class BridgeWindowSession implements IWindowSession { } @Override - public void onRectangleOnScreenRequested(IBinder window, Rect rectangle, boolean immediate) { + public void onRectangleOnScreenRequested(IBinder window, Rect rectangle) { // pass for now. } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java new file mode 100644 index 0000000..d95c815 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.bars; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.ActionBarCallback; +import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.internal.R; +import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.widget.ActionBarAccessor; +import com.android.internal.widget.ActionBarContainer; +import com.android.internal.widget.ActionBarView; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.resources.ResourceType; + +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.ActionBar.TabListener; +import android.app.FragmentTransaction; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.RelativeLayout; + +import java.util.ArrayList; + +/** + * A layout representing the action bar. + */ +public class ActionBarLayout extends LinearLayout { + + // Store another reference to the context so that we don't have to cast it repeatedly. + @NonNull private final BridgeContext mBridgeContext; + @NonNull private final Context mThemedContext; + + @NonNull private final ActionBar mActionBar; + + // Data for Action Bar. + @Nullable private final String mIcon; + @Nullable private final String mTitle; + @Nullable private final String mSubTitle; + private final boolean mSplit; + private final boolean mShowHomeAsUp; + private final int mNavMode; + + // Helper fields. + @NonNull private final MenuBuilder mMenuBuilder; + private final int mPopupMaxWidth; + @NonNull private final RenderResources res; + @Nullable private final ActionBarView mActionBarView; + @Nullable private FrameLayout mContentRoot; + @NonNull private final ActionBarCallback mCallback; + + // A fake parent for measuring views. + @Nullable private ViewGroup mMeasureParent; + + public ActionBarLayout(@NonNull BridgeContext context, @NonNull SessionParams params) { + + super(context); + setOrientation(LinearLayout.HORIZONTAL); + setGravity(Gravity.CENTER_VERTICAL); + + // Inflate action bar layout. + LayoutInflater.from(context).inflate(R.layout.screen_action_bar, this, + true /*attachToRoot*/); + mActionBar = new WindowDecorActionBar(this); + + // Set contexts. + mBridgeContext = context; + mThemedContext = mActionBar.getThemedContext(); + + // Set data for action bar. + mCallback = params.getProjectCallback().getActionBarCallback(); + mIcon = params.getAppIcon(); + mTitle = params.getAppLabel(); + // Split Action Bar when the screen size is narrow and the application requests split action + // bar when narrow. + mSplit = context.getResources().getBoolean(R.bool.split_action_bar_is_narrow) && + mCallback.getSplitActionBarWhenNarrow(); + mNavMode = mCallback.getNavigationMode(); + // TODO: Support Navigation Drawer Indicator. + mShowHomeAsUp = mCallback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP; + mSubTitle = mCallback.getSubTitle(); + + + // Set helper fields. + mMenuBuilder = new MenuBuilder(mThemedContext); + res = mBridgeContext.getRenderResources(); + mPopupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2, + mThemedContext.getResources().getDimensionPixelSize( + R.dimen.config_prefDialogWidth)); + mActionBarView = (ActionBarView) findViewById(R.id.action_bar); + mContentRoot = (FrameLayout) findViewById(android.R.id.content); + + setupActionBar(); + } + + /** + * Sets up the action bar by filling the appropriate data. + */ + private void setupActionBar() { + // Add title and sub title. + ResourceValue titleValue = res.findResValue(mTitle, false /*isFramework*/); + if (titleValue != null && titleValue.getValue() != null) { + mActionBar.setTitle(titleValue.getValue()); + } else { + mActionBar.setTitle(mTitle); + } + if (mSubTitle != null) { + mActionBar.setSubtitle(mSubTitle); + } + + // Add show home as up icon. + if (mShowHomeAsUp) { + mActionBar.setDisplayOptions(0xFF, ActionBar.DISPLAY_HOME_AS_UP); + } + + // Set the navigation mode. + mActionBar.setNavigationMode(mNavMode); + if (mNavMode == ActionBar.NAVIGATION_MODE_TABS) { + setupTabs(3); + } + + if (mActionBarView != null) { + // If the action bar style doesn't specify an icon, set the icon obtained from the session + // params. + if (!mActionBarView.hasIcon() && mIcon != null) { + Drawable iconDrawable = getDrawable(mIcon, false /*isFramework*/); + if (iconDrawable != null) { + mActionBar.setIcon(iconDrawable); + } + } + + // Set action bar to be split, if needed. + ActionBarContainer splitView = (ActionBarContainer) findViewById(R.id.split_action_bar); + mActionBarView.setSplitView(splitView); + mActionBarView.setSplitToolbar(mSplit); + + inflateMenus(); + } + } + + /** + * Gets the menus to add to the action bar from the callback, resolves them, inflates them and + * adds them to the action bar. + */ + private void inflateMenus() { + if (mActionBarView == null) { + return; + } + final MenuInflater inflater = new MenuInflater(mThemedContext); + for (String name : mCallback.getMenuIdNames()) { + if (mBridgeContext.getRenderResources().getProjectResource(ResourceType.MENU, name) + != null) { + int id = mBridgeContext.getProjectResourceValue(ResourceType.MENU, name, -1); + if (id > -1) { + inflater.inflate(id, mMenuBuilder); + } + } + } + mActionBarView.setMenu(mMenuBuilder, null /*callback*/); + } + + // TODO: Use an adapter, like List View to set up tabs. + private void setupTabs(int num) { + for (int i = 1; i <= num; i++) { + Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() { + @Override + public void onTabUnselected(Tab t, FragmentTransaction ft) { + // pass + } + @Override + public void onTabSelected(Tab t, FragmentTransaction ft) { + // pass + } + @Override + public void onTabReselected(Tab t, FragmentTransaction ft) { + // pass + } + }); + mActionBar.addTab(tab); + } + } + + @Nullable + private Drawable getDrawable(@NonNull String name, boolean isFramework) { + ResourceValue value = res.findResValue(name, isFramework); + value = res.resolveResValue(value); + if (value != null) { + return ResourceHelper.getDrawable(value, mBridgeContext); + } + return null; + } + + /** + * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to + * the content frame which shall serve as the new content root. + */ + public void createMenuPopup() { + assert mContentRoot != null && findViewById(android.R.id.content) == mContentRoot + : "Action Bar Menus have already been created."; + + if (!isOverflowPopupNeeded()) { + return; + } + + // Create a layout to hold the menus and the user's content. + RelativeLayout layout = new RelativeLayout(mThemedContext); + layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT)); + mContentRoot.addView(layout); + // Create a layout for the user's content. + FrameLayout contentRoot = new FrameLayout(mBridgeContext); + contentRoot.setLayoutParams(new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + // Add contentRoot and menus to the layout. + layout.addView(contentRoot); + layout.addView(createMenuView()); + // ContentRoot is now the view we just created. + mContentRoot = contentRoot; + } + + /** + * Returns a {@link LinearLayout} containing the menu list view to be embedded in a + * {@link RelativeLayout} + */ + @NonNull + private View createMenuView() { + DisplayMetrics metrics = mBridgeContext.getMetrics(); + OverflowMenuAdapter adapter = new OverflowMenuAdapter(mMenuBuilder, mThemedContext); + + LinearLayout layout = new LinearLayout(mThemedContext); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + measureContentWidth(adapter), LayoutParams.WRAP_CONTENT); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END); + if (mSplit) { + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + // TODO: Find correct value instead of hardcoded 10dp. + layoutParams.bottomMargin = getPixelValue("-10dp", metrics); + } else { + layoutParams.topMargin = getPixelValue("-10dp", metrics); + } + layout.setLayoutParams(layoutParams); + final TypedArray a = mThemedContext.obtainStyledAttributes(null, + R.styleable.PopupWindow, R.attr.popupMenuStyle, 0); + layout.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground)); + layout.setDividerDrawable(a.getDrawable(R.attr.actionBarDivider)); + a.recycle(); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setDividerPadding(getPixelValue("12dp", metrics)); + layout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); + + ListView listView = new ListView(mThemedContext, null, R.attr.dropDownListViewStyle); + listView.setAdapter(adapter); + layout.addView(listView); + return layout; + } + + private boolean isOverflowPopupNeeded() { + boolean needed = mCallback.isOverflowPopupNeeded(); + if (!needed) { + return false; + } + // Copied from android.widget.ActionMenuPresenter.updateMenuView() + ArrayList<MenuItemImpl> menus = mMenuBuilder.getNonActionItems(); + if (ActionBarAccessor.getActionMenuPresenter(mActionBarView).isOverflowReserved() && + menus != null) { + final int count = menus.size(); + if (count == 1) { + needed = !menus.get(0).isActionViewExpanded(); + } else { + needed = count > 0; + } + } + return needed; + } + + @Nullable + public FrameLayout getContentRoot() { + return mContentRoot; + } + + // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth() + private int measureContentWidth(@NonNull ListAdapter adapter) { + // Menus don't tend to be long, so this is more sane than it looks. + int maxWidth = 0; + View itemView = null; + int itemType = 0; + + final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int count = adapter.getCount(); + for (int i = 0; i < count; i++) { + final int positionType = adapter.getItemViewType(i); + if (positionType != itemType) { + itemType = positionType; + itemView = null; + } + + if (mMeasureParent == null) { + mMeasureParent = new FrameLayout(mThemedContext); + } + + itemView = adapter.getView(i, itemView, mMeasureParent); + itemView.measure(widthMeasureSpec, heightMeasureSpec); + + final int itemWidth = itemView.getMeasuredWidth(); + if (itemWidth >= mPopupMaxWidth) { + return mPopupMaxWidth; + } else if (itemWidth > maxWidth) { + maxWidth = itemWidth; + } + } + + return maxWidth; + } + + private int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) { + TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/); + return (int) typedValue.getDimension(metrics); + } + +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java new file mode 100644 index 0000000..82a5130 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.bars; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static android.os.Build.VERSION_CODES.*; + +/** + * Various helper methods to simulate older versions of platform. + */ +public class Config { + + // each of these resource dirs must end in '/' + private static final String GINGERBREAD_DIR = "/bars/v9/"; + private static final String JELLYBEAN_DIR = "/bars/v18/"; + private static final String KITKAT_DIR = "/bars/v19/"; + private static final String DEFAULT_RESOURCE_DIR = "/bars/v21/"; + + private static final List<String> sDefaultResourceDir = + Collections.singletonList(DEFAULT_RESOURCE_DIR); + + private static final int WHITE = 0xFFFFFFFF; + private static final int BLACK = 0xFF000000; + + public static boolean showOnScreenNavBar(int platformVersion) { + return platformVersion == 0 || platformVersion >= ICE_CREAM_SANDWICH; + } + + public static int getStatusBarColor(int platformVersion) { + // return white for froyo and earlier; black otherwise. + return platformVersion == 0 || platformVersion >= GINGERBREAD ? BLACK : WHITE; + } + + public static List<String> getResourceDirs(int platformVersion) { + // Special case the most used scenario. + if (platformVersion == 0) { + return sDefaultResourceDir; + } + List<String> list = new ArrayList<String>(4); + // Gingerbread - uses custom battery and wifi icons. + if (platformVersion <= GINGERBREAD) { + list.add(GINGERBREAD_DIR); + } + // ICS - JellyBean uses custom battery, wifi. + if (platformVersion <= JELLY_BEAN_MR2) { + list.add(JELLYBEAN_DIR); + } + // KitKat - uses custom wifi and nav icons. + if (platformVersion <= KITKAT) { + list.add(KITKAT_DIR); + } + list.add(DEFAULT_RESOURCE_DIR); + + return Collections.unmodifiableList(list); + } + + public static String getTime(int platformVersion) { + if (platformVersion == 0) { + return "5:00"; + } + if (platformVersion < GINGERBREAD) { + return "2:20"; + } + if (platformVersion < ICE_CREAM_SANDWICH) { + return "2:30"; + } + if (platformVersion < JELLY_BEAN) { + return "4:00"; + } + if (platformVersion < KITKAT) { + return "4:30"; + } + if (platformVersion <= KITKAT_WATCH) { + return "4:40"; + } + // Should never happen. + return "4:04"; + } + + public static int getTimeColor(int platformVersion) { + if (platformVersion == 0 || platformVersion >= KITKAT || + platformVersion > FROYO && platformVersion < HONEYCOMB) { + // Gingerbread and KitKat onwards. + return WHITE; + } + // Black for froyo. + if (platformVersion < GINGERBREAD) { + return BLACK; + } else if (platformVersion < KITKAT) { + // Honeycomb to JB-mr2: Holo blue light. + return 0xff33b5e5; + } + // Should never happen. + return WHITE; + } + + public static String getWifiIconType(int platformVersion) { + return platformVersion == 0 ? "xml" : "png"; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index bcd08eb4..13ddf07 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -26,7 +26,6 @@ import com.android.layoutlib.bridge.impl.ParserFactory; import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.resources.Density; import com.android.resources.LayoutDirection; -import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -59,11 +58,15 @@ import java.io.InputStream; */ abstract class CustomBar extends LinearLayout { + + private final int mSimulatedPlatformVersion; + protected abstract TextView getStyleableTextView(); - protected CustomBar(Context context, Density density, int orientation, String layoutPath, - String name) throws XmlPullParserException { + protected CustomBar(Context context, int orientation, String layoutPath, + String name, int simulatedPlatformVersion) throws XmlPullParserException { super(context); + mSimulatedPlatformVersion = simulatedPlatformVersion; setOrientation(orientation); if (orientation == LinearLayout.HORIZONTAL) { setGravity(Gravity.CENTER_VERTICAL); @@ -87,40 +90,6 @@ abstract class CustomBar extends LinearLayout { } } - private InputStream getIcon(String iconName, Density[] densityInOut, LayoutDirection direction, - String[] pathOut, boolean tryOtherDensities) { - // current density - Density density = densityInOut[0]; - - // bitmap url relative to this class - if (direction != null) { - pathOut[0] = "/bars/" + direction.getResourceValue() + "-" + density.getResourceValue() - + "/" + iconName; - } else { - pathOut[0] = "/bars/" + density.getResourceValue() + "/" + iconName; - } - - InputStream stream = getClass().getResourceAsStream(pathOut[0]); - if (stream == null && tryOtherDensities) { - for (Density d : Density.values()) { - if (d != density) { - densityInOut[0] = d; - stream = getIcon(iconName, densityInOut, direction, pathOut, - false /*tryOtherDensities*/); - if (stream != null) { - return stream; - } - } - } - // couldn't find resource with direction qualifier. try without. - if (direction != null) { - return getIcon(iconName, densityInOut, null, pathOut, true); - } - } - - return stream; - } - protected void loadIcon(int index, String iconName, Density density) { loadIcon(index, iconName, density, false); } @@ -130,20 +99,20 @@ abstract class CustomBar extends LinearLayout { if (child instanceof ImageView) { ImageView imageView = (ImageView) child; - String[] pathOut = new String[1]; - Density[] densityInOut = new Density[] { density }; - LayoutDirection dir = isRtl ? LayoutDirection.RTL : LayoutDirection.LTR; - InputStream stream = getIcon(iconName, densityInOut, dir, pathOut, - true /*tryOtherDensities*/); - density = densityInOut[0]; + LayoutDirection dir = isRtl ? LayoutDirection.RTL : null; + IconLoader iconLoader = new IconLoader(iconName, density, mSimulatedPlatformVersion, + dir); + InputStream stream = iconLoader.getIcon(); if (stream != null) { + density = iconLoader.getDensity(); + String path = iconLoader.getPath(); // look for a cached bitmap - Bitmap bitmap = Bridge.getCachedBitmap(pathOut[0], true /*isFramework*/); + Bitmap bitmap = Bridge.getCachedBitmap(path, true /*isFramework*/); if (bitmap == null) { try { bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density); - Bridge.setCachedBitmap(pathOut[0], bitmap, true /*isFramework*/); + Bridge.setCachedBitmap(path, bitmap, true /*isFramework*/); } catch (IOException e) { return; } @@ -158,94 +127,25 @@ abstract class CustomBar extends LinearLayout { } } - protected void loadIcon(int index, String iconReference) { - ResourceValue value = getResourceValue(iconReference); - if (value != null) { - loadIcon(index, value); - } - } - - protected void loadIconById(int id, String iconReference) { - ResourceValue value = getResourceValue(iconReference); - if (value != null) { - loadIconById(id, value); - } - } - - - protected Drawable loadIcon(int index, ResourceType type, String name) { - BridgeContext bridgeContext = (BridgeContext) mContext; - RenderResources res = bridgeContext.getRenderResources(); - - // find the resource - ResourceValue value = res.getFrameworkResource(type, name); - - // resolve it if needed - value = res.resolveResValue(value); - return loadIcon(index, value); - } - - private Drawable loadIcon(int index, ResourceValue value) { - View child = getChildAt(index); - if (child instanceof ImageView) { - ImageView imageView = (ImageView) child; - - return loadIcon(imageView, value); - } - - return null; - } - - private Drawable loadIconById(int id, ResourceValue value) { - View child = findViewById(id); - if (child instanceof ImageView) { - ImageView imageView = (ImageView) child; - - return loadIcon(imageView, value); - } - - return null; - } - - - private Drawable loadIcon(ImageView imageView, ResourceValue value) { - Drawable drawable = ResourceHelper.getDrawable(value, (BridgeContext) mContext); - if (drawable != null) { - imageView.setImageDrawable(drawable); - } - - return drawable; - } - - protected TextView setText(int index, String stringReference) { + protected TextView setText(int index, String string, boolean reference) { View child = getChildAt(index); if (child instanceof TextView) { TextView textView = (TextView) child; - setText(textView, stringReference); + setText(textView, string, reference); return textView; } return null; } - protected TextView setTextById(int id, String stringReference) { - View child = findViewById(id); - if (child instanceof TextView) { - TextView textView = (TextView) child; - setText(textView, stringReference); - return textView; - } - - return null; - } - - private void setText(TextView textView, String stringReference) { - ResourceValue value = getResourceValue(stringReference); - if (value != null) { - textView.setText(value.getValue()); - } else { - textView.setText(stringReference); + private void setText(TextView textView, String string, boolean reference) { + if (reference) { + ResourceValue value = getResourceValue(string); + if (value != null) { + string = value.getValue(); + } } + textView.setText(string); } protected void setStyle(String themeEntryName) { @@ -256,7 +156,7 @@ abstract class CustomBar extends LinearLayout { ResourceValue value = res.findItemInTheme(themeEntryName, true /*isFrameworkAttr*/); value = res.resolveResValue(value); - if (value instanceof StyleResourceValue == false) { + if (!(value instanceof StyleResourceValue)) { return; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java deleted file mode 100644 index 226649d..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java +++ /dev/null @@ -1,48 +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.bars; - -import com.android.resources.Density; - -import org.xmlpull.v1.XmlPullParserException; - -import android.content.Context; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class FakeActionBar extends CustomBar { - - private TextView mTextView; - - public FakeActionBar(Context context, Density density, String label, String icon) - throws XmlPullParserException { - super(context, density, LinearLayout.HORIZONTAL, "/bars/action_bar.xml", "action_bar.xml"); - - // Cannot access the inside items through id because no R.id values have been - // created for them. - // We do know the order though. - loadIconById(android.R.id.home, icon); - mTextView = setText(1, label); - - setStyle("actionBarStyle"); - } - - @Override - protected TextView getStyleableTextView() { - return mTextView; - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java new file mode 100644 index 0000000..9ab2e82 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.bars; + +import com.android.resources.Density; +import com.android.resources.LayoutDirection; + +import java.io.InputStream; + +public class IconLoader { + + private final String mIconName; + private final Density mDesiredDensity; + private final int mPlatformVersion; + private final LayoutDirection mDirection; + + private Density mCurrentDensity; + private StringBuilder mCurrentPath; + + IconLoader(String iconName, Density density, int platformVersion, LayoutDirection direction) { + mIconName = iconName; + mDesiredDensity = density; + mPlatformVersion = platformVersion; + mDirection = direction; + // An upper bound on the length of the path for the icon: /bars/v21/ldrtl-xxxhdpi/ + final int iconPathLength = 24; + mCurrentPath = new StringBuilder(iconPathLength + iconName.length()); + } + + public InputStream getIcon() { + for (String resourceDir : Config.getResourceDirs(mPlatformVersion)) { + mCurrentDensity = null; + InputStream stream = getIcon(resourceDir); + if (stream != null) { + return stream; + } + } + return null; + } + + /** + * Should only be called after {@link #getIcon()}. Returns the density of the icon, if found by + * {@code getIcon()}. If no icon was found, then the return value has no meaning. + */ + public Density getDensity() { + return mCurrentDensity; + } + + /** + * Should only be called after {@link #getIcon()}. Returns the path to the icon, if found by + * {@code getIcon()}. If no icon was found, then the return value has no meaning. + */ + public String getPath() { + return mCurrentPath.toString(); + } + + /** + * Search for icon in the resource directory. This iterates over all densities. + * If a match is found, mCurrentDensity will be set to the icon's density. + */ + private InputStream getIcon(String resourceDir) { + // First check for the desired density. + InputStream stream = getIcon(resourceDir, mDesiredDensity); + if (stream != null) { + mCurrentDensity = mDesiredDensity; + return stream; + } + // Didn't find in the desired density. Search in all. + for (Density density : Density.values()) { + if (density == mDesiredDensity) { + // Skip the desired density since it's already been checked. + continue; + } + stream = getIcon(resourceDir, density); + if (stream != null) { + mCurrentDensity = density; + return stream; + } + } + return null; + } + + /** + * Returns the icon for given density present in the given resource directory, taking layout + * direction into consideration. + */ + private InputStream getIcon(String resourceDir, Density density) { + mCurrentPath.setLength(0); + // Currently we don't have any LTR only resources and hence the check is skipped. If they + // are ever added, change to: + // if (mDirection == LayoutDirection.RTL || mDirection == LayoutDirection.LTR) { + if (mDirection == LayoutDirection.RTL) { + mCurrentPath.append(resourceDir) + .append(mDirection.getResourceValue()) + .append('-') + .append(density.getResourceValue()) + .append('/') + .append(mIconName); + InputStream stream = getClass().getResourceAsStream(mCurrentPath.toString()); + if (stream != null) { + return stream; + } + mCurrentPath.setLength(0); + } + mCurrentPath.append(resourceDir) + .append(density.getResourceValue()) + .append('/') + .append(mIconName); + return getClass().getResourceAsStream(mCurrentPath.toString()); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java index 84e676e..283ff57 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java @@ -17,7 +17,6 @@ package com.android.layoutlib.bridge.bars; import com.android.resources.Density; -import com.android.layoutlib.bridge.Bridge; import org.xmlpull.v1.XmlPullParserException; @@ -28,8 +27,9 @@ import android.widget.TextView; public class NavigationBar extends CustomBar { public NavigationBar(Context context, Density density, int orientation, boolean isRtl, - boolean rtlEnabled) throws XmlPullParserException { - super(context, density, orientation, "/bars/navigation_bar.xml", "navigation_bar.xml"); + boolean rtlEnabled, int simulatedPlatformVersion) throws XmlPullParserException { + super(context, orientation, "/bars/navigation_bar.xml", "navigation_bar.xml", + simulatedPlatformVersion); setBackgroundColor(0xFF000000); @@ -45,8 +45,11 @@ public class NavigationBar extends CustomBar { recent = 1; } + //noinspection SpellCheckingInspection loadIcon(back, "ic_sysbar_back.png", density, isRtl); + //noinspection SpellCheckingInspection loadIcon(2, "ic_sysbar_home.png", density, isRtl); + //noinspection SpellCheckingInspection loadIcon(recent, "ic_sysbar_recent.png", density, isRtl); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java new file mode 100644 index 0000000..778305d --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.bars; + +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.view.menu.MenuView; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +import java.util.ArrayList; + +/** + * Provides an adapter for Overflow menu popup. This is very similar to + * {@code MenuPopupHelper.MenuAdapter} + */ +public class OverflowMenuAdapter extends BaseAdapter { + + private final MenuBuilder mMenu; + private int mExpandedIndex = -1; + private final Context context; + + public OverflowMenuAdapter(MenuBuilder menu, Context context) { + mMenu = menu; + findExpandedIndex(); + this.context = context; + } + + @Override + public int getCount() { + ArrayList<MenuItemImpl> items = mMenu.getNonActionItems(); + if (mExpandedIndex < 0) { + return items.size(); + } + return items.size() - 1; + } + + @Override + public MenuItemImpl getItem(int position) { + ArrayList<MenuItemImpl> items = mMenu.getNonActionItems(); + if (mExpandedIndex >= 0 && position >= mExpandedIndex) { + position++; + } + return items.get(position); + } + + @Override + public long getItemId(int position) { + // Since a menu item's ID is optional, we'll use the position as an + // ID for the item in the AdapterView + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + LayoutInflater mInflater = LayoutInflater.from(context); + convertView = mInflater.inflate(com.android.internal.R.layout.popup_menu_item_layout, + parent, false); + } + + MenuView.ItemView itemView = (MenuView.ItemView) convertView; + itemView.initialize(getItem(position), 0); + return convertView; + } + + private void findExpandedIndex() { + final MenuItemImpl expandedItem = mMenu.getExpandedItem(); + if (expandedItem != null) { + final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems(); + final int count = items.size(); + for (int i = 0; i < count; i++) { + final MenuItemImpl item = items.get(i); + if (item == expandedItem) { + mExpandedIndex = i; + return; + } + } + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java index 3692d96..c7c62d6 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java @@ -16,36 +16,85 @@ package com.android.layoutlib.bridge.bars; +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.impl.ParserFactory; import com.android.resources.Density; -import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LevelListDrawable; import android.view.Gravity; +import android.view.View; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import java.io.IOException; +import java.io.InputStream; + public class StatusBar extends CustomBar { - public StatusBar(Context context, Density density, int direction, boolean RtlEnabled) - throws XmlPullParserException { - // FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar. + private final Context mContext; + private final int mSimulatedPlatformVersion; - super(context, density, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml"); + public StatusBar(Context context, Density density, int direction, boolean RtlEnabled, + int simulatedPlatformVersion) throws XmlPullParserException { + // FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar. + super(context, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml", + simulatedPlatformVersion); + mContext = context; + mSimulatedPlatformVersion = simulatedPlatformVersion; // FIXME: use FILL_H? setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT); - setBackgroundColor(0xFF000000); + setBackgroundColor(Config.getStatusBarColor(simulatedPlatformVersion)); // Cannot access the inside items through id because no R.id values have been // created for them. // We do know the order though. // 0 is the spacer - loadIcon(1, "stat_sys_wifi_signal_4_fully.png", density); - loadIcon(2, "stat_sys_battery_charge_anim100.png", density); + loadIcon(1, "stat_sys_wifi_signal_4_fully." + + Config.getWifiIconType(simulatedPlatformVersion), density); + loadIcon(2, "stat_sys_battery_100.png", density); + setText(3, Config.getTime(simulatedPlatformVersion), false) + .setTextColor(Config.getTimeColor(simulatedPlatformVersion)); + } + + @Override + protected void loadIcon(int index, String iconName, Density density) { + if (!iconName.endsWith(".xml")) { + super.loadIcon(index, iconName, density); + return; + } + View child = getChildAt(index); + if (child instanceof ImageView) { + ImageView imageView = (ImageView) child; + // The xml is stored only in xhdpi. + IconLoader iconLoader = new IconLoader(iconName, Density.XHIGH, + mSimulatedPlatformVersion, null); + InputStream stream = iconLoader.getIcon(); + + if (stream != null) { + try { + BridgeXmlBlockParser parser = new BridgeXmlBlockParser( + ParserFactory.create(stream, null), (BridgeContext) mContext, true); + Drawable drawable = Drawable.createFromXml(mContext.getResources(), parser); + if (drawable != null) { + imageView.setImageDrawable(drawable); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e, + null); + } catch (IOException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e, + null); + } + } + } } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java index c27859f..10f1383 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java @@ -16,8 +16,6 @@ package com.android.layoutlib.bridge.bars; -import com.android.resources.Density; - import org.xmlpull.v1.XmlPullParserException; import android.content.Context; @@ -28,14 +26,15 @@ public class TitleBar extends CustomBar { private TextView mTextView; - public TitleBar(Context context, Density density, String label) + public TitleBar(Context context, String label, int simulatedPlatformVersion) throws XmlPullParserException { - super(context, density, LinearLayout.HORIZONTAL, "/bars/title_bar.xml", "title_bar.xml"); + super(context, LinearLayout.HORIZONTAL, "/bars/title_bar.xml", "title_bar.xml", + simulatedPlatformVersion); // Cannot access the inside items through id because no R.id values have been // created for them. // We do know the order though. - mTextView = setText(0, label); + mTextView = setText(0, label, true); setStyle("windowTitleBackgroundStyle"); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java deleted file mode 100644 index cc7338a..0000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.layoutlib.bridge.impl; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import android.graphics.Typeface; - -import java.awt.Font; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -/** - * Provides {@link Font} object to the layout lib. - * <p/> - * The fonts are loaded from the SDK directory. Family/style mapping is done by parsing the - * fonts.xml file located alongside the ttf files. - */ -public final class FontLoader { - private static final String FONTS_SYSTEM = "system_fonts.xml"; - private static final String FONTS_VENDOR = "vendor_fonts.xml"; - private static final String FONTS_FALLBACK = "fallback_fonts.xml"; - - private static final String NODE_FAMILYSET = "familyset"; - private static final String NODE_FAMILY = "family"; - private static final String NODE_NAME = "name"; - private static final String NODE_FILE = "file"; - - private static final String ATTRIBUTE_VARIANT = "variant"; - private static final String ATTRIBUTE_VALUE_ELEGANT = "elegant"; - private static final String FONT_SUFFIX_NONE = ".ttf"; - private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf"; - private static final String FONT_SUFFIX_BOLD = "-Bold.ttf"; - // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked - // separately. - private static final String FONT_SUFFIX_ITALIC = "Italic.ttf"; - private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf"; - - // This must match the values of Typeface styles so that we can use them for indices in this - // array. - private static final int[] AWT_STYLES = new int[] { - Font.PLAIN, - Font.BOLD, - Font.ITALIC, - Font.BOLD | Font.ITALIC - }; - private static int[] DERIVE_BOLD_ITALIC = new int[] { - Typeface.ITALIC, Typeface.BOLD, Typeface.NORMAL - }; - private static int[] DERIVE_ITALIC = new int[] { Typeface.NORMAL }; - private static int[] DERIVE_BOLD = new int[] { Typeface.NORMAL }; - - private static final List<FontInfo> mMainFonts = new ArrayList<FontInfo>(); - private static final List<FontInfo> mFallbackFonts = new ArrayList<FontInfo>(); - - private final String mOsFontsLocation; - - public static FontLoader create(String fontOsLocation) { - try { - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - parserFactory.setNamespaceAware(true); - - // parse the system fonts - FontHandler handler = parseFontFile(parserFactory, fontOsLocation, FONTS_SYSTEM); - List<FontInfo> systemFonts = handler.getFontList(); - - - // parse the fallback fonts - handler = parseFontFile(parserFactory, fontOsLocation, FONTS_FALLBACK); - List<FontInfo> fallbackFonts = handler.getFontList(); - - return new FontLoader(fontOsLocation, systemFonts, fallbackFonts); - } catch (ParserConfigurationException e) { - // return null below - } catch (SAXException e) { - // return null below - } catch (FileNotFoundException e) { - // return null below - } catch (IOException e) { - // return null below - } - - return null; - } - - private static FontHandler parseFontFile(SAXParserFactory parserFactory, - String fontOsLocation, String fontFileName) - throws ParserConfigurationException, SAXException, IOException, FileNotFoundException { - - SAXParser parser = parserFactory.newSAXParser(); - File f = new File(fontOsLocation, fontFileName); - - FontHandler definitionParser = new FontHandler( - fontOsLocation + File.separator); - parser.parse(new FileInputStream(f), definitionParser); - return definitionParser; - } - - private FontLoader(String fontOsLocation, - List<FontInfo> fontList, List<FontInfo> fallBackList) { - mOsFontsLocation = fontOsLocation; - mMainFonts.addAll(fontList); - mFallbackFonts.addAll(fallBackList); - } - - - public String getOsFontsLocation() { - return mOsFontsLocation; - } - - /** - * Returns a {@link Font} object given a family name and a style value (constant in - * {@link Typeface}). - * @param family the family name - * @param style a 1-item array containing the requested style. Based on the font being read - * the actual style may be different. The array contains the actual style after - * the method returns. - * @return the font object or null if no match could be found. - */ - public synchronized List<Font> getFont(String family, int style) { - List<Font> result = new ArrayList<Font>(); - - if (family == null) { - return result; - } - - - // get the font objects from the main list based on family. - for (FontInfo info : mMainFonts) { - if (info.families.contains(family)) { - result.add(info.font[style]); - break; - } - } - - // add all the fallback fonts for the given style - for (FontInfo info : mFallbackFonts) { - result.add(info.font[style]); - } - - return result; - } - - - public synchronized List<Font> getFallbackFonts(int style) { - List<Font> result = new ArrayList<Font>(); - // add all the fallback fonts - for (FontInfo info : mFallbackFonts) { - result.add(info.font[style]); - } - return result; - } - - - private final static class FontInfo { - final Font[] font = new Font[4]; // Matches the 4 type-face styles. - final Set<String> families; - - FontInfo() { - families = new HashSet<String>(); - } - } - - private final static class FontHandler extends DefaultHandler { - private final String mOsFontsLocation; - - private FontInfo mFontInfo = null; - private final StringBuilder mBuilder = new StringBuilder(); - private List<FontInfo> mFontList = new ArrayList<FontInfo>(); - private boolean isCompactFont = true; - - private FontHandler(String osFontsLocation) { - super(); - mOsFontsLocation = osFontsLocation; - } - - public List<FontInfo> getFontList() { - return mFontList; - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) - */ - @Override - public void startElement(String uri, String localName, String name, Attributes attributes) - throws SAXException { - if (NODE_FAMILYSET.equals(localName)) { - mFontList = new ArrayList<FontInfo>(); - } else if (NODE_FAMILY.equals(localName)) { - if (mFontList != null) { - mFontInfo = null; - } - } else if (NODE_NAME.equals(localName)) { - if (mFontList != null && mFontInfo == null) { - mFontInfo = new FontInfo(); - } - } else if (NODE_FILE.equals(localName)) { - if (mFontList != null && mFontInfo == null) { - mFontInfo = new FontInfo(); - } - if (ATTRIBUTE_VALUE_ELEGANT.equals(attributes.getValue(ATTRIBUTE_VARIANT))) { - isCompactFont = false; - } else { - isCompactFont = true; - } - } - - mBuilder.setLength(0); - - super.startElement(uri, localName, name, attributes); - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) - */ - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - if (isCompactFont) { - mBuilder.append(ch, start, length); - } - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) - */ - @Override - public void endElement(String uri, String localName, String name) throws SAXException { - if (NODE_FAMILY.equals(localName)) { - if (mFontInfo != null) { - // if has a normal font file, add to the list - if (mFontInfo.font[Typeface.NORMAL] != null) { - mFontList.add(mFontInfo); - - // create missing font styles, order is important. - if (mFontInfo.font[Typeface.BOLD_ITALIC] == null) { - computeDerivedFont(Typeface.BOLD_ITALIC, DERIVE_BOLD_ITALIC); - } - if (mFontInfo.font[Typeface.ITALIC] == null) { - computeDerivedFont(Typeface.ITALIC, DERIVE_ITALIC); - } - if (mFontInfo.font[Typeface.BOLD] == null) { - computeDerivedFont(Typeface.BOLD, DERIVE_BOLD); - } - } - - mFontInfo = null; - } - } else if (NODE_NAME.equals(localName)) { - // handle a new name for an existing Font Info - if (mFontInfo != null) { - String family = trimXmlWhitespaces(mBuilder.toString()); - mFontInfo.families.add(family); - } - } else if (NODE_FILE.equals(localName)) { - // handle a new file for an existing Font Info - if (isCompactFont && mFontInfo != null) { - String fileName = trimXmlWhitespaces(mBuilder.toString()); - Font font = getFont(fileName); - if (font != null) { - if (fileName.endsWith(FONT_SUFFIX_REGULAR)) { - mFontInfo.font[Typeface.NORMAL] = font; - } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) { - mFontInfo.font[Typeface.BOLD] = font; - } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) { - mFontInfo.font[Typeface.BOLD_ITALIC] = font; - } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) { - mFontInfo.font[Typeface.ITALIC] = font; - } else if (fileName.endsWith(FONT_SUFFIX_NONE)) { - mFontInfo.font[Typeface.NORMAL] = font; - } - } - } - } - } - - private Font getFont(String fileName) { - try { - File file = new File(mOsFontsLocation, fileName); - if (file.exists()) { - return Font.createFont(Font.TRUETYPE_FONT, file); - } - } catch (Exception e) { - - } - - return null; - } - - private void computeDerivedFont( int toCompute, int[] basedOnList) { - for (int basedOn : basedOnList) { - if (mFontInfo.font[basedOn] != null) { - mFontInfo.font[toCompute] = - mFontInfo.font[basedOn].deriveFont(AWT_STYLES[toCompute]); - return; - } - } - - // we really shouldn't stop there. This means we don't have a NORMAL font... - assert false; - } - - private String trimXmlWhitespaces(String value) { - if (value == null) { - return null; - } - - // look for carriage return and replace all whitespace around it by just 1 space. - int index; - - while ((index = value.indexOf('\n')) != -1) { - // look for whitespace on each side - int left = index - 1; - while (left >= 0) { - if (Character.isWhitespace(value.charAt(left))) { - left--; - } else { - break; - } - } - - int right = index + 1; - int count = value.length(); - while (right < count) { - if (Character.isWhitespace(value.charAt(right))) { - right++; - } else { - break; - } - } - - // remove all between left and right (non inclusive) and replace by a single space. - String leftString = null; - if (left >= 0) { - leftString = value.substring(0, left + 1); - } - String rightString = null; - if (right < count) { - rightString = value.substring(right); - } - - if (leftString != null) { - value = leftString; - if (rightString != null) { - value += " " + rightString; - } - } else { - value = rightString != null ? rightString : ""; - } - } - - // now we un-escape the string - int length = value.length(); - char[] buffer = value.toCharArray(); - - for (int i = 0 ; i < length ; i++) { - if (buffer[i] == '\\') { - if (buffer[i+1] == 'n') { - // replace the char with \n - buffer[i+1] = '\n'; - } - - // offset the rest of the buffer since we go from 2 to 1 char - System.arraycopy(buffer, i+1, buffer, i, length - i - 1); - length--; - } - } - - return new String(buffer, 0, length); - } - - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java index 21d6b1a..3a0321a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.Bridge; import android.graphics.Bitmap_Delegate; import android.graphics.Canvas; +import android.graphics.ColorFilter_Delegate; import android.graphics.Paint; import android.graphics.Paint_Delegate; import android.graphics.Rect; @@ -48,8 +49,8 @@ import java.util.ArrayList; * This is based on top of {@link Graphics2D} but can operate independently if none are available * yet when setting transforms and clip information. * <p> - * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and - * {@link #draw(Drawable, Paint_Delegate)} + * This allows for drawing through {@link #draw(Drawable, Paint_Delegate, boolean, boolean)} and + * {@link #draw(Drawable)} * * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer} @@ -203,7 +204,7 @@ public class GcSnapshot { * called before the snapshot can be used to draw. Transform and clip operations are permitted * before. * - * @param image the image to associate to the snapshot or null. + * @param bitmap the image to associate to the snapshot or null. * @return the root snapshot */ public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) { @@ -557,7 +558,6 @@ public class GcSnapshot { * Executes the Drawable's draw method, with a null paint delegate. * <p/> * Note that the method can be called several times if there are more than one active layer. - * @param drawable */ public void draw(Drawable drawable) { draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/); @@ -567,20 +567,19 @@ public class GcSnapshot { * Executes the Drawable's draw method. * <p/> * Note that the method can be called several times if there are more than one active layer. - * @param drawable - * @param paint * @param compositeOnly whether the paint is used for composite only. This is typically * the case for bitmaps. * @param forceSrcMode if true, this overrides the composite to be SRC */ public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly, boolean forceSrcMode) { + int forceMode = forceSrcMode ? AlphaComposite.SRC : 0; // the current snapshot may not have a mLocalLayer (ie it was created on save() instead // of saveLayer(), but that doesn't mean there's no layer. // mLayers however saves all the information we need (flags). if (mLayers.size() == 1) { // no layer, only base layer. easy case. - drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceSrcMode); + drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceMode); } else { // draw in all the layers until the layer save flags tells us to stop (ie drawing // in that layer is limited to the layer itself. @@ -590,7 +589,7 @@ public class GcSnapshot { do { Layer layer = mLayers.get(i); - drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode); + drawInLayer(layer, drawable, paint, compositeOnly, forceMode); // then go to previous layer, only if there are any left, and its flags // doesn't restrict drawing to the layer itself. @@ -601,20 +600,61 @@ public class GcSnapshot { } private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint, - boolean compositeOnly, boolean forceSrcMode) { + boolean compositeOnly, int forceMode) { Graphics2D originalGraphics = layer.getGraphics(); - // get a Graphics2D object configured with the drawing parameters. - Graphics2D configuredGraphics2D = - paint != null ? - createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) : - (Graphics2D) originalGraphics.create(); + if (paint == null) { + drawOnGraphics((Graphics2D) originalGraphics.create(), drawable, + null /*paint*/, layer); + } else { + ColorFilter_Delegate filter = paint.getColorFilter(); + if (filter == null || !filter.isSupported()) { + // get a Graphics2D object configured with the drawing parameters. + Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint, + compositeOnly, forceMode); + drawOnGraphics(configuredGraphics, drawable, paint, layer); + return; + } + + int width = layer.getImage().getWidth(); + int height = layer.getImage().getHeight(); + + // Create a temporary image to which the color filter will be applied. + BufferedImage image = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics(); + // Configure the Graphics2D object with drawing parameters and shader. + Graphics2D imageGraphics = createCustomGraphics( + imageBaseGraphics, paint, compositeOnly, + AlphaComposite.SRC_OVER); + // get a Graphics2D object configured with the drawing parameters, but no shader. + Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint, + true /*compositeOnly*/, forceMode); + try { + // The main draw operation. + drawable.draw(imageGraphics, paint); + + // Apply the color filter. + filter.applyFilter(imageGraphics, width, height); + + // Draw the tinted image on the main layer. + configuredGraphics.drawImage(image, 0, 0, null); + layer.change(); + } finally { + // dispose Graphics2D objects + imageGraphics.dispose(); + imageBaseGraphics.dispose(); + configuredGraphics.dispose(); + } + } + } + private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint, + Layer layer) { try { - drawable.draw(configuredGraphics2D, paint); + drawable.draw(g, paint); layer.change(); } finally { - // dispose Graphics2D object - configuredGraphics2D.dispose(); + g.dispose(); } } @@ -685,7 +725,7 @@ public class GcSnapshot { // now draw put the content of the local layer onto the layer, // using the paint information Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint, - true /*alphaOnly*/, false /*forceSrcMode*/); + true /*alphaOnly*/, 0 /*forceMode*/); g.drawImage(mLocalLayer.getImage(), mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, @@ -701,7 +741,7 @@ public class GcSnapshot { * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. */ private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint, - boolean compositeOnly, boolean forceSrcMode) { + boolean compositeOnly, int forceMode) { // make new one graphics Graphics2D g = (Graphics2D) original.create(); @@ -714,70 +754,73 @@ public class GcSnapshot { RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } + // set the shader first, as it'll replace the color if it can be used it. boolean customShader = false; + if (!compositeOnly) { + customShader = setShader(g, paint); + // set the stroke + g.setStroke(paint.getJavaStroke()); + } + // set the composite. + setComposite(g, paint, compositeOnly || customShader, forceMode); - // get the shader first, as it'll replace the color if it can be used it. - if (compositeOnly == false) { - Shader_Delegate shaderDelegate = paint.getShader(); - if (shaderDelegate != null) { - if (shaderDelegate.isSupported()) { - java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint(); - assert shaderPaint != null; - if (shaderPaint != null) { - g.setPaint(shaderPaint); - customShader = true; - } - } else { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER, - shaderDelegate.getSupportMessage(), - null /*throwable*/, null /*data*/); + return g; + } + + private boolean setShader(Graphics2D g, Paint_Delegate paint) { + Shader_Delegate shaderDelegate = paint.getShader(); + if (shaderDelegate != null) { + if (shaderDelegate.isSupported()) { + java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint(); + assert shaderPaint != null; + if (shaderPaint != null) { + g.setPaint(shaderPaint); + return true; } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER, + shaderDelegate.getSupportMessage(), + null /*throwable*/, null /*data*/); } + } - // if no shader, use the paint color - if (customShader == false) { - g.setColor(new Color(paint.getColor(), true /*hasAlpha*/)); - } + // if no shader, use the paint color + g.setColor(new Color(paint.getColor(), true /*hasAlpha*/)); - // set the stroke - g.setStroke(paint.getJavaStroke()); - } + return false; + } + private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha, + int forceMode) { // the alpha for the composite. Always opaque if the normal paint color is used since // it contains the alpha - int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF; - - if (forceSrcMode) { - g.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC, (float) alpha / 255.f)); - } else { - boolean customXfermode = false; - Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); - if (xfermodeDelegate != null) { - if (xfermodeDelegate.isSupported()) { - Composite composite = xfermodeDelegate.getComposite(alpha); - assert composite != null; - if (composite != null) { - g.setComposite(composite); - customXfermode = true; - } - } else { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE, - xfermodeDelegate.getSupportMessage(), - null /*throwable*/, null /*data*/); + int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF; + if (forceMode != 0) { + g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f)); + return; + } + Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); + if (xfermodeDelegate != null) { + if (xfermodeDelegate.isSupported()) { + Composite composite = xfermodeDelegate.getComposite(alpha); + assert composite != null; + if (composite != null) { + g.setComposite(composite); + return; } - } - - // if there was no custom xfermode, but we have alpha (due to a shader and a non - // opaque alpha channel in the paint color), then we create an AlphaComposite anyway - // that will handle the alpha. - if (customXfermode == false && alpha != 0xFF) { - g.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, (float) alpha / 255.f)); + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE, + xfermodeDelegate.getSupportMessage(), + null /*throwable*/, null /*data*/); } } - - return g; + // if there was no custom xfermode, but we have alpha (due to a shader and a non + // opaque alpha channel in the paint color), then we create an AlphaComposite anyway + // that will handle the alpha. + if (alpha != 0xFF) { + g.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, (float) alpha / 255.f)); + } } private void mapRect(AffineTransform matrix, RectF dst, RectF src) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java new file mode 100644 index 0000000..9588035 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.impl; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; + +import android.graphics.BlendComposite; +import android.graphics.BlendComposite.BlendingMode; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffColorFilter_Delegate; +import android.graphics.PorterDuffXfermode_Delegate; + +import java.awt.AlphaComposite; +import java.awt.Composite; + +/** + * Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link + * PorterDuffXfermode_Delegate}. + */ +public final class PorterDuffUtility { + + // Make the class non-instantiable. + private PorterDuffUtility() { + } + + /** + * Convert the porterDuffMode from the framework to its corresponding enum. This defaults to + * {@link Mode#SRC_OVER} for invalid modes. + */ + public static Mode getPorterDuffMode(int porterDuffMode) { + Mode[] values = Mode.values(); + if (porterDuffMode >= 0 && porterDuffMode < values.length) { + return values[porterDuffMode]; + } + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("Unknown PorterDuff.Mode: %1$d", porterDuffMode), null /*data*/); + assert false; + return Mode.SRC_OVER; + } + + /** + * A utility method to get the {@link Composite} that represents the filter for the given + * PorterDuff mode and the alpha. Defaults to {@link Mode#SRC_OVER} for invalid modes. + */ + public static Composite getComposite(Mode mode, int alpha255) { + float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f; + switch (mode) { + case CLEAR: + return AlphaComposite.getInstance(AlphaComposite.CLEAR, alpha1); + case SRC: + return AlphaComposite.getInstance(AlphaComposite.SRC, alpha1); + case DST: + return AlphaComposite.getInstance(AlphaComposite.DST, alpha1); + case SRC_OVER: + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1); + case DST_OVER: + return AlphaComposite.getInstance(AlphaComposite.DST_OVER, alpha1); + case SRC_IN: + return AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha1); + case DST_IN: + return AlphaComposite.getInstance(AlphaComposite.DST_IN, alpha1); + case SRC_OUT: + return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, alpha1); + case DST_OUT: + return AlphaComposite.getInstance(AlphaComposite.DST_OUT, alpha1); + case SRC_ATOP: + return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha1); + case DST_ATOP: + return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, alpha1); + case XOR: + return AlphaComposite.getInstance(AlphaComposite.XOR, alpha1); + case DARKEN: + return BlendComposite.getInstance(BlendingMode.DARKEN, alpha1); + case LIGHTEN: + return BlendComposite.getInstance(BlendingMode.LIGHTEN, alpha1); + case MULTIPLY: + return BlendComposite.getInstance(BlendingMode.MULTIPLY, alpha1); + case SCREEN: + return BlendComposite.getInstance(BlendingMode.SCREEN, alpha1); + case ADD: + return BlendComposite.getInstance(BlendingMode.ADD, alpha1); + case OVERLAY: + return BlendComposite.getInstance(BlendingMode.OVERLAY, alpha1); + default: + Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, + String.format("Unsupported PorterDuff Mode: %1$s", mode.name()), + null, null /*data*/); + + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1); + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 377d996..b8dce70 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -28,7 +28,6 @@ import com.android.ide.common.rendering.api.HardwareConfig; import com.android.ide.common.rendering.api.IAnimationListener; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; -import com.android.ide.common.rendering.api.RenderParams; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.ResourceReference; @@ -38,17 +37,26 @@ import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.SessionParams.RenderingMode; import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.ViewType; import com.android.internal.util.XmlUtils; +import com.android.internal.view.menu.ActionMenuItemView; +import com.android.internal.view.menu.BridgeMenuItemImpl; +import com.android.internal.view.menu.IconMenuItemView; +import com.android.internal.view.menu.ListMenuItemView; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.view.menu.MenuView; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; -import com.android.layoutlib.bridge.bars.FakeActionBar; +import com.android.layoutlib.bridge.bars.Config; import com.android.layoutlib.bridge.bars.NavigationBar; import com.android.layoutlib.bridge.bars.StatusBar; import com.android.layoutlib.bridge.bars.TitleBar; +import com.android.layoutlib.bridge.bars.ActionBarLayout; import com.android.layoutlib.bridge.impl.binding.FakeAdapter; import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; +import com.android.resources.Density; import com.android.resources.ResourceType; import com.android.resources.ScreenOrientation; import com.android.util.Pair; @@ -77,9 +85,12 @@ import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewParent; import android.view.WindowManagerGlobal_Delegate; +import android.view.ViewParent; import android.widget.AbsListView; import android.widget.AbsSpinner; +import android.widget.ActionMenuView; import android.widget.AdapterView; import android.widget.ExpandableListView; import android.widget.FrameLayout; @@ -100,11 +111,10 @@ import java.util.Map; /** * Class implementing the render session. - * + * <p/> * A session is a stateful representation of a layout file. It is initialized with data coming * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then * be done on the layout. - * */ public class RenderSessionImpl extends RenderAction<SessionParams> { @@ -134,6 +144,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // information being returned through the API private BufferedImage mImage; private List<ViewInfo> mViewInfoList; + private List<ViewInfo> mSystemViewInfoList; private static final class PostInflateException extends Exception { private static final long serialVersionUID = 1L; @@ -146,10 +157,11 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { /** * Creates a layout scene with all the information coming from the layout bridge API. * <p> - * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init()}, which act as a + * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init(long)}, + * which act as a * call to {@link RenderSessionImpl#acquire(long)} * - * @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams) + * @see Bridge#createSession(SessionParams) */ public RenderSessionImpl(SessionParams params) { super(new SessionParams(params)); @@ -169,13 +181,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { @Override public Result init(long timeout) { Result result = super.init(timeout); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { return result; } SessionParams params = getParams(); BridgeContext context = getContext(); + RenderResources resources = getParams().getResources(); DisplayMetrics metrics = getContext().getMetrics(); @@ -193,6 +206,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // FIXME: find those out, and possibly add them to the render params boolean hasNavigationBar = true; + //noinspection ConstantConditions IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(), metrics, Surface.ROTATION_0, hasNavigationBar); @@ -225,15 +239,16 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { HardwareConfig hardwareConfig = params.getHardwareConfig(); BridgeContext context = getContext(); boolean isRtl = Bridge.isLocaleRtl(params.getLocale()); - int direction = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; + int layoutDirection = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; // the view group that receives the window background. - ViewGroup backgroundView = null; + ViewGroup backgroundView; if (mWindowIsFloating || params.isForceNoDecor()) { backgroundView = mViewRoot = mContentRoot = new FrameLayout(context); - mViewRoot.setLayoutDirection(direction); + mViewRoot.setLayoutDirection(layoutDirection); } else { + int simulatedPlatformVersion = params.getSimulatedPlatformVersion(); if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) { /* * This is a special case where the navigation bar is on the right. @@ -254,21 +269,18 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { the bottom */ LinearLayout topLayout = new LinearLayout(context); - topLayout.setLayoutDirection(direction); + topLayout.setLayoutDirection(layoutDirection); mViewRoot = topLayout; topLayout.setOrientation(LinearLayout.HORIZONTAL); - try { - NavigationBar navigationBar = new NavigationBar(context, - hardwareConfig.getDensity(), LinearLayout.VERTICAL, isRtl, - params.isRtlSupported()); - navigationBar.setLayoutParams( - new LinearLayout.LayoutParams( - mNavigationBarSize, - LayoutParams.MATCH_PARENT)); - topLayout.addView(navigationBar); - } catch (XmlPullParserException e) { - + if (Config.showOnScreenNavBar(simulatedPlatformVersion)) { + try { + NavigationBar navigationBar = createNavigationBar(context, + hardwareConfig.getDensity(), isRtl, params.isRtlSupported(), + simulatedPlatformVersion); + topLayout.addView(navigationBar); + } catch (XmlPullParserException ignored) { + } } } @@ -293,14 +305,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { LinearLayout topLayout = new LinearLayout(context); topLayout.setOrientation(LinearLayout.VERTICAL); - topLayout.setLayoutDirection(direction); + topLayout.setLayoutDirection(layoutDirection); // if we don't already have a view root this is it if (mViewRoot == null) { mViewRoot = topLayout; } else { + int topLayoutWidth = + params.getHardwareConfig().getScreenWidth() - mNavigationBarSize; LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - layoutParams.weight = 1; + topLayoutWidth, LayoutParams.MATCH_PARENT); topLayout.setLayoutParams(layoutParams); // this is the case of soft buttons + vertical bar. @@ -319,13 +332,11 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { if (mStatusBarSize > 0) { // system bar try { - StatusBar systemBar = new StatusBar(context, hardwareConfig.getDensity(), - direction, params.isRtlSupported()); - systemBar.setLayoutParams( - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, mStatusBarSize)); - topLayout.addView(systemBar); - } catch (XmlPullParserException e) { + StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(), + layoutDirection, params.isRtlSupported(), + simulatedPlatformVersion); + topLayout.addView(statusBar); + } catch (XmlPullParserException ignored) { } } @@ -342,50 +353,41 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // if the theme says no title/action bar, then the size will be 0 if (mActionBarSize > 0) { - try { - FakeActionBar actionBar = new FakeActionBar(context, - hardwareConfig.getDensity(), - params.getAppLabel(), params.getAppIcon()); - actionBar.setLayoutParams( - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, mActionBarSize)); - backgroundLayout.addView(actionBar); - } catch (XmlPullParserException e) { - - } + ActionBarLayout actionBar = createActionBar(context, params); + backgroundLayout.addView(actionBar); + actionBar.createMenuPopup(); + mContentRoot = actionBar.getContentRoot(); } else if (mTitleBarSize > 0) { try { - TitleBar titleBar = new TitleBar(context, - hardwareConfig.getDensity(), params.getAppLabel()); - titleBar.setLayoutParams( - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, mTitleBarSize)); + TitleBar titleBar = createTitleBar(context, + params.getAppLabel(), + simulatedPlatformVersion); backgroundLayout.addView(titleBar); - } catch (XmlPullParserException e) { + } catch (XmlPullParserException ignored) { } } // content frame - mContentRoot = new FrameLayout(context); - layoutParams = new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, 0); - layoutParams.weight = 1; - mContentRoot.setLayoutParams(layoutParams); - backgroundLayout.addView(mContentRoot); + if (mContentRoot == null) { + mContentRoot = new FrameLayout(context); + layoutParams = new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, 0); + layoutParams.weight = 1; + mContentRoot.setLayoutParams(layoutParams); + backgroundLayout.addView(mContentRoot); + } - if (mNavigationBarOrientation == LinearLayout.HORIZONTAL && + if (Config.showOnScreenNavBar(simulatedPlatformVersion) && + mNavigationBarOrientation == LinearLayout.HORIZONTAL && mNavigationBarSize > 0) { // system bar try { - NavigationBar navigationBar = new NavigationBar(context, - hardwareConfig.getDensity(), LinearLayout.HORIZONTAL, isRtl, - params.isRtlSupported()); - navigationBar.setLayoutParams( - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, mNavigationBarSize)); + NavigationBar navigationBar = createNavigationBar(context, + hardwareConfig.getDensity(), isRtl, params.isRtlSupported(), + simulatedPlatformVersion); topLayout.addView(navigationBar); - } catch (XmlPullParserException e) { + } catch (XmlPullParserException ignored) { } } @@ -410,7 +412,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { postInflateProcess(view, params.getProjectCallback()); // get the background drawable - if (mWindowBackground != null && backgroundView != null) { + if (mWindowBackground != null) { Drawable d = ResourceHelper.getDrawable(mWindowBackground, context); backgroundView.setBackground(d); } @@ -441,7 +443,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * @throws IllegalStateException if the current context is different than the one owned by * the scene, or if {@link #acquire(long)} was not called. * - * @see RenderParams#getRenderingMode() + * @see SessionParams#getRenderingMode() * @see RenderSession#render(long) */ public Result render(boolean freshRender) { @@ -487,6 +489,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // first measure the full layout, with EXACTLY to get the size of the // content as it is inside the decor/dialog + @SuppressWarnings("deprecation") Pair<Integer, Integer> exactMeasure = measureView( mViewRoot, mContentRoot.getChildAt(0), mMeasuredScreenWidth, MeasureSpec.EXACTLY, @@ -494,6 +497,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // now measure the content only using UNSPECIFIED (where applicable, based on // the rendering mode). This will give us the size the content needs. + @SuppressWarnings("deprecation") Pair<Integer, Integer> result = measureView( mContentRoot, mContentRoot.getChildAt(0), mMeasuredScreenWidth, widthMeasureSpecMode, @@ -569,7 +573,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue()); } - if (freshRender && newImage == false) { + if (freshRender && !newImage) { Graphics2D gc = mImage.createGraphics(); gc.setComposite(AlphaComposite.Src); @@ -584,7 +588,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mViewRoot.draw(mCanvas); } - mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode()); + mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), + false); // success! return SUCCESS.createResult(); @@ -614,6 +619,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * @param heightMode the MeasureSpec mode to use for the height. * @return the measured width/height if measuredView is non-null, null otherwise. */ + @SuppressWarnings("deprecation") // For the use of Pair private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView, int width, int widthMode, int height, int heightMode) { int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode); @@ -644,7 +650,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { BridgeContext context = getContext(); // find the animation file. - ResourceValue animationResource = null; + ResourceValue animationResource; int animationId = 0; if (isFrameworkAnimation) { animationResource = context.getRenderResources().getFrameworkResource( @@ -734,7 +740,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // add it to the parentView in the correct location Result result = addView(parentView, child, index); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { return result; } @@ -804,13 +810,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { public void run() { Result result = moveView(previousParent, newParentView, childView, index, params); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { listener.done(result); } // ready to do the work, acquire the scene. result = acquire(250); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { listener.done(result); return; } @@ -868,7 +874,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } Result result = moveView(previousParent, newParentView, childView, index, layoutParams); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { return result; } @@ -1002,7 +1008,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } Result result = removeView(parent, childView); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { return result; } @@ -1030,7 +1036,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { private void findBackground(RenderResources resources) { - if (getParams().isBgColorOverridden() == false) { + if (!getParams().isBgColorOverridden()) { mWindowBackground = resources.findItemInTheme("windowBackground", true /*isFrameworkAttr*/); if (mWindowBackground != null) { @@ -1047,7 +1053,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { boolean windowFullscreen = getBooleanThemeValue(resources, "windowFullscreen", false /*defaultValue*/); - if (windowFullscreen == false && mWindowIsFloating == false) { + if (!windowFullscreen && !mWindowIsFloating) { // default value mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT; @@ -1101,7 +1107,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { boolean windowNoTitle = getBooleanThemeValue(resources, "windowNoTitle", false /*defaultValue*/); - if (windowNoTitle == false) { + if (!windowNoTitle) { // default size of the window title bar mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT; @@ -1128,7 +1134,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } private void findNavigationBar(RenderResources resources, DisplayMetrics metrics) { - if (hasSoftwareButtons() && mWindowIsFloating == false) { + if (hasSoftwareButtons() && !mWindowIsFloating) { // default value mNavigationBarSize = 48; // ?? @@ -1142,15 +1148,12 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { int shortSize = hardwareConfig.getScreenHeight(); // compute in dp - int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / hardwareConfig.getDensity().getDpiValue(); + int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / + hardwareConfig.getDensity().getDpiValue(); - if (shortSizeDp < 600) { - // 0-599dp: "phone" UI with bar on the side - barOnBottom = false; - } else { - // 600+dp: "tablet" UI with bar on the bottom - barOnBottom = true; - } + // 0-599dp: "phone" UI with bar on the side + // 600+dp: "tablet" UI with bar on the bottom + barOnBottom = shortSizeDp >= 600; } if (barOnBottom) { @@ -1201,13 +1204,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** - * Post process on a view hierachy that was just inflated. - * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the + * Post process on a view hierarchy that was just inflated. + * <p/> + * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically * based on the content of the {@link FrameLayout}. * @param view the root view to process. * @param projectCallback callback to the project. */ + @SuppressWarnings("deprecation") // For the use of Pair private void postInflateProcess(View view, IProjectCallback projectCallback) throws PostInflateException { if (view instanceof TabHost) { @@ -1310,7 +1315,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); } - if ((v instanceof TabWidget) == false) { + if (!(v instanceof TabWidget)) { throw new PostInflateException(String.format( "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); @@ -1319,12 +1324,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { v = tabHost.findViewById(android.R.id.tabcontent); if (v == null) { - // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) + // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty) + //noinspection SpellCheckingInspection throw new PostInflateException( "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); } - if ((v instanceof FrameLayout) == false) { + if (!(v instanceof FrameLayout)) { + //noinspection SpellCheckingInspection throw new PostInflateException(String.format( "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); @@ -1332,7 +1339,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { FrameLayout content = (FrameLayout)v; - // now process the content of the framelayout and dynamically create tabs for it. + // now process the content of the frameLayout and dynamically create tabs for it. final int count = content.getChildCount(); // this must be called before addTab() so that the TabHost searches its TabWidget @@ -1350,13 +1357,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } }); tabHost.addTab(spec); - return; } else { - // for each child of the framelayout, add a new TabSpec + // for each child of the frameLayout, add a new TabSpec for (int i = 0 ; i < count ; i++) { View child = content.getChildAt(i); String tabSpec = String.format("tab_spec%d", i+1); int id = child.getId(); + @SuppressWarnings("deprecation") Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id); String name; if (resource != null) { @@ -1369,50 +1376,159 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } - private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) { - if (view == null) { - return null; - } + /** + * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the + * bounds of all the views. + * + * @param view the root View + * @param offset an offset for the view bounds. + * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. + * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the + * content frame. + * + * @return {@code ViewInfo} containing the bounds of the view and it children otherwise. + */ + private ViewInfo visit(View view, int offset, boolean setExtendedInfo, + boolean isContentFrame) { + ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame); - // adjust the offset to this view. - offset += view.getTop(); + if (view instanceof ViewGroup) { + ViewGroup group = ((ViewGroup) view); + result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset, + setExtendedInfo, isContentFrame)); + } + return result; + } - if (view == mContentRoot) { - return visitAllChildren(mContentRoot, offset, setExtendedInfo); + /** + * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo} + * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with + * the children of the {@code mContentRoot}. + * + * @param viewGroup the root View + * @param offset an offset from the top for the content view frame. + * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. + * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the + * content frame. {@code false} if the {@code ViewInfo} to be created is + * part of the system decor. + */ + private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, + boolean setExtendedInfo, boolean isContentFrame) { + if (viewGroup == null) { + return null; } - // otherwise, look for mContentRoot in the children - if (view instanceof ViewGroup) { - ViewGroup group = ((ViewGroup) view); + if (!isContentFrame) { + offset += viewGroup.getTop(); + } - for (int i = 0; i < group.getChildCount(); i++) { - List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset, + int childCount = viewGroup.getChildCount(); + if (viewGroup == mContentRoot) { + List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount); + List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount); + for (int i = 0; i < childCount; i++) { + ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset, setExtendedInfo); - if (list != null) { - return list; - } + childrenWithoutOffset.add(childViewInfo[0]); + childrenWithOffset.add(childViewInfo[1]); } + mViewInfoList = childrenWithOffset; + return childrenWithoutOffset; + } else { + List<ViewInfo> children = new ArrayList<ViewInfo>(childCount); + for (int i = 0; i < childCount; i++) { + children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo, + isContentFrame)); + } + return children; } + } - return null; + /** + * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the + * bounds of all the views. It returns two {@code ViewInfo} objects with the same children, + * one with the {@code offset} and other without the {@code offset}. The offset is needed to + * get the right bounds if the {@code ViewInfo} hierarchy is accessed from + * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the + * offset is not needed. + * + * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at + * index 1 is with the offset. + */ + private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) { + ViewInfo[] result = new ViewInfo[2]; + if (view == null) { + return result; + } + + result[0] = createViewInfo(view, 0, setExtendedInfo, true); + result[1] = createViewInfo(view, offset, setExtendedInfo, true); + if (view instanceof ViewGroup) { + List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true); + result[0].setChildren(children); + result[1].setChildren(children); + } + return result; } /** - * Visits a View and its children and generate a {@link ViewInfo} containing the - * bounds of all the views. - * @param view the root View - * @param offset an offset for the view bounds. - * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. + * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children + * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not + * set. + * @param offset an offset for the view bounds. Used only if view is part of the content frame. */ - private ViewInfo visit(View view, int offset, boolean setExtendedInfo) { + private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo, + boolean isContentFrame) { if (view == null) { return null; } - ViewInfo result = new ViewInfo(view.getClass().getName(), - getContext().getViewKey(view), - view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset, - view, view.getLayoutParams()); + ViewInfo result; + if (isContentFrame) { + // The view is part of the layout added by the user. Hence, + // the ViewCookie may be obtained only through the Context. + result = new ViewInfo(view.getClass().getName(), + getContext().getViewKey(view), + view.getLeft(), view.getTop() + offset, view.getRight(), + view.getBottom() + offset, view, view.getLayoutParams()); + } else { + // We are part of the system decor. + SystemViewInfo r = new SystemViewInfo(view.getClass().getName(), + getViewKey(view), + view.getLeft(), view.getTop(), view.getRight(), + view.getBottom(), view, view.getLayoutParams()); + result = r; + // We currently mark three kinds of views: + // 1. Menus in the Action Bar + // 2. Menus in the Overflow popup. + // 3. The overflow popup button. + if (view instanceof ListMenuItemView) { + // Mark 2. + // All menus in the popup are of type ListMenuItemView. + r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU); + } else { + // Mark 3. + ViewGroup.LayoutParams lp = view.getLayoutParams(); + if (lp instanceof ActionMenuView.LayoutParams && + ((ActionMenuView.LayoutParams) lp).isOverflowButton) { + r.setViewType(ViewType.ACTION_BAR_OVERFLOW); + } else { + // Mark 1. + // A view is a menu in the Action Bar is it is not the overflow button and of + // its parent is of type ActionMenuView. We can also check if the view is + // instanceof ActionMenuItemView but that will fail for menus using + // actionProviderClass. + ViewParent parent = view.getParent(); + while (parent != mViewRoot && parent instanceof ViewGroup) { + if (parent instanceof ActionMenuView) { + r.setViewType(ViewType.ACTION_BAR_MENU); + break; + } + parent = parent.getParent(); + } + } + } + } if (setExtendedInfo) { MarginLayoutParams marginParams = null; @@ -1427,37 +1543,92 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { marginParams != null ? marginParams.bottomMargin : 0); } - if (view instanceof ViewGroup) { - ViewGroup group = ((ViewGroup) view); - result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo)); + return result; + } + + /* (non-Javadoc) + * The cookie for menu items are stored in menu item and not in the map from View stored in + * BridgeContext. + */ + private Object getViewKey(View view) { + BridgeContext context = getContext(); + if (!(view instanceof MenuView.ItemView)) { + return context.getViewKey(view); + } + MenuItemImpl menuItem; + if (view instanceof ActionMenuItemView) { + menuItem = ((ActionMenuItemView) view).getItemData(); + } else if (view instanceof ListMenuItemView) { + menuItem = ((ListMenuItemView) view).getItemData(); + } else if (view instanceof IconMenuItemView) { + menuItem = ((IconMenuItemView) view).getItemData(); + } else { + menuItem = null; + } + if (menuItem instanceof BridgeMenuItemImpl) { + return ((BridgeMenuItemImpl) menuItem).getViewCookie(); } - return result; + return null; + } + + private void invalidateRenderingSize() { + mMeasuredScreenWidth = mMeasuredScreenHeight = -1; } /** - * Visits all the children of a given ViewGroup generate a list of {@link ViewInfo} - * containing the bounds of all the views. - * @param view the root View - * @param offset an offset for the view bounds. - * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. + * Creates the status bar with wifi and battery icons. */ - private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, - boolean setExtendedInfo) { - if (viewGroup == null) { - return null; - } + private StatusBar createStatusBar(BridgeContext context, Density density, int direction, + boolean isRtlSupported, int platformVersion) throws XmlPullParserException { + StatusBar statusBar = new StatusBar(context, density, + direction, isRtlSupported, platformVersion); + statusBar.setLayoutParams( + new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, mStatusBarSize)); + return statusBar; + } - List<ViewInfo> children = new ArrayList<ViewInfo>(); - for (int i = 0; i < viewGroup.getChildCount(); i++) { - children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo)); + /** + * Creates the navigation bar with back, home and recent buttons. + * + * @param isRtl true if the current locale is right-to-left + * @param isRtlSupported true is the project manifest declares that the application + * is RTL aware. + */ + private NavigationBar createNavigationBar(BridgeContext context, Density density, + boolean isRtl, boolean isRtlSupported, int simulatedPlatformVersion) + throws XmlPullParserException { + NavigationBar navigationBar = new NavigationBar(context, + density, mNavigationBarOrientation, isRtl, + isRtlSupported, simulatedPlatformVersion); + if (mNavigationBarOrientation == LinearLayout.VERTICAL) { + navigationBar.setLayoutParams(new LinearLayout.LayoutParams(mNavigationBarSize, + LayoutParams.MATCH_PARENT)); + } else { + navigationBar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, + mNavigationBarSize)); } - return children; + return navigationBar; } + private TitleBar createTitleBar(BridgeContext context, String title, + int simulatedPlatformVersion) + throws XmlPullParserException { + TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion); + titleBar.setLayoutParams( + new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, mTitleBarSize)); + return titleBar; + } - private void invalidateRenderingSize() { - mMeasuredScreenWidth = mMeasuredScreenHeight = -1; + /** + * Creates the action bar. Also queries the project callback for missing information. + */ + private ActionBarLayout createActionBar(BridgeContext context, SessionParams params) { + ActionBarLayout actionBar = new ActionBarLayout(context, params); + actionBar.setLayoutParams(new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + return actionBar; } public BufferedImage getImage() { @@ -1472,6 +1643,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { return mViewInfoList; } + public List<ViewInfo> getSystemViewInfos() { + return mSystemViewInfoList; + } + public Map<String, String> getDefaultProperties(Object viewObject) { return getContext().getDefaultPropMap(viewObject); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index 6dcb693..22f8e1c 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -16,6 +16,7 @@ package com.android.layoutlib.bridge.impl; +import com.android.annotations.NonNull; import com.android.ide.common.rendering.api.DensityBasedResourceValue; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderResources; @@ -63,11 +64,11 @@ public final class ResourceHelper { * Returns the color value represented by the given string value * @param value the color value * @return the color as an int - * @throw NumberFormatException if the conversion failed. + * @throws NumberFormatException if the conversion failed. */ public static int getColor(String value) { if (value != null) { - if (value.startsWith("#") == false) { + if (!value.startsWith("#")) { throw new NumberFormatException( String.format("Color value '%s' must start with #", value)); } @@ -113,7 +114,7 @@ public final class ResourceHelper { public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) { String value = resValue.getValue(); - if (value != null && RenderResources.REFERENCE_NULL.equals(value) == false) { + if (value != null && !RenderResources.REFERENCE_NULL.equals(value)) { // first check if the value is a file (xml most likely) File f = new File(value); if (f.isFile()) { @@ -165,6 +166,9 @@ public final class ResourceHelper { * @param context the current context */ public static Drawable getDrawable(ResourceValue value, BridgeContext context) { + if (value == null) { + return null; + } String stringValue = value.getValue(); if (RenderResources.REFERENCE_NULL.equals(stringValue)) { return null; @@ -355,9 +359,9 @@ public final class ResourceHelper { * @param requireUnit whether the value is expected to contain a unit. * @return true if success. */ - public static boolean parseFloatAttribute(String attribute, String value, + public static boolean parseFloatAttribute(String attribute, @NonNull String value, TypedValue outValue, boolean requireUnit) { - assert requireUnit == false || attribute != null; + assert !requireUnit || attribute != null; // remove the space before and after value = value.trim(); @@ -376,7 +380,7 @@ public final class ResourceHelper { } // check the first character - if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.' && buf[0] != '-') { + if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') { return false; } @@ -408,7 +412,7 @@ public final class ResourceHelper { if (end.length() == 0) { if (outValue != null) { - if (requireUnit == false) { + if (!requireUnit) { outValue.type = TypedValue.TYPE_FLOAT; outValue.data = Float.floatToIntBits(f); } else { @@ -486,6 +490,8 @@ public final class ResourceHelper { private static void applyUnit(UnitEntry unit, TypedValue outValue, float[] outScale) { outValue.type = unit.type; + // COMPLEX_UNIT_SHIFT is 0 and hence intelliJ complains about it. Suppress the warning. + //noinspection PointlessBitwiseExpression outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT; outScale[0] = unit.scale; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java new file mode 100644 index 0000000..9fea167 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.impl; + +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.ViewType; + +/** + * ViewInfo for views added by the platform. + */ +public class SystemViewInfo extends ViewInfo { + + private ViewType mViewType; + + public SystemViewInfo(String name, Object cookie, int left, int top, + int right, int bottom) { + super(name, cookie, left, top, right, bottom); + } + + public SystemViewInfo(String name, Object cookie, int left, int top, + int right, int bottom, Object viewObject, Object layoutParamsObject) { + super(name, cookie, left, top, right, bottom, viewObject, + layoutParamsObject); + } + + @Override + public ViewType getViewType() { + if (mViewType != null) { + return mViewType; + } + return ViewType.SYSTEM_UNKNOWN; + } + + public void setViewType(ViewType type) { + mViewType = type; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java index 53e1640..a2a8aa9 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java @@ -18,6 +18,7 @@ package com.android.layoutlib.bridge.util; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.GrowingArrayUtils; import android.util.SparseArray; @@ -59,10 +60,8 @@ public class SparseWeakArray<E> { * number of mappings. */ public SparseWeakArray(int initialCapacity) { - initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); - - mKeys = new long[initialCapacity]; - mValues = new WeakReference[initialCapacity]; + mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity); + mValues = new WeakReference[mKeys.length]; mSize = 0; } @@ -142,18 +141,6 @@ public class SparseWeakArray<E> { mGarbage = false; mSize = o; - - int newSize = ArrayUtils.idealLongArraySize(mSize); - if (newSize < mKeys.length) { - long[] nkeys = new long[newSize]; - WeakReference<?>[] nvalues = new WeakReference[newSize]; - - System.arraycopy(mKeys, 0, nkeys, 0, newSize); - System.arraycopy(mValues, 0, nvalues, 0, newSize); - - mKeys = nkeys; - mValues = nvalues; - } } /** @@ -182,28 +169,8 @@ public class SparseWeakArray<E> { i = ~binarySearch(mKeys, 0, mSize, key); } - if (mSize >= mKeys.length) { - int n = ArrayUtils.idealLongArraySize(mSize + 1); - - long[] nkeys = new long[n]; - WeakReference<?>[] nvalues = new WeakReference[n]; - - // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); - System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); - System.arraycopy(mValues, 0, nvalues, 0, mValues.length); - - mKeys = nkeys; - mValues = nvalues; - } - - if (mSize - i != 0) { - // Log.e("SparseArray", "move " + (mSize - i)); - System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); - System.arraycopy(mValues, i, mValues, i + 1, mSize - i); - } - - mKeys[i] = key; - mValues[i] = new WeakReference(value); + mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); + mValues = GrowingArrayUtils.insert(mValues, mSize, i, new WeakReference(value)); mSize++; } } @@ -321,24 +288,9 @@ public class SparseWeakArray<E> { gc(); } - int pos = mSize; - if (pos >= mKeys.length) { - int n = ArrayUtils.idealLongArraySize(pos + 1); - - long[] nkeys = new long[n]; - WeakReference<?>[] nvalues = new WeakReference[n]; - - // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); - System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); - System.arraycopy(mValues, 0, nvalues, 0, mValues.length); - - mKeys = nkeys; - mValues = nvalues; - } - - mKeys[pos] = key; - mValues[pos] = new WeakReference(value); - mSize = pos + 1; + mKeys = GrowingArrayUtils.append(mKeys, mSize, key); + mValues = GrowingArrayUtils.append(mValues, mSize, new WeakReference(value)); + mSize++; } private boolean hasReclaimedRefs() { diff --git a/tools/layoutlib/bridge/src/dalvik/system/VMRuntime_Delegate.java b/tools/layoutlib/bridge/src/dalvik/system/VMRuntime_Delegate.java new file mode 100644 index 0000000..36efc3a --- /dev/null +++ b/tools/layoutlib/bridge/src/dalvik/system/VMRuntime_Delegate.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dalvik.system; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate used to provide implementation of a select few native methods of {@link VMRuntime} + * <p/> + * Through the layoutlib_create tool, the original native methods of VMRuntime have been replaced + * by calls to methods of the same name in this delegate class. + */ +public class VMRuntime_Delegate { + + // Copied from libcore/libdvm/src/main/java/dalvik/system/VMRuntime + @LayoutlibDelegate + /*package*/ static Object newUnpaddedArray(VMRuntime runtime, Class<?> componentType, + int minLength) { + // Dalvik has 32bit pointers, the array header is 16bytes plus 4bytes for dlmalloc, + // allocations are 8byte aligned so having 4bytes of array data avoids padding. + if (!componentType.isPrimitive()) { + int size = ((minLength & 1) == 0) ? minLength + 1 : minLength; + return java.lang.reflect.Array.newInstance(componentType, size); + } else if (componentType == char.class) { + int bytes = 20 + (2 * minLength); + int alignedUpBytes = (bytes + 7) & -8; + int dataBytes = alignedUpBytes - 20; + int size = dataBytes / 2; + return new char[size]; + } else if (componentType == int.class) { + int size = ((minLength & 1) == 0) ? minLength + 1 : minLength; + return new int[size]; + } else if (componentType == byte.class) { + int bytes = 20 + minLength; + int alignedUpBytes = (bytes + 7) & -8; + int dataBytes = alignedUpBytes - 20; + int size = dataBytes; + return new byte[size]; + } else if (componentType == boolean.class) { + int bytes = 20 + minLength; + int alignedUpBytes = (bytes + 7) & -8; + int dataBytes = alignedUpBytes - 20; + int size = dataBytes; + return new boolean[size]; + } else if (componentType == short.class) { + int bytes = 20 + (2 * minLength); + int alignedUpBytes = (bytes + 7) & -8; + int dataBytes = alignedUpBytes - 20; + int size = dataBytes / 2; + return new short[size]; + } else if (componentType == float.class) { + int size = ((minLength & 1) == 0) ? minLength + 1 : minLength; + return new float[size]; + } else if (componentType == long.class) { + return new long[minLength]; + } else if (componentType == double.class) { + return new double[minLength]; + } else { + assert componentType == void.class; + throw new IllegalArgumentException("Can't allocate an array of void"); + } + } + +} diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java index b3a173f..b8b5fed 100644 --- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java +++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java @@ -18,6 +18,7 @@ package libcore.icu; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import com.ibm.icu.text.DateTimePatternGenerator; +import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; import java.util.Locale; @@ -117,6 +118,11 @@ public class ICU_Delegate { } @LayoutlibDelegate + /*package*/ static int getCurrencyNumericCode(String currencyCode) { + return Currency.getInstance(currencyCode).getNumericCode(); + } + + @LayoutlibDelegate /*package*/ static String getCurrencySymbol(String locale, String currencyCode) { return ""; } @@ -142,12 +148,12 @@ public class ICU_Delegate { } @LayoutlibDelegate - /*package*/ static String getISO3CountryNative(String locale) { + /*package*/ static String getISO3Country(String locale) { return ""; } @LayoutlibDelegate - /*package*/ static String getISO3LanguageNative(String locale) { + /*package*/ static String getISO3Language(String locale) { return ""; } @@ -171,17 +177,6 @@ public class ICU_Delegate { return Locale.getISOCountries(); } - - @LayoutlibDelegate - /*package*/ static String localeForLanguageTag(String languageTag, boolean strict) { - return ""; - } - - @LayoutlibDelegate - /*package*/ static String languageTagForLocale(String locale) { - return ""; - } - @LayoutlibDelegate /*package*/ static boolean initLocaleDataNative(String locale, LocaleData result) { diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk index 98cade9..11390c3 100644 --- a/tools/layoutlib/bridge/tests/Android.mk +++ b/tools/layoutlib/bridge/tests/Android.mk @@ -18,15 +18,24 @@ include $(CLEAR_VARS) # Only compile source java files in this lib. LOCAL_SRC_FILES := $(call all-java-files-under, src) - LOCAL_JAVA_RESOURCE_DIRS := res +LOCAL_JAVACFLAGS := -source 6 -target 6 LOCAL_MODULE := layoutlib-tests LOCAL_MODULE_TAGS := optional -LOCAL_JAVA_LIBRARIES := layoutlib kxml2-2.3.0 junit +LOCAL_JAVA_LIBRARIES := layoutlib \ + kxml2-2.3.0 \ + icu4j \ + layoutlib_api-prebuilt \ + tools-common-prebuilt \ + sdk-common \ + junit include $(BUILD_HOST_JAVA_LIBRARY) +# Copy the jar to DIST_DIR for sdk builds +$(call dist-for-goals, sdk win_sdk, $(LOCAL_INSTALLED_MODULE)) + # Build all sub-directories include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/.gitignore b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/.gitignore new file mode 100644 index 0000000..a2ce0dc --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/.gitignore @@ -0,0 +1,14 @@ +.gradle +local.properties +.idea +.DS_Store +*.iml +# We need the built .class files to load custom views and R class. +# The only way to negate an exclusion is by including every single parent +# and excluding all children of those parents. + +/build/* +!/build/intermediates/ + +/build/intermediates/* +!/build/intermediates/classes/ diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle new file mode 100644 index 0000000..80be12d --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle @@ -0,0 +1,43 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.12.+' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 20 + buildToolsVersion '20' + defaultConfig { + applicationId 'com.android.layoutlib.test.myapplication' + minSdkVersion 19 + targetSdkVersion 20 + versionCode 1 + versionName '1.0' + } + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class Binary files differnew file mode 100644 index 0000000..2b4f7bf --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class Binary files differnew file mode 100644 index 0000000..d252462 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class Binary files differnew file mode 100644 index 0000000..9bab801 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class Binary files differnew file mode 100644 index 0000000..7ad8605 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class Binary files differnew file mode 100644 index 0000000..e9e0a33 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class Binary files differnew file mode 100644 index 0000000..d109302 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class Binary files differnew file mode 100644 index 0000000..816ecc8 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class Binary files differnew file mode 100644 index 0000000..b034b75 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class Binary files differnew file mode 100644 index 0000000..f86b1d3 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class Binary files differnew file mode 100644 index 0000000..8bbae90 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class Binary files differnew file mode 100644 index 0000000..8af745d --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle.properties b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle.properties new file mode 100644 index 0000000..5d08ba7 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Settings specified in this file will override any Gradle settings +# configured through the IDE. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.jar b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 0000000..8c0fb64 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.jar diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..5de946b --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew.bat b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/proguard-rules.pro b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/proguard-rules.pro new file mode 100644 index 0000000..b0fcd2d --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/google/home/deepanshu/ssd/sdk_out/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/androidTest/java/com/android/layoulib/test/myapplication/ApplicationTest.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/androidTest/java/com/android/layoulib/test/myapplication/ApplicationTest.java new file mode 100644 index 0000000..7304af1 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/androidTest/java/com/android/layoulib/test/myapplication/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.android.layoulib.test.myapplication; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> + */ +public class ApplicationTest extends ApplicationTestCase<Application> { + public ApplicationTest() { + super(Application.class); + } +}
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/AndroidManifest.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2067474 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.layoutlib.test.myapplication" > +<!-- If changing package here, update LayoutLibCallBack in tests. --> + <application + android:allowBackup="true" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:theme="@style/AppTheme" > + <activity + android:name="com.android.layoutlib.test.myapplication.MyActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/MyActivity.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/MyActivity.java new file mode 100644 index 0000000..59de457 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/MyActivity.java @@ -0,0 +1,35 @@ +package com.android.layoutlib.test.myapplication; + +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + +public class MyActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.my, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + if (id == R.id.action_settings) { + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/ic_launcher.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 0000000..67481d4 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#ff0000" /> + <size + android:width="20dp" + android:height="20dp" /> +</shape>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/activity.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/activity.xml new file mode 100644 index 0000000..97d1983 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/activity.xml @@ -0,0 +1,21 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + tools:context=".MyActivity"> + + <TextView + android:text="@string/hello_world" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/text1"/> + + <include layout="@layout/layout" + android:layout_below="@+id/text1" + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> +</RelativeLayout> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml new file mode 100644 index 0000000..2704c07 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Some text"/> +</LinearLayout>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/menu/my.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/menu/my.xml new file mode 100644 index 0000000..bea58cc --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/menu/my.xml @@ -0,0 +1,8 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:context=".MyActivity" > + <item android:id="@+id/action_settings" + android:title="@string/action_settings" + android:orderInCategory="100" + android:showAsAction="never" /> +</menu> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/dimens.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/strings.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/strings.xml new file mode 100644 index 0000000..2b7083b --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">My Application</string> + <string name="hello_world">Hello world!</string> + <string name="action_settings">Settings</string> + +</resources> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml new file mode 100644 index 0000000..ff6c9d2 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> + <!-- Customize your theme here. --> + </style> + +</resources> diff --git a/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java index ec4edac..d20fb14 100644 --- a/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java +++ b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java @@ -23,16 +23,6 @@ import junit.framework.TestCase; */ public class Matrix_DelegateTest extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - public void testIdentity() { Matrix m1 = new Matrix(); diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java index d3218db..8b362ec 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java @@ -41,27 +41,29 @@ import junit.framework.TestCase; */ public class TestDelegates extends TestCase { + private List<String> mErrors = new ArrayList<String>(); + public void testNativeDelegates() { final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES; - final int count = classes.length; - for (int i = 0 ; i < count ; i++) { - loadAndCompareClasses(classes[i], classes[i] + "_Delegate"); + mErrors.clear(); + for (String clazz : classes) { + loadAndCompareClasses(clazz, clazz + "_Delegate"); } + assertTrue(getErrors(), mErrors.isEmpty()); } public void testMethodDelegates() { final String[] methods = CreateInfo.DELEGATE_METHODS; - final int count = methods.length; - for (int i = 0 ; i < count ; i++) { - String methodName = methods[i]; - + mErrors.clear(); + for (String methodName : methods) { // extract the class name String className = methodName.substring(0, methodName.indexOf('#')); String targetClassName = className.replace('$', '_') + "_Delegate"; loadAndCompareClasses(className, targetClassName); } + assertTrue(getErrors(), mErrors.isEmpty()); } private void loadAndCompareClasses(String originalClassName, String delegateClassName) { @@ -73,9 +75,9 @@ public class TestDelegates extends TestCase { compare(originalClass, delegateClass); } catch (ClassNotFoundException e) { - fail("Failed to load class: " + e.getMessage()); + mErrors.add("Failed to load class: " + e.getMessage()); } catch (SecurityException e) { - fail("Failed to load class: " + e.getMessage()); + mErrors.add("Failed to load class: " + e.getMessage()); } } @@ -121,27 +123,40 @@ public class TestDelegates extends TestCase { Method delegateMethod = delegateClass.getDeclaredMethod(originalMethod.getName(), parameters); + // check the return type of the methods match. + if (delegateMethod.getReturnType() != originalMethod.getReturnType()) { + mErrors.add( + String.format("Delegate method %1$s.%2$s does not match the " + + "corresponding framework method which returns %3$s", + delegateClass.getName(), + getMethodName(delegateMethod), + originalMethod.getReturnType().getName())); + } + // check that the method has the annotation - assertNotNull( - String.format( - "Delegate method %1$s for class %2$s does not have the @LayoutlibDelegate annotation", - delegateMethod.getName(), - originalClass.getName()), - delegateMethod.getAnnotation(LayoutlibDelegate.class)); + if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) { + mErrors.add( + String.format("Delegate method %1$s for class %2$s does not have the " + + "@LayoutlibDelegate annotation", + delegateMethod.getName(), + originalClass.getName())); + } // check that the method is static - assertTrue( - String.format( - "Delegate method %1$s for class %2$s is not static", - delegateMethod.getName(), - originalClass.getName()), - (delegateMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC); + if ((delegateMethod.getModifiers() & Modifier.STATIC) != Modifier.STATIC) { + mErrors.add( + String.format( + "Delegate method %1$s for class %2$s is not static", + delegateMethod.getName(), + originalClass.getName()) + ); + } // add the method as checked. checkedDelegateMethods.add(delegateMethod); } catch (NoSuchMethodException e) { String name = getMethodName(originalMethod, parameters); - fail(String.format("Missing %1$s.%2$s", delegateClass.getName(), name)); + mErrors.add(String.format("Missing %1$s.%2$s", delegateClass.getName(), name)); } } @@ -158,12 +173,12 @@ public class TestDelegates extends TestCase { continue; } - assertTrue( - String.format( - "Delegate method %1$s.%2$s is not used anymore and must be removed", - delegateClass.getName(), - getMethodName(delegateMethod)), - checkedDelegateMethods.contains(delegateMethod)); + if (!checkedDelegateMethods.contains(delegateMethod)) { + mErrors.add(String.format( + "Delegate method %1$s.%2$s is not used anymore and must be removed", + delegateClass.getName(), + getMethodName(delegateMethod))); + } } } @@ -194,4 +209,12 @@ public class TestDelegates extends TestCase { return sb.toString(); } + + private String getErrors() { + StringBuilder s = new StringBuilder(); + for (String error : mErrors) { + s.append(error).append('\n'); + } + return s.toString(); + } } diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java index 865a008..92fcf90 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java @@ -24,17 +24,6 @@ import org.xmlpull.v1.XmlPullParser; import junit.framework.TestCase; public class BridgeXmlBlockParserTest extends TestCase { - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - public void testXmlBlockParser() throws Exception { XmlPullParser parser = ParserFactory.create( diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java new file mode 100644 index 0000000..a2588a6 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.intensive; + +import com.android.annotations.NonNull; +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.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.io.FolderWrapper; +import com.android.layoutlib.bridge.Bridge; +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.utils.ILogger; + +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.FileFilter; +import java.net.URL; +import java.util.Arrays; +import java.util.Comparator; + +import static org.junit.Assert.fail; + +/** + * This is a set of tests that loads all the framework resources and a project checked in this + * test's resources. The main dependencies + * are: + * 1. Fonts directory. + * 2. Framework Resources. + * 3. App resources. + * 4. build.prop file + * + * These are configured by two variables set in the system properties. + * + * 1. platform.dir: This is the directory for the current platform in the built SDK + * (.../sdk/platforms/android-<version>). + * + * The fonts are platform.dir/data/fonts. + * The Framework resources are platform.dir/data/res. + * build.prop is at platform.dir/build.prop. + * + * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this + * falls back to getClass().getProtectionDomain().getCodeSource().getLocation() + * + * The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res + */ +public class Main { + + 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; + private static final String TEST_RES_DIR; + private static final String APP_TEST_RES = "/testApp/MyApplication/src/main/res"; + + private LayoutLog mLayoutLibLog; + private FrameworkResources mFrameworkRepo; + private ResourceRepository mProjectResources; + private ILogger mLogger; + private Bridge mBridge; + + static { + // Test that System Properties are properly set. + PLATFORM_DIR = getPlatformDir(); + if (PLATFORM_DIR == null) { + fail(String.format("System Property %1$s not properly set. The value is %2$s", + PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY))); + } + + TEST_RES_DIR = getTestResDir(); + if (TEST_RES_DIR == null) { + fail(String.format("System property %1$s.dir not properly set. The value is %2$s", + RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY))); + } + } + + private static String getPlatformDir() { + String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY); + if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) { + return platformDir; + } + // System Property not set. Try to find the directory in the build directory. + String androidHostOut = System.getenv("ANDROID_HOST_OUT"); + if (androidHostOut != null) { + platformDir = getPlatformDirFromHostOut(new File(androidHostOut)); + if (platformDir != null) { + return platformDir; + } + } + String workingDirString = System.getProperty("user.dir"); + File workingDir = new File(workingDirString); + // Test if workingDir is android checkout root. + platformDir = getPlatformDirFromRoot(workingDir); + if (platformDir != null) { + return platformDir; + } + // Test if workingDir is platform/frameworks/base/tools/layoutlib. That is, root should be + // workingDir/../../../../ (4 levels up) + File currentDir = workingDir; + for (int i = 0; i < 4; i++) { + if (currentDir != null) { + currentDir = currentDir.getParentFile(); + } + } + return currentDir == null ? null : getPlatformDirFromRoot(currentDir); + } + + private static String getPlatformDirFromRoot(File root) { + if (!root.isDirectory()) { + return null; + } + File out = new File(root, "out"); + if (!out.isDirectory()) { + return null; + } + File host = new File(out, "host"); + if (!host.isDirectory()) { + return null; + } + File[] hosts = host.listFiles(new FileFilter() { + @Override + public boolean accept(File path) { + return path.isDirectory() && (path.getName().startsWith("linux-") || path.getName() + .startsWith("darwin-")); + } + }); + for (File hostOut : hosts) { + String platformDir = getPlatformDirFromHostOut(hostOut); + if (platformDir != null) { + return platformDir; + } + } + return null; + } + + private static String getPlatformDirFromHostOut(File out) { + if (!out.isDirectory()) { + return null; + } + File sdkDir = new File(out, "sdk" + File.separator + "sdk"); + if (!sdkDir.isDirectory()) { + // The directory we thought that should contain the sdk is not a directory. + return null; + } + File[] possibleSdks = sdkDir.listFiles(new FileFilter() { + @Override + public boolean accept(File path) { + return path.isDirectory() && path.getName().contains("android-sdk"); + } + }); + for (File possibleSdk : possibleSdks) { + File platformsDir = new File(possibleSdk, "platforms"); + File[] platforms = platformsDir.listFiles(new FileFilter() { + @Override + public boolean accept(File path) { + return path.isDirectory() && path.getName().startsWith("android-"); + } + }); + if (platforms == null || platforms.length == 0) { + continue; + } + Arrays.sort(platforms, new Comparator<File>() { + // Codenames before ints. Higher APIs precede lower. + @Override + public int compare(File o1, File o2) { + final int MAX_VALUE = 1000; + String suffix1 = o1.getName().substring("android-".length()); + String suffix2 = o2.getName().substring("android-".length()); + int suff1, suff2; + try { + suff1 = Integer.parseInt(suffix1); + } catch (NumberFormatException e) { + suff1 = MAX_VALUE; + } + try { + suff2 = Integer.parseInt(suffix2); + } catch (NumberFormatException e) { + suff2 = MAX_VALUE; + } + if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) { + return suff2 - suff1; + } + return suffix2.compareTo(suffix1); + } + }); + return platforms[0].getAbsolutePath(); + } + return null; + } + + private static String getTestResDir() { + String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY); + if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) { + return resourceDir; + } + // TEST_RES_DIR not explicitly set. Fallback to the class's source location. + try { + URL location = Main.class.getProtectionDomain().getCodeSource().getLocation(); + return new File(location.getPath()).exists() ? location.getPath() : null; + } catch (NullPointerException e) { + // Prevent a lot of null checks by just catching the exception. + return null; + } + } + /** + * Initialize the bridge and the resource maps. + */ + @Before + public void setUp() { + File data_dir = new File(PLATFORM_DIR, "data"); + File res = new File(data_dir, "res"); + mFrameworkRepo = new FrameworkResources(new FolderWrapper(res)); + mFrameworkRepo.loadResources(); + mFrameworkRepo.loadPublicResources(getLogger()); + + mProjectResources = + new ResourceRepository(new FolderWrapper(TEST_RES_DIR + APP_TEST_RES), false) { + @NonNull + @Override + protected ResourceItem createResourceItem(String name) { + return new ResourceItem(name); + } + }; + mProjectResources.loadResources(); + + 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"); + mBridge = new Bridge(); + mBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation, + ConfigGenerator.getEnumMap(attrs), getLayoutLog()); + } + + /** + * Create a new rendering session and test that rendering /layout/activity.xml on nexus 5 + * doesn't throw any exceptions. + */ + @Test + public void testRendering() throws ClassNotFoundException { + // Create the layout pull parser. + LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/activity.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); + layoutLibCallback.initResources(); + // TODO: Set up action bar handler properly to test menu rendering. + // Create session params. + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, layoutLibCallback); + RenderSession session = mBridge.createSession(params); + if (!session.getResult().isSuccess()) { + getLogger().error(session.getResult().getException(), + session.getResult().getErrorMessage()); + } + // Render the session with a timeout of 50s. + Result renderResult = session.render(50000); + if (!renderResult.isSuccess()) { + getLogger().error(session.getResult().getException(), + session.getResult().getErrorMessage()); + } + } + + /** + * Uses Theme.Material and Target sdk version as 21. + */ + private SessionParams getSessionParams(LayoutPullParser layoutParser, + ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback) { + FolderConfiguration config = configGenerator.getFolderConfig(); + ResourceResolver resourceResolver = + ResourceResolver.create(mProjectResources.getConfiguredResources(config), + mFrameworkRepo.getConfiguredResources(config), "Theme.Material", false); + + return new SessionParams( + layoutParser, + RenderingMode.NORMAL, + null /*used for caching*/, + configGenerator.getHardwareConfig(), + resourceResolver, + layoutLibCallback, + 0, + 21, // TODO: Make it more configurable to run tests for various versions. + getLayoutLog()); + } + + private LayoutLog getLayoutLog() { + if (mLayoutLibLog == null) { + mLayoutLibLog = new LayoutLog() { + @Override + public void warning(String tag, String message, Object data) { + System.out.println("Warning " + tag + ": " + message); + fail(message); + } + + @Override + public void fidelityWarning(String tag, String message, Throwable throwable, + Object data) { + System.out.println("FidelityWarning " + tag + ": " + message); + if (throwable != null) { + throwable.printStackTrace(); + } + fail(message); + } + + @Override + public void error(String tag, String message, Object data) { + System.out.println("Error " + tag + ": " + message); + fail(message); + } + + @Override + public void error(String tag, String message, Throwable throwable, Object data) { + System.out.println("Error " + tag + ": " + message); + if (throwable != null) { + throwable.printStackTrace(); + } + fail(message); + } + }; + } + return mLayoutLibLog; + } + + private ILogger getLogger() { + if (mLogger == null) { + mLogger = new ILogger() { + @Override + public void error(Throwable t, String msgFormat, Object... args) { + if (t != null) { + t.printStackTrace(); + } + fail(String.format(msgFormat, args)); + } + + @Override + public void warning(String msgFormat, Object... args) { + fail(String.format(msgFormat, args)); + } + + @Override + public void info(String msgFormat, Object... args) { + // pass. + } + + @Override + public void verbose(String msgFormat, Object... args) { + // pass. + } + }; + } + return mLogger; + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java new file mode 100644 index 0000000..a5c3202 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.intensive.setup; + +import com.android.ide.common.rendering.api.HardwareConfig; +import com.android.ide.common.resources.configuration.CountryCodeQualifier; +import com.android.ide.common.resources.configuration.DensityQualifier; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.KeyboardStateQualifier; +import com.android.ide.common.resources.configuration.LanguageQualifier; +import com.android.ide.common.resources.configuration.LayoutDirectionQualifier; +import com.android.ide.common.resources.configuration.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.NetworkCodeQualifier; +import com.android.ide.common.resources.configuration.NightModeQualifier; +import com.android.ide.common.resources.configuration.RegionQualifier; +import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; +import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; +import com.android.ide.common.resources.configuration.ScreenRatioQualifier; +import com.android.ide.common.resources.configuration.ScreenSizeQualifier; +import com.android.ide.common.resources.configuration.TextInputMethodQualifier; +import com.android.ide.common.resources.configuration.TouchScreenQualifier; +import com.android.ide.common.resources.configuration.UiModeQualifier; +import com.android.ide.common.resources.configuration.VersionQualifier; +import com.android.resources.Density; +import com.android.resources.Keyboard; +import com.android.resources.KeyboardState; +import com.android.resources.Navigation; +import com.android.resources.NightMode; +import com.android.resources.ScreenOrientation; +import com.android.resources.ScreenRatio; +import com.android.resources.ScreenSize; +import com.android.resources.TouchScreen; +import com.android.resources.UiMode; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +import com.google.android.collect.Maps; + +/** + * Provides {@link FolderConfiguration} and {@link HardwareConfig} for various devices. Also + * provides utility methods to parse build.prop and attrs.xml to generate the appropriate maps. + */ +@SuppressWarnings("UnusedDeclaration") // For the pre-configured nexus generators. +public class ConfigGenerator { + + public static final ConfigGenerator NEXUS_4 = new ConfigGenerator(); + + public static final ConfigGenerator NEXUS_5 = new ConfigGenerator() + .setScreenHeight(1920) + .setScreenWidth(1080) + .setXdpi(445) + .setYdpi(445) + .setOrientation(ScreenOrientation.PORTRAIT) + .setDensity(Density.XXHIGH) + .setRatio(ScreenRatio.NOTLONG) + .setSize(ScreenSize.NORMAL) + .setKeyboard(Keyboard.NOKEY) + .setTouchScreen(TouchScreen.FINGER) + .setKeyboardState(KeyboardState.SOFT) + .setSoftButtons(true) + .setNavigation(Navigation.NONAV); + + public static final ConfigGenerator NEXUS_7 = new ConfigGenerator() + .setScreenHeight(1920) + .setScreenWidth(1200) + .setXdpi(323) + .setYdpi(323) + .setOrientation(ScreenOrientation.PORTRAIT) + .setDensity(Density.XHIGH) + .setRatio(ScreenRatio.NOTLONG) + .setSize(ScreenSize.LARGE) + .setKeyboard(Keyboard.NOKEY) + .setTouchScreen(TouchScreen.FINGER) + .setKeyboardState(KeyboardState.SOFT) + .setSoftButtons(true) + .setNavigation(Navigation.NONAV); + + public static final ConfigGenerator NEXUS_10 = new ConfigGenerator() + .setScreenHeight(1600) + .setScreenWidth(2560) + .setXdpi(300) + .setYdpi(300) + .setOrientation(ScreenOrientation.LANDSCAPE) + .setDensity(Density.XHIGH) + .setRatio(ScreenRatio.NOTLONG) + .setSize(ScreenSize.XLARGE) + .setKeyboard(Keyboard.NOKEY) + .setTouchScreen(TouchScreen.FINGER) + .setKeyboardState(KeyboardState.SOFT) + .setSoftButtons(true) + .setNavigation(Navigation.NONAV); + + private static final String TAG_ATTR = "attr"; + private static final String TAG_ENUM = "enum"; + private static final String TAG_FLAG = "flag"; + private static final String ATTR_NAME = "name"; + private static final String ATTR_VALUE = "value"; + + // Device Configuration. Defaults are for a Nexus 4 device. + private int mScreenHeight = 1280; + private int mScreenWidth = 768; + private int mXdpi = 320; + private int mYdpi = 320; + private ScreenOrientation mOrientation = ScreenOrientation.PORTRAIT; + private Density mDensity = Density.XHIGH; + private ScreenRatio mRatio = ScreenRatio.NOTLONG; + private ScreenSize mSize = ScreenSize.NORMAL; + private Keyboard mKeyboard = Keyboard.NOKEY; + private TouchScreen mTouchScreen = TouchScreen.FINGER; + private KeyboardState mKeyboardState = KeyboardState.SOFT; + private boolean mSoftButtons = true; + private Navigation mNavigation = Navigation.NONAV; + + public FolderConfiguration getFolderConfig() { + FolderConfiguration config = new FolderConfiguration(); + config.createDefault(); + config.setDensityQualifier(new DensityQualifier(mDensity)); + config.setNavigationMethodQualifier(new NavigationMethodQualifier(mNavigation)); + if (mScreenWidth > mScreenHeight) { + config.setScreenDimensionQualifier(new ScreenDimensionQualifier(mScreenWidth, + mScreenHeight)); + } else { + config.setScreenDimensionQualifier(new ScreenDimensionQualifier(mScreenHeight, + mScreenWidth)); + } + config.setScreenRatioQualifier(new ScreenRatioQualifier(mRatio)); + config.setScreenSizeQualifier(new ScreenSizeQualifier(mSize)); + config.setTextInputMethodQualifier(new TextInputMethodQualifier(mKeyboard)); + config.setTouchTypeQualifier(new TouchScreenQualifier(mTouchScreen)); + config.setKeyboardStateQualifier(new KeyboardStateQualifier(mKeyboardState)); + config.setScreenOrientationQualifier(new ScreenOrientationQualifier(mOrientation)); + + config.updateScreenWidthAndHeight(); + + // some default qualifiers. + config.setUiModeQualifier(new UiModeQualifier(UiMode.NORMAL)); + config.setNightModeQualifier(new NightModeQualifier(NightMode.NOTNIGHT)); + config.setCountryCodeQualifier(new CountryCodeQualifier()); + config.setLanguageQualifier(new LanguageQualifier()); + config.setLayoutDirectionQualifier(new LayoutDirectionQualifier()); + config.setNetworkCodeQualifier(new NetworkCodeQualifier()); + config.setRegionQualifier(new RegionQualifier()); + config.setVersionQualifier(new VersionQualifier()); + return config; + } + + public HardwareConfig getHardwareConfig() { + return new HardwareConfig(mScreenWidth, mScreenHeight, mDensity, mXdpi, mYdpi, mSize, + mOrientation, mSoftButtons); + } + + public static Map<String, String> loadProperties(File path) { + Properties p = new Properties(); + Map<String, String> map = Maps.newHashMap(); + try { + p.load(new FileInputStream(path)); + for (String key : p.stringPropertyNames()) { + map.put(key, p.getProperty(key)); + } + } catch (IOException e) { + e.printStackTrace(); + } + return map; + } + + public static Map<String, Map<String, Integer>> getEnumMap(File path) { + Map<String, Map<String, Integer>> map = Maps.newHashMap(); + try { + XmlPullParser xmlPullParser = XmlPullParserFactory.newInstance().newPullParser(); + xmlPullParser.setInput(new FileInputStream(path), null); + int eventType = xmlPullParser.getEventType(); + String attr = null; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if (TAG_ATTR.equals(xmlPullParser.getName())) { + attr = xmlPullParser.getAttributeValue(null, ATTR_NAME); + } else if (TAG_ENUM.equals(xmlPullParser.getName()) + || TAG_FLAG.equals(xmlPullParser.getName())) { + String name = xmlPullParser.getAttributeValue(null, ATTR_NAME); + String value = xmlPullParser.getAttributeValue(null, ATTR_VALUE); + // Integer.decode cannot handle "ffffffff", see JDK issue 6624867 + int i = (int) (long) Long.decode(value); + assert attr != null; + Map<String, Integer> attributeMap = map.get(attr); + if (attributeMap == null) { + attributeMap = Maps.newHashMap(); + map.put(attr, attributeMap); + } + attributeMap.put(name, i); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (TAG_ATTR.equals(xmlPullParser.getName())) { + attr = null; + } + } + eventType = xmlPullParser.next(); + } + } catch (XmlPullParserException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return map; + } + + // Methods to set the configuration values. + + public ConfigGenerator setScreenHeight(int height) { + mScreenHeight = height; + return this; + } + + public ConfigGenerator setScreenWidth(int width) { + mScreenWidth = width; + return this; + } + + public ConfigGenerator setXdpi(int xdpi) { + mXdpi = xdpi; + return this; + } + + public ConfigGenerator setYdpi(int ydpi) { + mYdpi = ydpi; + return this; + } + + public ConfigGenerator setOrientation(ScreenOrientation orientation) { + mOrientation = orientation; + return this; + } + + public ConfigGenerator setDensity(Density density) { + mDensity = density; + return this; + } + + public ConfigGenerator setRatio(ScreenRatio ratio) { + mRatio = ratio; + return this; + } + + public ConfigGenerator setSize(ScreenSize size) { + mSize = size; + return this; + } + + public ConfigGenerator setKeyboard(Keyboard keyboard) { + mKeyboard = keyboard; + return this; + } + + public ConfigGenerator setTouchScreen(TouchScreen touchScreen) { + mTouchScreen = touchScreen; + return this; + } + + public ConfigGenerator setKeyboardState(KeyboardState state) { + mKeyboardState = state; + return this; + } + + public ConfigGenerator setSoftButtons(boolean softButtons) { + mSoftButtons = softButtons; + return this; + } + + public ConfigGenerator setNavigation(Navigation navigation) { + mNavigation = navigation; + return this; + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java new file mode 100644 index 0000000..565e881 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.intensive.setup; + +import com.android.SdkConstants; +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.IProjectCallback; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.resources.ResourceType; +import com.android.ide.common.resources.IntArrayWrapper; +import com.android.util.Pair; +import com.android.utils.ILogger; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Map; + +import com.google.android.collect.Maps; + +@SuppressWarnings("deprecation") // For Pair +public class LayoutLibTestCallback extends ClassLoader implements IProjectCallback { + + private static final String PROJECT_CLASSES_LOCATION = "/testApp/MyApplication/build/intermediates/classes/debug/"; + private static final String PACKAGE_NAME = "com.android.layoutlib.test.myapplication"; + + private final Map<Integer, Pair<ResourceType, String>> mProjectResources = Maps.newHashMap(); + private final Map<IntArrayWrapper, String> mStyleableValueToNameMap = Maps.newHashMap(); + private final Map<ResourceType, Map<String, Integer>> mResources = Maps.newHashMap(); + private final Map<String, Class<?>> mClasses = Maps.newHashMap(); + private final ILogger mLog; + private final ActionBarCallback mActionBarCallback = new ActionBarCallback(); + + public LayoutLibTestCallback(ILogger logger) { + mLog = logger; + } + + public void initResources() throws ClassNotFoundException { + Class<?> rClass = loadClass(PACKAGE_NAME + ".R"); + Class<?>[] nestedClasses = rClass.getDeclaredClasses(); + for (Class<?> resClass : nestedClasses) { + final ResourceType resType = ResourceType.getEnum(resClass.getSimpleName()); + + if (resType != null) { + final Map<String, Integer> resName2Id = Maps.newHashMap(); + mResources.put(resType, resName2Id); + + for (Field field : resClass.getDeclaredFields()) { + final int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers)) { // May not be final in library projects + final Class<?> type = field.getType(); + try { + if (type.isArray() && type.getComponentType() == int.class) { + mStyleableValueToNameMap.put( + new IntArrayWrapper((int[]) field.get(null)), + field.getName()); + } else if (type == int.class) { + final Integer value = (Integer) field.get(null); + mProjectResources.put(value, Pair.of(resType, field.getName())); + resName2Id.put(field.getName(), value); + } else { + mLog.error(null, "Unknown field type in R class: %1$s", type); + } + } catch (IllegalAccessException ignored) { + mLog.error(ignored, "Malformed R class: %1$s", PACKAGE_NAME + ".R"); + } + } + } + } + } + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + Class<?> aClass = mClasses.get(name); + if (aClass != null) { + return aClass; + } + String pathName = PROJECT_CLASSES_LOCATION.concat(name.replace('.', '/')).concat(".class"); + InputStream classInputStream = getClass().getResourceAsStream(pathName); + if (classInputStream == null) { + throw new ClassNotFoundException("Unable to find class " + name + " at " + pathName); + } + byte[] data; + try { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + data = new byte[16384]; + while ((nRead = classInputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + data = buffer.toByteArray(); + } catch (IOException e) { + // Wrap the exception with ClassNotFoundException so that caller can deal with it. + throw new ClassNotFoundException("Unable to load class " + name, e); + } + aClass = defineClass(name, data, 0, data.length); + mClasses.put(name, aClass); + return aClass; + } + + @Override + public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs) + throws Exception { + Class<?> viewClass = findClass(name); + Constructor<?> viewConstructor = viewClass.getConstructor(constructorSignature); + viewConstructor.setAccessible(true); + return viewConstructor.newInstance(constructorArgs); + } + + @Override + public String getNamespace() { + return String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, + PACKAGE_NAME); + } + + @Override + public Pair<ResourceType, String> resolveResourceId(int id) { + return mProjectResources.get(id); + } + + @Override + public String resolveResourceId(int[] id) { + return mStyleableValueToNameMap.get(new IntArrayWrapper(id)); + } + + @Override + public Integer getResourceId(ResourceType type, String name) { + return mResources.get(type).get(name); + } + + @Override + public ILayoutPullParser getParser(String layoutName) { + org.junit.Assert.fail("This method shouldn't be called by this version of LayoutLib."); + return null; + } + + @Override + public ILayoutPullParser getParser(ResourceValue layoutResource) { + return new LayoutPullParser(new File(layoutResource.getValue())); + } + + @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() { + return mActionBarCallback; + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java new file mode 100644 index 0000000..c79b662 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.intensive.setup; + +import com.android.ide.common.rendering.api.ILayoutPullParser; + +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOError; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import static com.android.SdkConstants.ATTR_IGNORE; +import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW; +import static com.android.SdkConstants.GRID_VIEW; +import static com.android.SdkConstants.LIST_VIEW; +import static com.android.SdkConstants.SPINNER; +import static com.android.SdkConstants.TOOLS_URI; + +public class LayoutPullParser extends KXmlParser implements ILayoutPullParser{ + + /** + * @param layoutPath Must start with '/' and be relative to test resources. + */ + public LayoutPullParser(String layoutPath) { + assert layoutPath.startsWith("/"); + try { + init(getClass().getResourceAsStream(layoutPath)); + } catch (XmlPullParserException e) { + throw new IOError(e); + } + } + + /** + * @param layoutFile Path of the layout xml file on disk. + */ + public LayoutPullParser(File layoutFile) { + try { + init(new FileInputStream(layoutFile)); + } catch (XmlPullParserException e) { + throw new IOError(e); + } catch (FileNotFoundException e) { + throw new IOError(e); + } + } + + private void init(InputStream stream) throws XmlPullParserException { + setFeature(FEATURE_PROCESS_NAMESPACES, true); + setInput(stream, null); + } + + @Override + public Object getViewCookie() { + // TODO: Implement this properly. + String name = super.getName(); + if (name == null) { + return null; + } + + // Store tools attributes if this looks like a layout we'll need adapter view + // bindings for in the LayoutlibCallback. + if (LIST_VIEW.equals(name) || EXPANDABLE_LIST_VIEW.equals(name) || GRID_VIEW.equals(name) || SPINNER.equals(name)) { + Map<String, String> map = null; + int count = getAttributeCount(); + for (int i = 0; i < count; i++) { + String namespace = getAttributeNamespace(i); + if (namespace != null && namespace.equals(TOOLS_URI)) { + String attribute = getAttributeName(i); + if (attribute.equals(ATTR_IGNORE)) { + continue; + } + if (map == null) { + map = new HashMap<String, String>(4); + } + map.put(attribute, getAttributeValue(i)); + } + } + + return map; + } + + return null; + } + + @Override + @Deprecated + public ILayoutPullParser getParser(String layoutName) { + // Studio returns null. + return null; + } + +} diff --git a/tools/layoutlib/create/.classpath b/tools/layoutlib/create/.classpath index cd8bb0d..25c3b3e 100644 --- a/tools/layoutlib/create/.classpath +++ b/tools/layoutlib/create/.classpath @@ -4,6 +4,6 @@ <classpathentry excluding="mock_data/" kind="src" path="tests"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> - <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_PLAT/prebuilts/tools/common/asm-tools/src-4.0.zip"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/asm/asm-4.0.jar" sourcepath="/ANDROID_PLAT_SRC/prebuilts/misc/common/asm/src.zip"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk index 9bd48ab..e6f0bc3 100644 --- a/tools/layoutlib/create/Android.mk +++ b/tools/layoutlib/create/Android.mk @@ -26,3 +26,6 @@ LOCAL_MODULE := layoutlib_create include $(BUILD_HOST_JAVA_LIBRARY) +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt index ef2b185..727b194 100644 --- a/tools/layoutlib/create/README.txt +++ b/tools/layoutlib/create/README.txt @@ -4,46 +4,46 @@ - Description - --------------- -Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor -to perform layout. +Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor to perform +layout. - Usage - --------- - ./layoutlib_create path/to/android.jar destination.jar + ./layoutlib_create destination.jar path/to/android1.jar path/to/android2.jar - Design Overview - ------------------- -Layoutlib_create uses the "android.jar" containing all the Java code used by Android -as generated by the Android build, right before the classes are converted to a DEX format. +Layoutlib_create uses a few jars from the framework containing the Java code used by Android as +generated by the Android build, right before the classes are converted to a DEX format. -The Android JAR can't be used directly in Eclipse: -- it contains references to native code (which we want to avoid in Eclipse), -- some classes need to be overridden, for example all the drawing code that is - replaced by Java 2D calls in Eclipse. -- some of the classes that need to be changed are final and/or we need access - to their private internal state. +These jars can't be used directly in Eclipse as: +- they contains references to native code (which we want to avoid in Eclipse), +- some classes need to be overridden, for example all the drawing code that is replaced by Java 2D + calls in Eclipse. +- some of the classes that need to be changed are final and/or we need access to their private + internal state. Consequently this tool: - parses the input JAR, - modifies some of the classes directly using some bytecode manipulation, - filters some packages and removes those we don't want in the output JAR, - injects some new classes, -- generates a modified JAR file that is suitable for the Android plugin - for Eclipse to perform rendering. +- generates a modified JAR file that is suitable for the Android plugin for Eclipse to perform + rendering. The ASM library is used to do the bytecode modification using its visitor pattern API. -The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the -configuration is done in the main() method and the CreateInfo structure is expected to -change with the Android platform as new classes are added, changed or removed. +The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the configuration +is done in the main() method and the CreateInfo structure is expected to change with the Android +platform as new classes are added, changed or removed. Some configuration that may be platform +dependent is also present elsewhere in code. -The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the -platform, that provides all the necessary missing implementation for rendering graphics -in Eclipse. +The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the platform, that +provides all the necessary missing implementation for rendering graphics in Eclipse. @@ -58,97 +58,102 @@ The tool works in two phases: - Analyzer ---------- -The goal of the analyzer is to create a graph of all the classes from the input JAR -with their dependencies and then only keep the ones we want. +The goal of the analyzer is to create a graph of all the classes from the input JAR with their +dependencies and then only keep the ones we want. -To do that, the analyzer is created with a list of base classes to keep -- everything -that derives from these is kept. Currently the one such class is android.view.View: -since we want to render layouts, anything that is sort of a view needs to be kept. +To do that, the analyzer is created with a list of base classes to keep -- everything that derives +from these is kept. Currently the one such class is android.view.View: since we want to render +layouts, anything that is sort of a view needs to be kept. -The analyzer is also given a list of class names to keep in the output. -This is done using shell-like glob patterns that filter on the fully-qualified -class names, for example "android.*.R**" ("*" does not matches dots whilst "**" does, -and "." and "$" are interpreted as-is). -In practice we almost but not quite request the inclusion of full packages. +The analyzer is also given a list of class names to keep in the output. This is done using +shell-like glob patterns that filter on the fully-qualified class names, for example "android.*.R**" +("*" does not matches dots whilst "**" does, and "." and "$" are interpreted as-is). In practice we +almost but not quite request the inclusion of full packages. -The analyzer is also given a list of classes to exclude. A fake implementation of these -classes is injected by the Generator. +The analyzer is also given a list of classes to exclude. A fake implementation of these classes is +injected by the Generator. -With this information, the analyzer parses the input zip to find all the classes. -All classes deriving from the requested bases classes are kept. -All classes which name matched the glob pattern are kept. -The analysis then finds all the dependencies of the classes that are to be kept -using an ASM visitor on the class, the field types, the method types and annotations types. -Classes that belong to the current JRE are excluded. +With this information, the analyzer parses the input zip to find all the classes. All classes +deriving from the requested bases classes are kept. All classes whose name match the glob pattern +are kept. The analysis then finds all the dependencies of the classes that are to be kept using an +ASM visitor on the class, the field types, the method types and annotations types. Classes that +belong to the current JRE are excluded. -The output of the analyzer is a set of ASM ClassReader instances which are then -fed to the generator. +The output of the analyzer is a set of ASM ClassReader instances which are then fed to the +generator. - Generator ----------- -The generator is constructed from a CreateInfo struct that acts as a config file -and lists: -- the classes to inject in the output JAR -- these classes are directly implemented - in layoutlib_create and will be used to interface with the renderer in Eclipse. +The generator is constructed from a CreateInfo struct that acts as a config file and lists: +- the classes to inject in the output JAR -- these classes are directly implemented in + layoutlib_create and will be used to interface with the renderer in Eclipse. - specific methods to override (see method stubs details below). - specific methods for which to delegate calls. - specific methods to remove based on their return type. - specific classes to rename. - specific classes to refactor. -Each of these are specific strategies we use to be able to modify the Android code -to fit within the Eclipse renderer. These strategies are explained beow. +Each of these are specific strategies we use to be able to modify the Android code to fit within the +Eclipse renderer. These strategies are explained below. -The core method of the generator is transform(): it takes an input ASM ClassReader -and modifies it to produce a byte array suitable for the final JAR file. +The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it +to produce a byte array suitable for the final JAR file. The first step of the transformation is to implement the method delegates. -The TransformClassAdapter is then used to process the potentially renamed class. -All protected or private classes are market as public. -All classes are made non-final. -Interfaces are left as-is. +The TransformClassAdapter is then used to process the potentially renamed class. All protected or +private classes are market as public. All classes are made non-final. Interfaces are left as-is. -If a method has a return type that must be erased, the whole method is skipped. -Methods are also changed from protected/private to public. -The code of the methods is then kept as-is, except for native methods which are -replaced by a stub. Methods that are to be overridden are also replaced by a stub. +If a method has a return type that must be erased, the whole method is skipped. Methods are also +changed from protected/private to public. The code of the methods is then kept as-is, except for +native methods which are replaced by a stub. Methods that are to be overridden are also replaced by +a stub. Finally fields are also visited and changed from protected/private to public. -The next step of the transformation is changing the name of the class in case -we requested the class to be renamed. This uses the RenameClassAdapter to also rename -all inner classes and references in methods and types. Note that other classes are -not transformed and keep referencing the original name. - -The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but -updates the references in all classes. This is used to update the references of classes -in the java package that were added in the Dalvik VM but are not a part of the standard -JVM. The existing classes are modified to update all references to these non-standard -classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is -injected. - -The ClassAdapters are chained together to achieve the desired output. (Look at section -2.2.7 Transformation chains in the asm user guide, link in the References.) The order of -execution of these is: +The next step of the transformation is changing the name of the class in case we requested the class +to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in +methods and types. Note that other classes are not transformed and keep referencing the original +name. + +The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but updates the +references in all classes. This is used to update the references of classes in the java package that +were added in the Dalvik VM but are not a part of the Desktop VM. The existing classes are +modified to update all references to these non-desktop classes. An alternate implementation of +these (com.android.tools.layoutlib.java.*) is injected. + +RenameClassAdapter and RefactorClassAdapter both inherit from AbstractClassAdapter which changes the +class version (version of the JDK used to compile the class) to 50 (corresponding to Java 6), if the +class was originally compiled with Java 7 (version 51). This is because we don't currently generate +the StackMapTable correctly and Java 7 VM enforces that classes with version greater than 51 have +valid StackMapTable. As a side benefit of this, we can continue to support Java 6 because Java 7 on +Mac has horrible font rendering support. + +ReplaceMethodCallsAdapter replaces calls to certain methods. This is different from the +DelegateMethodAdapter since it doesn't preserve the original copy of the method and more importantly +changes the calls to a method in each class instead of changing the implementation of the method. +This is useful for methods in the Java namespace where we cannot add delegates. The configuration +for this is not done through the CreateInfo class, but done in the ReplaceMethodAdapter. + +The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7 +Transformation chains in the asm user guide, link in the References.) The order of execution of +these is: ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] -> -RefactorClassAdapter -> ClassWriter +RefactorClassAdapter -> [ReplaceMethodCallsAdapter] -> ClassWriter - Method stubs -------------- -As indicated above, all native and overridden methods are replaced by a stub. -We don't have the code to replace with in layoutlib_create. -Instead the StubMethodAdapter replaces the code of the method by a call to -OverrideMethod.invokeX(). When using the final JAR, the bridge can register +As indicated above, all native and overridden methods are replaced by a stub. We don't have the +code to replace with in layoutlib_create. Instead the StubMethodAdapter replaces the code of the +method by a call to OverrideMethod.invokeX(). When using the final JAR, the bridge can register listeners from these overridden method calls based on the method signatures. -The listeners are currently pretty basic: we only pass the signature of the -method being called, its caller object and a flag indicating whether the -method was native. We do not currently provide the parameters. The listener -can however specify the return value of the overridden method. +The listeners are currently pretty basic: we only pass the signature of the method being called, its +caller object and a flag indicating whether the method was native. We do not currently provide the +parameters. The listener can however specify the return value of the overridden method. This strategy is now obsolete and replaced by the method delegates. @@ -156,97 +161,89 @@ This strategy is now obsolete and replaced by the method delegates. - Strategies ------------ -We currently have 6 strategies to deal with overriding the rendering code -and make it run in Eclipse. Most of these strategies are implemented hand-in-hand -by the bridge (which runs in Eclipse) and the generator. +We currently have 6 strategies to deal with overriding the rendering code and make it run in +Eclipse. Most of these strategies are implemented hand-in-hand by the bridge (which runs in Eclipse) +and the generator. 1- Class Injection This is the easiest: we currently inject the following classes: -- OverrideMethod and its associated MethodListener and MethodAdapter are used - to intercept calls to some specific methods that are stubbed out and change - their return value. -- CreateInfo class, which configured the generator. Not used yet, but could - in theory help us track what the generator changed. -- AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new - classes are injected. The implementation for these classes has been taken from - Android's libcore (platform/libcore/luni/src/main/java/java/...). -- Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM. - They are added to the Dalvik VM for performance reasons. An implementation that is very - close to the original (which is at platform/libcore/luni/src/main/java/...) is injected. - Since these classees were in part of the java package, where we can't inject classes, - all references to these have been updated (See strategy 4- Refactoring Classes). +- OverrideMethod and its associated MethodListener and MethodAdapter are used to intercept calls to + some specific methods that are stubbed out and change their return value. +- CreateInfo class, which configured the generator. Not used yet, but could in theory help us track + what the generator changed. +- AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new classes are + injected. The implementation for these classes has been taken from Android's libcore + (platform/libcore/luni/src/main/java/java/...). +- Charsets, IntegralToString and UnsafeByteSequence are not part of the Desktop VM. They are + added to the Dalvik VM for performance reasons. An implementation that is very close to the + original (which is at platform/libcore/luni/src/main/java/...) is injected. Since these classees + were in part of the java package, where we can't inject classes, all references to these have been + updated (See strategy 4- Refactoring Classes). 2- Overriding methods -As explained earlier, the creator doesn't have any replacement code for -methods to override. Instead it removes the original code and replaces it -by a call to a specific OveriddeMethod.invokeX(). The bridge then registers -a listener on the method signature and can provide an implementation. +As explained earlier, the creator doesn't have any replacement code for methods to override. Instead +it removes the original code and replaces it by a call to a specific OveriddeMethod.invokeX(). The +bridge then registers a listener on the method signature and can provide an implementation. -This strategy is now obsolete and replaced by the method delegates. -See strategy 5 below. +This strategy is now obsolete and replaced by the method delegates (See strategy 6- Method +Delegates). 3- Renaming classes -This simply changes the name of a class in its definition, as well as all its -references in internal inner classes and methods. -Calls from other classes are not modified -- they keep referencing the original -class name. This allows the bridge to literally replace an implementation. +This simply changes the name of a class in its definition, as well as all its references in internal +inner classes and methods. Calls from other classes are not modified -- they keep referencing the +original class name. This allows the bridge to literally replace an implementation. -An example will make this easier: android.graphics.Paint is the main drawing -class that we need to replace. To do so, the generator renames Paint to _original_Paint. -Later the bridge provides its own replacement version of Paint which will be used -by the rest of the Android stack. The replacement version of Paint can still use -(either by inheritance or delegation) all the original non-native code of _original_Paint -if it so desires. +An example will make this easier: android.graphics.Paint is the main drawing class that we need to +replace. To do so, the generator renames Paint to _original_Paint. Later the bridge provides its own +replacement version of Paint which will be used by the rest of the Android stack. The replacement +version of Paint can still use (either by inheritance or delegation) all the original non-native +code of _original_Paint if it so desires. -Some of the Android classes are basically wrappers over native objects and since -we don't have the native code in Eclipse, we need to provide a full alternate -implementation. Sub-classing doesn't work as some native methods are static and -we don't control object creation. +Some of the Android classes are basically wrappers over native objects and since we don't have the +native code in Eclipse, we need to provide a full alternate implementation. Sub-classing doesn't +work as some native methods are static and we don't control object creation. This won't rename/replace the inner static methods of a given class. 4- Refactoring classes -This is very similar to the Renaming classes except that it also updates the reference in -all classes. This is done for classes which are added to the Dalvik VM for performance -reasons but are not present in the Standard Java VM. An implementation for these classes -is also injected. +This is very similar to the Renaming classes except that it also updates the reference in all +classes. This is done for classes which are added to the Dalvik VM for performance reasons but are +not present in the Desktop VM. An implementation for these classes is also injected. 5- Method erasure based on return type -This is mostly an implementation detail of the bridge: in the Paint class -mentioned above, some inner static classes are used to pass around -attributes (e.g. FontMetrics, or the Style enum) and all the original implementation -is native. +This is mostly an implementation detail of the bridge: in the Paint class mentioned above, some +inner static classes are used to pass around attributes (e.g. FontMetrics, or the Style enum) and +all the original implementation is native. -In this case we have a strategy that tells the generator that anything returning, for -example, the inner class Paint$Style in the Paint class should be discarded and the -bridge will provide its own implementation. +In this case we have a strategy that tells the generator that anything returning, for example, the +inner class Paint$Style in the Paint class should be discarded and the bridge will provide its own +implementation. 6- Method Delegates -This strategy is used to override method implementations. -Given a method SomeClass.MethodName(), 1 or 2 methods are generated: -a- A copy of the original method named SomeClass.MethodName_Original(). - The content is the original method as-is from the reader. - This step is omitted if the method is native, since it has no Java implementation. -b- A brand new implementation of SomeClass.MethodName() which calls to a - non-existing static method named SomeClass_Delegate.MethodName(). - The implementation of this 'delegate' method is done in layoutlib_brigde. - -The delegate method is a static method. -If the original method is non-static, the delegate method receives the original 'this' -as its first argument. If the original method is an inner non-static method, it also -receives the inner 'this' as the second argument. +This strategy is used to override method implementations. Given a method SomeClass.MethodName(), 1 +or 2 methods are generated: +a- A copy of the original method named SomeClass.MethodName_Original(). The content is the original +method as-is from the reader. This step is omitted if the method is native, since it has no Java +implementation. +b- A brand new implementation of SomeClass.MethodName() which calls to a non-existing static method +named SomeClass_Delegate.MethodName(). The implementation of this 'delegate' method is done in +layoutlib_bridge. + +The delegate method is a static method. If the original method is non-static, the delegate method +receives the original 'this' as its first argument. If the original method is an inner non-static +method, it also receives the inner 'this' as the second argument. diff --git a/tools/layoutlib/create/create.iml b/tools/layoutlib/create/create.iml new file mode 100644 index 0000000..b7e8eb3 --- /dev/null +++ b/tools/layoutlib/create/create.iml @@ -0,0 +1,18 @@ +<?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" /> + <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/tests/data" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/tests/mock_data" type="java-test-resource" /> + <excludeFolder url="file://$MODULE_DIR$/.settings" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="asm-4.0" level="project" /> + <orderEntry type="library" scope="TEST" name="JUnit4" level="application" /> + </component> +</module> + diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java index b2caa25..a6902a4 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java @@ -97,7 +97,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor { if (type.getSort() == Type.OBJECT) { String in = type.getInternalName(); String newIn = renameInternalType(in); - if (newIn != in) { + if (!newIn.equals(in)) { return Type.getType("L" + newIn + ";"); } } else if (type.getSort() == Type.ARRAY) { @@ -177,6 +177,17 @@ public abstract class AbstractClassAdapter extends ClassVisitor { } } + /* Java 7 verifies the StackMapTable of a class if its version number is greater than 50.0. + * However, the check is disabled if the class version number is 50.0 or less. Generation + * of the StackMapTable requires a rewrite using the tree API of ASM. As a workaround, + * we rewrite the version number of the class to be 50.0 + * + * http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6693236 + */ + if (version > 50) { + version = 50; + } + super.visit(version, access, name, signature, superName, interfaces); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java index 9a31705..aa51c46 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -63,7 +64,8 @@ public class AsmAnalyzer { private final Set<String> mExcludedClasses; /** Glob patterns of files to keep as is. */ private final String[] mIncludeFileGlobs; - /** Copy these files into the output as is. */ + /** Internal names of classes that contain method calls that need to be rewritten. */ + private final Set<String> mReplaceMethodCallClasses = new HashSet<String>(); /** * Creates a new analyzer. @@ -109,6 +111,7 @@ public class AsmAnalyzer { mGen.setKeep(found); mGen.setDeps(deps); mGen.setCopyFiles(filesFound); + mGen.setRewriteMethodCallClasses(mReplaceMethodCallClasses); } } @@ -118,7 +121,7 @@ public class AsmAnalyzer { * * @param classes The map of class name => ASM ClassReader. Class names are * in the form "android.view.View". - * @param fileFound The map of file name => InputStream. The file name is + * @param filesFound The map of file name => InputStream. The file name is * in the form "android/data/dataFile". */ void parseZip(List<String> jarPathList, Map<String, ClassReader> classes, @@ -143,8 +146,8 @@ public class AsmAnalyzer { String className = classReaderToClassName(cr); classes.put(className, cr); } else { - for (int i = 0; i < includeFilePatterns.length; ++i) { - if (includeFilePatterns[i].matcher(entry.getName()).matches()) { + for (Pattern includeFilePattern : includeFilePatterns) { + if (includeFilePattern.matcher(entry.getName()).matches()) { filesFound.put(entry.getName(), zip.getInputStream(entry)); break; } @@ -239,7 +242,8 @@ public class AsmAnalyzer { for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { String class_name = entry.getKey(); - if (regexp.matcher(class_name).matches()) { + if (regexp.matcher(class_name).matches() && + !mExcludedClasses.contains(getOuterClassName(class_name))) { findClass(class_name, zipClasses, inOutFound); } } @@ -270,6 +274,9 @@ public class AsmAnalyzer { */ void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses, Map<String, ClassReader> inOutFound) throws LogAbortException { + if (mExcludedClasses.contains(getOuterClassName(super_name))) { + return; + } findClass(super_name, zipClasses, inOutFound); for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { @@ -321,6 +328,7 @@ public class AsmAnalyzer { deps, new_deps); for (ClassReader cr : inOutKeepClasses.values()) { + visitor.setClassName(cr.getClassName()); cr.accept(visitor, 0 /* flags */); } @@ -337,6 +345,7 @@ public class AsmAnalyzer { inOutKeepClasses.size(), deps.size()); for (ClassReader cr : temp.values()) { + visitor.setClassName(cr.getClassName()); cr.accept(visitor, 0 /* flags */); } } @@ -347,7 +356,13 @@ public class AsmAnalyzer { return deps; } - + private String getOuterClassName(String className) { + int pos = className.indexOf('$'); + if (pos > 0) { + return className.substring(0, pos); + } + return className; + } // ---------------------------------- @@ -367,6 +382,8 @@ public class AsmAnalyzer { /** New classes to keep as-is found by this visitor. */ private final Map<String, ClassReader> mOutKeep; + private String mClassName; + /** * Creates a new visitor that will find all the dependencies for the visited class. * Types which are already in the zipClasses, keepClasses or inDeps are not marked. @@ -390,6 +407,10 @@ public class AsmAnalyzer { mOutDeps = outDeps; } + private void setClassName(String className) { + mClassName = className; + } + /** * Considers the given class name as a dependency. * If it does, add to the mOutDeps map. @@ -406,7 +427,7 @@ public class AsmAnalyzer { mOutKeep.containsKey(className) || mInDeps.containsKey(className) || mOutDeps.containsKey(className) || - mExcludedClasses.contains(getBaseName(className))) { + mExcludedClasses.contains(getOuterClassName(className))) { return; } @@ -429,7 +450,7 @@ public class AsmAnalyzer { // - android classes are added to dependencies // - non-android classes are added to the list of classes to keep as-is (they don't need // to be stubbed). - if (className.indexOf("android") >= 0) { // TODO make configurable + if (className.contains("android")) { // TODO make configurable mOutDeps.put(className, cr); } else { mOutKeep.put(className, cr); @@ -490,14 +511,6 @@ public class AsmAnalyzer { } } - private String getBaseName(String className) { - int pos = className.indexOf('$'); - if (pos > 0) { - return className.substring(0, pos); - } - return className; - } - // --------------------------------------------------- // --- ClassVisitor, FieldVisitor // --------------------------------------------------- @@ -594,7 +607,7 @@ public class AsmAnalyzer { // type and exceptions do not use generic types. considerSignature(signature); - return new MyMethodVisitor(); + return new MyMethodVisitor(mClassName); } @Override @@ -614,8 +627,11 @@ public class AsmAnalyzer { private class MyMethodVisitor extends MethodVisitor { - public MyMethodVisitor() { + private String mOwnerClass; + + public MyMethodVisitor(String ownerClass) { super(Opcodes.ASM4); + mOwnerClass = ownerClass; } @@ -632,8 +648,8 @@ public class AsmAnalyzer { // field instruction @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { - // name is the field's name. - considerName(name); + // owner is the class that declares the field. + considerName(owner); // desc is the field's descriptor (see Type). considerDesc(desc); } @@ -709,6 +725,12 @@ public class AsmAnalyzer { considerName(owner); // desc is the method's descriptor (see Type). considerDesc(desc); + + + // Check if method needs to replaced by a call to a different method. + if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) { + mReplaceMethodCallClasses.add(mOwnerClass); + } } // instruction multianewarray, whatever that is diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index 207d8ae..bd6f070 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -21,7 +21,6 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -55,6 +54,8 @@ public class AsmGenerator { private Map<String, ClassReader> mDeps; /** All files that are to be copied as-is. */ private Map<String, InputStream> mCopyFiles; + /** All classes where certain method calls need to be rewritten. */ + private Set<String> mReplaceMethodCallsClasses; /** Counter of number of classes renamed during transform. */ private int mRenameCount; /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */ @@ -133,7 +134,7 @@ public class AsmGenerator { assert i + 1 < n; String oldFqcn = binaryToInternalClassName(refactorClasses[i]); String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]); - mRefactorClasses.put(oldFqcn, newFqcn);; + mRefactorClasses.put(oldFqcn, newFqcn); } // create the map of renamed class -> return type of method to delete. @@ -203,44 +204,33 @@ public class AsmGenerator { mCopyFiles = copyFiles; } - /** Gets the map of classes to output as-is, except if they have native methods */ - public Map<String, ClassReader> getKeep() { - return mKeep; - } - - /** Gets the map of dependencies that must be completely stubbed */ - public Map<String, ClassReader> getDeps() { - return mDeps; - } - - /** Gets the map of files to output as-is. */ - public Map<String, InputStream> getCopyFiles() { - return mCopyFiles; + public void setRewriteMethodCallClasses(Set<String> rewriteMethodCallClasses) { + mReplaceMethodCallsClasses = rewriteMethodCallClasses; } /** Generates the final JAR */ - public void generate() throws FileNotFoundException, IOException { + public void generate() throws IOException { TreeMap<String, byte[]> all = new TreeMap<String, byte[]>(); for (Class<?> clazz : mInjectClasses) { String name = classToEntryPath(clazz); InputStream is = ClassLoader.getSystemResourceAsStream(name); ClassReader cr = new ClassReader(is); - byte[] b = transform(cr, true /* stubNativesOnly */); + byte[] b = transform(cr, true); name = classNameToEntryPath(transformName(cr.getClassName())); all.put(name, b); } for (Entry<String, ClassReader> entry : mDeps.entrySet()) { ClassReader cr = entry.getValue(); - byte[] b = transform(cr, true /* stubNativesOnly */); + byte[] b = transform(cr, true); String name = classNameToEntryPath(transformName(cr.getClassName())); all.put(name, b); } for (Entry<String, ClassReader> entry : mKeep.entrySet()) { ClassReader cr = entry.getValue(); - byte[] b = transform(cr, true /* stubNativesOnly */); + byte[] b = transform(cr, true); String name = classNameToEntryPath(transformName(cr.getClassName())); all.put(name, b); } @@ -292,7 +282,7 @@ public class AsmGenerator { /** * Utility method to get the JAR entry path from a Class name. - * e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class" + * e.g. it returns something like "com/foo/OuterClass$InnerClass1$InnerClass2.class" */ private String classToEntryPath(Class<?> clazz) { String name = ""; @@ -329,14 +319,14 @@ public class AsmGenerator { String newName = transformName(className); // transformName returns its input argument if there's no need to rename the class - if (newName != className) { + if (!newName.equals(className)) { mRenameCount++; // This class is being renamed, so remove it from the list of classes not renamed. mClassesNotRenamed.remove(className); } mLog.debug("Transform %s%s%s%s", className, - newName == className ? "" : " (renamed to " + newName + ")", + newName.equals(className) ? "" : " (renamed to " + newName + ")", hasNativeMethods ? " -- has natives" : "", stubNativesOnly ? " -- stub natives only" : ""); @@ -344,15 +334,19 @@ public class AsmGenerator { // original class reader. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses); - if (newName != className) { + ClassVisitor cv = cw; + + if (mReplaceMethodCallsClasses.contains(className)) { + cv = new ReplaceMethodCallsAdapter(cv); + } + + cv = new RefactorClassAdapter(cv, mRefactorClasses); + if (!newName.equals(className)) { cv = new RenameClassAdapter(cv, className, newName); } - cv = new TransformClassAdapter(mLog, mStubMethods, - mDeleteReturns.get(className), - newName, cv, - stubNativesOnly, stubNativesOnly || hasNativeMethods); + cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className), + newName, cv, stubNativesOnly); Set<String> delegateMethods = mDelegateMethods.get(className); if (delegateMethods != null && !delegateMethods.isEmpty()) { @@ -365,7 +359,7 @@ public class AsmGenerator { } } - cr.accept(cv, 0 /* flags */); + cr.accept(cv, 0); return cw.toByteArray(); } @@ -399,7 +393,7 @@ public class AsmGenerator { */ boolean hasNativeMethods(ClassReader cr) { ClassHasNativeVisitor cv = new ClassHasNativeVisitor(); - cr.accept(cv, 0 /* flags */); + cr.accept(cv, 0); return cv.hasNativeMethods(); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 29a5706..83fac85 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -23,6 +23,10 @@ import com.android.tools.layoutlib.java.IntegralToString; import com.android.tools.layoutlib.java.Objects; import com.android.tools.layoutlib.java.UnsafeByteSequence; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + /** * Describes the work to be done by {@link AsmGenerator}. */ @@ -98,6 +102,17 @@ public final class CreateInfo implements ICreateInfo { public String[] getJavaPkgClasses() { return JAVA_PKG_CLASSES; } + + public Set<String> getExcludedClasses() { + String[] refactoredClasses = getJavaPkgClasses(); + int count = refactoredClasses.length / 2 + EXCLUDED_CLASSES.length; + Set<String> excludedClasses = new HashSet<String>(count); + for (int i = 0; i < refactoredClasses.length; i+=2) { + excludedClasses.add(refactoredClasses[i]); + } + excludedClasses.addAll(Arrays.asList(EXCLUDED_CLASSES)); + return excludedClasses; + } //----- /** @@ -125,22 +140,32 @@ public final class CreateInfo implements ICreateInfo { "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;", "android.content.res.Resources$Theme#obtainStyledAttributes", "android.content.res.Resources$Theme#resolveAttribute", + "android.content.res.Resources$Theme#resolveAttributes", + "android.content.res.AssetManager#newTheme", + "android.content.res.AssetManager#deleteTheme", + "android.content.res.AssetManager#applyThemeStyle", "android.content.res.TypedArray#getValueAt", + "android.content.res.TypedArray#obtain", "android.graphics.BitmapFactory#finishDecode", + "android.graphics.Typeface#getSystemFontConfigLocation", "android.os.Handler#sendMessageAtTime", "android.os.HandlerThread#run", - "android.os.Build#getString", "android.text.format.DateFormat#is24HourFormat", + "android.util.Xml#newPullParser", "android.view.Choreographer#getRefreshRate", "android.view.Display#updateDisplayInfoLocked", + "android.view.Display#getWindowManager", "android.view.LayoutInflater#rInflate", "android.view.LayoutInflater#parseInclude", "android.view.View#isInEditMode", "android.view.ViewRootImpl#isInTouchMode", "android.view.WindowManagerGlobal#getWindowManagerService", "android.view.inputmethod.InputMethodManager#getInstance", + "android.view.MenuInflater#registerMenu", + "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", "com.android.internal.textservice.ITextServicesManager$Stub#asInterface", + "dalvik.system.VMRuntime#newUnpaddedArray" }; /** @@ -163,6 +188,7 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.DiscretePathEffect", "android.graphics.DrawFilter", "android.graphics.EmbossMaskFilter", + "android.graphics.FontFamily", "android.graphics.LayerRasterizer", "android.graphics.LightingColorFilter", "android.graphics.LinearGradient", @@ -186,7 +212,9 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.Typeface", "android.graphics.Xfermode", "android.os.SystemClock", + "android.os.SystemProperties", "android.text.AndroidBidi", + "android.text.StaticLayout", "android.text.format.Time", "android.view.Display", "libcore.icu.DateIntervalFormat", @@ -229,6 +257,11 @@ public final class CreateInfo implements ICreateInfo { "java.lang.UnsafeByteSequence", "com.android.tools.layoutlib.java.UnsafeByteSequence", }; + private final static String[] EXCLUDED_CLASSES = + new String[] { + "org.kxml2.io.KXmlParser" + }; + /** * List of classes for which the methods returning them should be deleted. * The array contains a list of null terminated section starting with the name of the class diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java index 927be97..3d89c68 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java @@ -32,10 +32,10 @@ public class DelegateClassAdapter extends ClassVisitor { /** Suffix added to original methods. */ private static final String ORIGINAL_SUFFIX = "_Original"; - private static String CONSTRUCTOR = "<init>"; - private static String CLASS_INIT = "<clinit>"; + private static final String CONSTRUCTOR = "<init>"; + private static final String CLASS_INIT = "<clinit>"; - public final static String ALL_NATIVES = "<<all_natives>>"; + public static final String ALL_NATIVES = "<<all_natives>>"; private final String mClassName; private final Set<String> mDelegateMethods; @@ -78,19 +78,16 @@ public class DelegateClassAdapter extends ClassVisitor { mDelegateMethods.contains(name); if (!useDelegate) { - // Not creating a delegate for this method, pass it as-is from the reader - // to the writer. + // Not creating a delegate for this method, pass it as-is from the reader to the writer. return super.visitMethod(access, name, desc, signature, exceptions); } - if (useDelegate) { - if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) { - // We don't currently support generating delegates for constructors. - throw new UnsupportedOperationException( - String.format( - "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$ - mClassName, name, desc)); - } + if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) { + // We don't currently support generating delegates for constructors. + throw new UnsupportedOperationException( + String.format( + "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$ + mClassName, name, desc)); } if (isNative) { @@ -98,8 +95,8 @@ public class DelegateClassAdapter extends ClassVisitor { access = access & ~Opcodes.ACC_NATIVE; MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions); - DelegateMethodAdapter2 a = new DelegateMethodAdapter2( - mLog, null /*mwOriginal*/, mwDelegate, mClassName, name, desc, isStatic); + DelegateMethodAdapter a = new DelegateMethodAdapter( + mLog, null, mwDelegate, mClassName, name, desc, isStatic); // A native has no code to visit, so we need to generate it directly. a.generateDelegateCode(); @@ -112,22 +109,16 @@ public class DelegateClassAdapter extends ClassVisitor { // The content is the original method as-is from the reader. // - A brand new implementation of SomeClass.MethodName() which calls to a // non-existing method named SomeClass_Delegate.MethodName(). - // The implementation of this 'delegate' method is done in layoutlib_brigde. + // The implementation of this 'delegate' method is done in layoutlib_bridge. int accessDelegate = access; - // change access to public for the original one - if (Main.sOptions.generatePublicAccess) { - access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); - access |= Opcodes.ACC_PUBLIC; - } MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX, desc, signature, exceptions); MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name, desc, signature, exceptions); - DelegateMethodAdapter2 a = new DelegateMethodAdapter2( + return new DelegateMethodAdapter( mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic); - return a; } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java index 0000b22..12690db 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java @@ -71,7 +71,7 @@ import java.util.ArrayList; * Instances of this class are not re-usable. * The class adapter creates a new instance for each method. */ -class DelegateMethodAdapter2 extends MethodVisitor { +class DelegateMethodAdapter extends MethodVisitor { /** Suffix added to delegate classes. */ public static final String DELEGATE_SUFFIX = "_Delegate"; @@ -97,10 +97,10 @@ class DelegateMethodAdapter2 extends MethodVisitor { private Object[] mDelegateLineNumber; /** - * Creates a new {@link DelegateMethodAdapter2} that will transform this method + * Creates a new {@link DelegateMethodAdapter} that will transform this method * into a delegate call. * <p/> - * See {@link DelegateMethodAdapter2} for more details. + * See {@link DelegateMethodAdapter} for more details. * * @param log The logger object. Must not be null. * @param mvOriginal The parent method writer to copy of the original method. @@ -114,7 +114,7 @@ class DelegateMethodAdapter2 extends MethodVisitor { * {@link Type#getArgumentTypes(String)}) * @param isStatic True if the method is declared static. */ - public DelegateMethodAdapter2(Log log, + public DelegateMethodAdapter(Log log, MethodVisitor mvOriginal, MethodVisitor mvDelegate, String className, @@ -138,7 +138,7 @@ class DelegateMethodAdapter2 extends MethodVisitor { * (since they have no code to visit). * <p/> * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to - * return this instance of {@link DelegateMethodAdapter2} and let the normal visitor pattern + * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then * this method will be invoked from {@link MethodVisitor#visitEnd()}. */ diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java index c988c70..7690fcd 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java @@ -316,9 +316,7 @@ public class DependencyFinder { // Add it to the dependency set for the currently visited class, as needed. assert mCurrentDepSet != null; - if (mCurrentDepSet != null) { - mCurrentDepSet.add(className); - } + mCurrentDepSet.add(className); } /** @@ -527,7 +525,8 @@ public class DependencyFinder { // field instruction @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { - // name is the field's name. + // owner is the class that declares the field. + considerName(owner); // desc is the field's descriptor (see Type). considerDesc(desc); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java index 9387814..e49a668 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java @@ -16,6 +16,8 @@ package com.android.tools.layoutlib.create; +import java.util.Set; + /** * Interface describing the work to be done by {@link AsmGenerator}. */ @@ -69,4 +71,6 @@ public interface ICreateInfo { * The list can be empty but must not be null. */ public abstract String[] getJavaPkgClasses(); + + public abstract Set<String> getExcludedClasses(); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index a79fba1..cd3c39e 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -18,6 +18,7 @@ package com.android.tools.layoutlib.create; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -49,7 +50,6 @@ import java.util.Set; public class Main { public static class Options { - public boolean generatePublicAccess = true; public boolean listAllDeps = false; public boolean listOnlyMissingDeps = false; } @@ -64,7 +64,7 @@ public class Main { String[] osDestJar = { null }; if (!processArgs(log, args, osJarPath, osDestJar)) { - log.error("Usage: layoutlib_create [-v] [-p] output.jar input.jar ..."); + log.error("Usage: layoutlib_create [-v] output.jar input.jar ..."); log.error("Usage: layoutlib_create [-v] [--list-deps|--missing-deps] input.jar ..."); System.exit(1); } @@ -88,7 +88,7 @@ public class Main { try { CreateInfo info = new CreateInfo(); - Set<String> excludeClasses = getExcludedClasses(info); + Set<String> excludeClasses = info.getExcludedClasses(); AsmGenerator agen = new AsmGenerator(log, osDestJar, info); AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen, @@ -114,6 +114,9 @@ public class Main { "android.os.*", // for android.os.Handler "android.database.ContentObserver", // for Digital clock "com.android.i18n.phonenumbers.*", // for TextView with autolink attribute + "android.app.DatePickerDialog", // b.android.com/28318 + "android.app.TimePickerDialog", // b.android.com/61515 + "com.android.internal.view.menu.ActionMenu", }, excludeClasses, new String[] { @@ -154,16 +157,6 @@ public class Main { return 1; } - private static Set<String> getExcludedClasses(CreateInfo info) { - String[] refactoredClasses = info.getJavaPkgClasses(); - Set<String> excludedClasses = new HashSet<String>(refactoredClasses.length); - for (int i = 0; i < refactoredClasses.length; i+=2) { - excludedClasses.add(refactoredClasses[i]); - } - return excludedClasses; - - } - private static int listDeps(ArrayList<String> osJarPath, Log log) { DependencyFinder df = new DependencyFinder(log); try { @@ -190,12 +183,9 @@ public class Main { private static boolean processArgs(Log log, String[] args, ArrayList<String> osJarPath, String[] osDestJar) { boolean needs_dest = true; - for (int i = 0; i < args.length; i++) { - String s = args[i]; + for (String s : args) { if (s.equals("-v")) { log.setVerbose(true); - } else if (s.equals("-p")) { - sOptions.generatePublicAccess = false; } else if (s.equals("--list-deps")) { sOptions.listAllDeps = true; needs_dest = false; @@ -209,7 +199,7 @@ public class Main { osJarPath.add(s); } } else { - log.error("Unknow argument: %s", s); + log.error("Unknown argument: %s", s); return false; } } @@ -223,8 +213,6 @@ public class Main { return false; } - sOptions.generatePublicAccess = false; - return true; } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java index a6aff99..4c87b3c 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java @@ -36,6 +36,7 @@ public final class OverrideMethod { * Sets the default listener for all methods not specifically handled. * Null means to do nothing. */ + @SuppressWarnings("UnusedDeclaration") // Used by Bridge by reflection for debug purposes. public static void setDefaultListener(MethodListener listener) { sDefaultListener = listener; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java index 661074c..40bd126 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java @@ -73,7 +73,7 @@ public class RenameClassAdapter extends AbstractClassAdapter { return mNewName; } - if (mOldBase != mOldName && type.equals(mOldBase)) { + if (!mOldBase.equals(mOldName) && type.equals(mOldBase)) { return mNewBase; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java new file mode 100644 index 0000000..9c6fbac --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.layoutlib.create; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * Replaces calls to certain methods that do not exist in the Desktop VM. Useful for methods in the + * "java" package. + */ +public class ReplaceMethodCallsAdapter extends ClassVisitor { + + /** + * Descriptors for specialized versions {@link System#arraycopy} that are not present on the + * Desktop VM. + */ + private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<String>(Arrays.asList( + "([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V", + "([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V")); + + private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(2); + + private static final String ANDROID_LOCALE_CLASS = + "com/android/layoutlib/bridge/android/AndroidLocale"; + + private static final String JAVA_LOCALE_CLASS = "java/util/Locale"; + private static final Type STRING = Type.getType(String.class); + + // Static initialization block to initialize METHOD_REPLACERS. + static { + // Case 1: java.lang.System.arraycopy() + METHOD_REPLACERS.add(new MethodReplacer() { + @Override + public boolean isNeeded(String owner, String name, String desc) { + return "java/lang/System".equals(owner) && "arraycopy".equals(name) && + ARRAYCOPY_DESCRIPTORS.contains(desc); + } + + @Override + public void replace(int[] opcode, String[] methodInformation) { + assert methodInformation.length == 3 && isNeeded(methodInformation[0], methodInformation[1], methodInformation[2]) + && opcode.length == 1; + methodInformation[2] = "(Ljava/lang/Object;ILjava/lang/Object;II)V"; + } + }); + + // Case 2: java.util.Locale.toLanguageTag() and java.util.Locale.getScript() + METHOD_REPLACERS.add(new MethodReplacer() { + + String LOCALE_TO_STRING = Type.getMethodDescriptor(STRING, Type.getType(Locale.class)); + + @Override + public boolean isNeeded(String owner, String name, String desc) { + return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) && + ("toLanguageTag".equals(name) || "getScript".equals(name)); + } + + @Override + public void replace(int[] opcode, String[] methodInformation) { + assert methodInformation.length == 3 && isNeeded(methodInformation[0], methodInformation[1], methodInformation[2]) + && opcode.length == 1; + opcode[0] = Opcodes.INVOKESTATIC; + methodInformation[0] = ANDROID_LOCALE_CLASS; + methodInformation[2] = LOCALE_TO_STRING; + } + }); + + // Case 3: java.util.Locale.adjustLanguageCode() or java.util.Locale.forLanguageTag() + METHOD_REPLACERS.add(new MethodReplacer() { + + private final String STRING_TO_STRING = Type.getMethodDescriptor(STRING, STRING); + private final String STRING_TO_LOCALE = Type.getMethodDescriptor( + Type.getType(Locale.class), STRING); + + @Override + public boolean isNeeded(String owner, String name, String desc) { + return JAVA_LOCALE_CLASS.equals(owner) && + ("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) || + "forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE)); + } + + @Override + public void replace(int[] opcode, String[] methodInformation) { + assert methodInformation.length == 3 && isNeeded(methodInformation[0], methodInformation[1], methodInformation[2]) + && opcode.length == 1; + methodInformation[0] = ANDROID_LOCALE_CLASS; + } + }); + } + + public static boolean isReplacementNeeded(String owner, String name, String desc) { + for (MethodReplacer replacer : METHOD_REPLACERS) { + if (replacer.isNeeded(owner, name, desc)) { + return true; + } + } + return false; + } + + public ReplaceMethodCallsAdapter(ClassVisitor cv) { + super(Opcodes.ASM4, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + return new MyMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions)); + } + + private class MyMethodVisitor extends MethodVisitor { + + public MyMethodVisitor(MethodVisitor mv) { + super(Opcodes.ASM4, mv); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + for (MethodReplacer replacer : METHOD_REPLACERS) { + if (replacer.isNeeded(owner, name, desc)) { + String[] methodInformation = {owner, name, desc}; + int[] opcodeOut = {opcode}; + replacer.replace(opcodeOut, methodInformation); + opcode = opcodeOut[0]; + owner = methodInformation[0]; + name = methodInformation[1]; + desc = methodInformation[2]; + break; + } + } + super.visitMethodInsn(opcode, owner, name, desc); + } + } + + private interface MethodReplacer { + public boolean isNeeded(String owner, String name, String desc); + + /** + * This method must update the arrays with the new values of the method attributes - + * opcode, owner, name and desc. + * @param opcode This array should contain the original value of the opcode. The value is + * modified by the method if needed. The size of the array must be 1. + * + * @param methodInformation This array should contain the original values of the method + * attributes - owner, name and desc in that order. The values + * may be modified as needed. The size of the array must be 3. + */ + public void replace(int[] opcode, String[] methodInformation); + } +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java index 51e7535..416b73a 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java @@ -29,8 +29,8 @@ import org.objectweb.asm.Type; */ class StubMethodAdapter extends MethodVisitor { - private static String CONSTRUCTOR = "<init>"; - private static String CLASS_INIT = "<clinit>"; + private static final String CONSTRUCTOR = "<init>"; + private static final String CLASS_INIT = "<clinit>"; /** The parent method writer */ private MethodVisitor mParentVisitor; diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java index d45a183..d9ecf98 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java @@ -17,7 +17,6 @@ package com.android.tools.layoutlib.create; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -40,19 +39,16 @@ class TransformClassAdapter extends ClassVisitor { /** * Creates a new class adapter that will stub some or all methods. - * @param logger * @param stubMethods list of method signatures to always stub out * @param deleteReturns list of types that trigger the deletion of methods returning them. * @param className The name of the class being modified * @param cv The parent class writer visitor * @param stubNativesOnly True if only native methods should be stubbed. False if all * methods should be stubbed. - * @param hasNative True if the method has natives, in which case its access should be - * changed. */ public TransformClassAdapter(Log logger, Set<String> stubMethods, Set<String> deleteReturns, String className, ClassVisitor cv, - boolean stubNativesOnly, boolean hasNative) { + boolean stubNativesOnly) { super(Opcodes.ASM4, cv); mLog = logger; mStubMethods = stubMethods; @@ -70,11 +66,6 @@ class TransformClassAdapter extends ClassVisitor { // This class might be being renamed. name = mClassName; - // remove protected or private and set as public - if (Main.sOptions.generatePublicAccess) { - access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); - access |= Opcodes.ACC_PUBLIC; - } // remove final access = access & ~Opcodes.ACC_FINAL; // note: leave abstract classes as such @@ -87,11 +78,6 @@ class TransformClassAdapter extends ClassVisitor { /* Visits the header of an inner class. */ @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { - // remove protected or private and set as public - if (Main.sOptions.generatePublicAccess) { - access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); - access |= Opcodes.ACC_PUBLIC; - } // remove final access = access & ~Opcodes.ACC_FINAL; // note: leave abstract classes as such @@ -119,12 +105,6 @@ class TransformClassAdapter extends ClassVisitor { String methodSignature = mClassName.replace('/', '.') + "#" + name; - // change access to public - if (Main.sOptions.generatePublicAccess) { - access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); - access |= Opcodes.ACC_PUBLIC; - } - // remove final access = access & ~Opcodes.ACC_FINAL; @@ -155,18 +135,6 @@ class TransformClassAdapter extends ClassVisitor { } } - /* Visits a field. Makes it public. */ - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, - Object value) { - // change access to public - if (Main.sOptions.generatePublicAccess) { - access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); - access |= Opcodes.ACC_PUBLIC; - } - return super.visitField(access, name, desc, signature, value); - } - /** * Extracts the return {@link Type} of this descriptor. */ diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk new file mode 100644 index 0000000..c197d57 --- /dev/null +++ b/tools/layoutlib/create/tests/Android.mk @@ -0,0 +1,35 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, com) + +LOCAL_JAVA_RESOURCE_DIRS := data mock_data + +LOCAL_MODULE := layoutlib-create-tests +LOCAL_MODULE_TAGS := optional + +LOCAL_JAVA_LIBRARIES := layoutlib_create junit +LOCAL_STATIC_JAVA_LIBRARIES := asm-4.0 + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Copy the jar to DIST_DIR for sdk builds +$(call dist-for-goals, sdk win_sdk, $(LOCAL_INSTALLED_MODULE)) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java index 7ec0d38..78e2c48 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java @@ -83,6 +83,7 @@ public class AsmAnalyzerTest { "mock_android.dummy.InnerTest$MyStaticInnerClass", "mock_android.dummy.InnerTest$NotStaticInner1", "mock_android.dummy.InnerTest$NotStaticInner2", + "mock_android.util.EmptyArray", "mock_android.view.View", "mock_android.view.ViewGroup", "mock_android.view.ViewGroup$LayoutParams", @@ -217,15 +218,16 @@ public class AsmAnalyzerTest { TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>(); TreeMap<String, ClassReader> out_deps = new TreeMap<String, ClassReader>(); - ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep); + ClassReader cr = mAa.findClass("mock_android.widget.LinearLayout", zipClasses, keep); DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps); // get first level dependencies cr.accept(visitor, 0 /* flags */); assertArrayEquals(new String[] { + "mock_android.util.EmptyArray", "mock_android.view.ViewGroup", - "mock_android.widget.TableLayout$LayoutParams", + "mock_android.widget.LinearLayout$LayoutParams", }, out_deps.keySet().toArray()); @@ -255,7 +257,7 @@ public class AsmAnalyzerTest { assertArrayEquals(new String[] { }, out_deps.keySet().toArray()); assertArrayEquals(new String[] { - "mock_android.widget.TableLayout", + "mock_android.widget.LinearLayout", }, keep.keySet().toArray()); } } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java index 0dbc238..cf91386 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -19,6 +19,7 @@ package com.android.tools.layoutlib.create; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.After; @@ -36,6 +37,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Map; @@ -119,6 +121,11 @@ public class AsmGeneratorTest { } @Override + public Set<String> getExcludedClasses() { + return null; + } + + @Override public String[] getDeleteReturns() { // methods deleted from their return type. return new String[0]; @@ -184,6 +191,11 @@ public class AsmGeneratorTest { } @Override + public Set<String> getExcludedClasses() { + return Collections.singleton("java.lang.JavaClass"); + } + + @Override public String[] getDeleteReturns() { // methods deleted from their return type. return new String[0]; @@ -217,6 +229,80 @@ public class AsmGeneratorTest { filesFound.keySet().toArray()); } + @Test + public void testClassExclusion() throws IOException, LogAbortException { + ICreateInfo ci = new ICreateInfo() { + @Override + public Class<?>[] getInjectedClasses() { + return new Class<?>[0]; + } + + @Override + public String[] getDelegateMethods() { + return new String[0]; + } + + @Override + public String[] getDelegateClassNatives() { + return new String[0]; + } + + @Override + public String[] getOverriddenMethods() { + // methods to force override + return new String[0]; + } + + @Override + public String[] getRenamedClasses() { + // classes to rename (so that we can replace them) + return new String[0]; + } + + @Override + public String[] getJavaPkgClasses() { + // classes to refactor (so that we can replace them) + return new String[0]; + } + + @Override + public Set<String> getExcludedClasses() { + Set<String> set = new HashSet<String>(2); + set.add("mock_android.dummy.InnerTest"); + set.add("java.lang.JavaClass"); + return set; + } + + @Override + public String[] getDeleteReturns() { + // methods deleted from their return type. + return new String[0]; + } + }; + + AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci); + Set<String> excludedClasses = ci.getExcludedClasses(); + AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen, + null, // derived from + new String[] { // include classes + "**" + }, + excludedClasses, + new String[] { /* include files */ + "mock_android/data/data*" + }); + aa.analyze(); + agen.generate(); + Map<String, ClassReader> output = new TreeMap<String, ClassReader>(); + Map<String, InputStream> filesFound = new TreeMap<String, InputStream>(); + parseZip(mOsDestJar, output, filesFound); + for (String s : output.keySet()) { + assertFalse(excludedClasses.contains(s)); + } + assertArrayEquals(new String[] {"mock_android/data/dataFile"}, + filesFound.keySet().toArray()); + } + private void parseZip(String jarPath, Map<String, ClassReader> classes, Map<String, InputStream> filesFound) throws IOException { diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java index 6e120ce..648cea4 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -116,9 +116,16 @@ public class DelegateClassAdapterTest { // Check that the native method does NOT have the new annotation Method[] m = clazz2.getDeclaredMethods(); - assertEquals("native_instance", m[2].getName()); - assertTrue(Modifier.isNative(m[2].getModifiers())); - Annotation[] a = m[2].getAnnotations(); + Method nativeInstanceMethod = null; + for (Method method : m) { + if ("native_instance".equals(method.getName())) { + nativeInstanceMethod = method; + break; + } + } + assertNotNull(nativeInstanceMethod); + assertTrue(Modifier.isNative(nativeInstanceMethod.getModifiers())); + Annotation[] a = nativeInstanceMethod.getAnnotations(); assertEquals(0, a.length); } }; @@ -130,7 +137,7 @@ public class DelegateClassAdapterTest { } /** - * {@link DelegateMethodAdapter2} does not support overriding constructors yet, + * {@link DelegateMethodAdapter} does not support overriding constructors yet, * so this should fail with an {@link UnsupportedOperationException}. * * Although not tested here, the message of the exception should contain the @@ -184,9 +191,16 @@ public class DelegateClassAdapterTest { // Check that the native method now has the new annotation and is not native Method[] m = clazz2.getDeclaredMethods(); - assertEquals("native_instance", m[2].getName()); - assertFalse(Modifier.isNative(m[2].getModifiers())); - Annotation[] a = m[2].getAnnotations(); + Method nativeInstanceMethod = null; + for (Method method : m) { + if ("native_instance".equals(method.getName())) { + nativeInstanceMethod = method; + break; + } + } + assertNotNull(nativeInstanceMethod); + assertFalse(Modifier.isNative(nativeInstanceMethod.getModifiers())); + Annotation[] a = nativeInstanceMethod.getAnnotations(); assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName()); } }; @@ -237,13 +251,8 @@ public class DelegateClassAdapterTest { assertEquals(4+10+20, callGet(o2, 10, 20)); assertEquals(1+10+20, callGet_Original(o2, 10, 20)); - // The original Outer has a private method that is - // delegated. We should be able to call both the delegate - // and the original (which is now public). - assertEquals("outerPrivateMethod", - callMethod(o2, "privateMethod_Original", false /*makePublic*/)); - - // The original method is private, so by default we can't access it + // The original Outer has a private method, + // so by default we can't access it. boolean gotIllegalAccessException = false; try { callMethod(o2, "privateMethod", false /*makePublic*/); @@ -251,16 +260,24 @@ public class DelegateClassAdapterTest { gotIllegalAccessException = true; } assertTrue(gotIllegalAccessException); - // Try again, but now making it accessible - assertEquals("outerPrivate_Delegate", - callMethod(o2, "privateMethod", true /*makePublic*/)); + + // The private method from original Outer has been + // delegated. The delegate generated should have the + // same access. + gotIllegalAccessException = false; + try { + assertEquals("outerPrivateMethod", + callMethod(o2, "privateMethod_Original", false /*makePublic*/)); + } catch (IllegalAccessException e) { + gotIllegalAccessException = true; + } + assertTrue(gotIllegalAccessException); // Check the inner class. Since it's not a static inner class, we need // to use the hidden constructor that takes the outer class as first parameter. Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME); - Constructor<?> innerCons = innerClazz2.getConstructor( - new Class<?>[] { outerClazz2 }); - Object i2 = innerCons.newInstance(new Object[] { o2 }); + Constructor<?> innerCons = innerClazz2.getConstructor(outerClazz2); + Object i2 = innerCons.newInstance(o2); assertNotNull(i2); // The original Inner.get returns 3+10+20, @@ -344,10 +361,10 @@ public class DelegateClassAdapterTest { */ public int callGet(Object instance, int a, long b) throws Exception { Method m = instance.getClass().getMethod("get", - new Class<?>[] { int.class, long.class } ); + int.class, long.class); - Object result = m.invoke(instance, new Object[] { a, b }); - return ((Integer) result).intValue(); + Object result = m.invoke(instance, a, b); + return (Integer) result; } /** @@ -356,10 +373,10 @@ public class DelegateClassAdapterTest { */ public int callGet_Original(Object instance, int a, long b) throws Exception { Method m = instance.getClass().getMethod("get_Original", - new Class<?>[] { int.class, long.class } ); + int.class, long.class); - Object result = m.invoke(instance, new Object[] { a, b }); - return ((Integer) result).intValue(); + Object result = m.invoke(instance, a, b); + return (Integer) result; } /** @@ -388,10 +405,10 @@ public class DelegateClassAdapterTest { */ public int callAdd(Object instance, int a, int b) throws Exception { Method m = instance.getClass().getMethod("add", - new Class<?>[] { int.class, int.class }); + int.class, int.class); - Object result = m.invoke(instance, new Object[] { a, b }); - return ((Integer) result).intValue(); + Object result = m.invoke(instance, a, b); + return (Integer) result; } /** @@ -401,10 +418,10 @@ public class DelegateClassAdapterTest { public int callCallNativeInstance(Object instance, int a, double d, Object[] o) throws Exception { Method m = instance.getClass().getMethod("callNativeInstance", - new Class<?>[] { int.class, double.class, Object[].class }); + int.class, double.class, Object[].class); - Object result = m.invoke(instance, new Object[] { a, d, o }); - return ((Integer) result).intValue(); + Object result = m.invoke(instance, a, d, o); + return (Integer) result; } public abstract void testModifiedInstance() throws Exception; @@ -442,8 +459,8 @@ public class DelegateClassAdapterTest { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw); - Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() }); - Object tcv = cons.newInstance(new Object[] { pw }); + Constructor<?> cons = tcvClass.getConstructor(pw.getClass()); + Object tcv = cons.newInstance(pw); ClassReader cr2 = new ClassReader(bytes); cr2.accept((ClassVisitor) tcv, 0 /* flags */); @@ -452,8 +469,7 @@ public class DelegateClassAdapterTest { } // Re-throw exception with new message - RuntimeException ex = new RuntimeException(sb.toString(), t); - return ex; + return new RuntimeException(sb.toString(), t); } catch (Throwable ignore) { // In case of problem, just throw the original exception as-is. return t; diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar Binary files differindex 8dd0481..c6ca3c4 100644 --- a/tools/layoutlib/create/tests/data/mock_android.jar +++ b/tools/layoutlib/create/tests/data/mock_android.jar diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java b/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java new file mode 100644 index 0000000..aaeebf6 --- /dev/null +++ b/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mock_android.util; + +import java.lang.JavaClass; + +public class EmptyArray { + + public static final Object[] OBJECT = new Object[0]; +} diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java index 3870a63..af56c4b 100644 --- a/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java +++ b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java @@ -16,11 +16,13 @@ package mock_android.widget; +import mock_android.util.EmptyArray; import mock_android.view.ViewGroup; public class LinearLayout extends ViewGroup { - public class LayoutParams extends mock_android.view.ViewGroup.LayoutParams { + Object[] mObjects = EmptyArray.OBJECT; + public class LayoutParams extends MarginLayoutParams { } diff --git a/tools/layoutlib/rename_font/README b/tools/layoutlib/rename_font/README new file mode 100644 index 0000000..600b756 --- /dev/null +++ b/tools/layoutlib/rename_font/README @@ -0,0 +1,9 @@ +This tool is used to rename the PS name encoded inside the ttf font that we ship +with the SDK. There is bug in Java that returns incorrect results for +java.awt.Font#layoutGlyphVector() if two fonts with same name but differnt +versions are loaded. As a workaround, we rename all the fonts that we ship with +the SDK by appending the font version to its name. + + +The build_font.py copies all files from input_dir to output_dir while renaming +the font files (*.ttf) in the process. diff --git a/tools/layoutlib/rename_font/Roboto-Regular.ttf b/tools/layoutlib/rename_font/Roboto-Regular.ttf Binary files differnew file mode 100644 index 0000000..7469063 --- /dev/null +++ b/tools/layoutlib/rename_font/Roboto-Regular.ttf diff --git a/tools/layoutlib/rename_font/build_font.py b/tools/layoutlib/rename_font/build_font.py new file mode 100755 index 0000000..c747d92 --- /dev/null +++ b/tools/layoutlib/rename_font/build_font.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python + +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Rename the PS name of all fonts in the input directories and copy them to the +output directory. + +Usage: build_font.py /path/to/input_fonts1/ /path/to/input_fonts2/ /path/to/output_fonts/ + +""" + +import glob +from multiprocessing import Pool +import os +import re +import shutil +import sys +import xml.etree.ElementTree as etree + +# Prevent .pyc files from being created. +sys.dont_write_bytecode = True + +# fontTools is available at platform/external/fonttools +from fontTools import ttx + +# global variable +dest_dir = '/tmp' + + +class FontInfo(object): + family = None + style = None + version = None + ends_in_regular = False + fullname = None + + +class InvalidFontException(Exception): + pass + + +# These constants represent the value of nameID parameter in the namerecord for +# different information. +# see http://scripts.sil.org/cms/scripts/page.php?item_id=IWS-Chapter08#3054f18b +NAMEID_FAMILY = 1 +NAMEID_STYLE = 2 +NAMEID_FULLNAME = 4 +NAMEID_VERSION = 5 + + +def main(argv): + if len(argv) < 2: + sys.exit('Usage: build_font.py /path/to/input_fonts/ /path/to/out/dir/') + for directory in argv: + if not os.path.isdir(directory): + sys.exit(directory + ' is not a valid directory') + global dest_dir + dest_dir = argv[-1] + src_dirs = argv[:-1] + cwd = os.getcwd() + os.chdir(dest_dir) + files = glob.glob('*') + for filename in files: + os.remove(filename) + os.chdir(cwd) + input_fonts = list() + for src_dir in src_dirs: + for dirname, dirnames, filenames in os.walk(src_dir): + for filename in filenames: + input_path = os.path.join(dirname, filename) + extension = os.path.splitext(filename)[1].lower() + if extension == '.ttf': + input_fonts.append(input_path) + elif extension == '.xml': + shutil.copy(input_path, dest_dir) + if '.git' in dirnames: + # don't go into any .git directories. + dirnames.remove('.git') + # Create as many threads as the number of CPUs + pool = Pool(processes=None) + pool.map(convert_font, input_fonts) + + +def convert_font(input_path): + filename = os.path.basename(input_path) + print 'Converting font: ' + filename + # the path to the output file. The file name is the fontfilename.ttx + ttx_path = os.path.join(dest_dir, filename) + ttx_path = ttx_path[:-1] + 'x' + try: + # run ttx to generate an xml file in the output folder which represents all + # its info + ttx_args = ['-q', '-d', dest_dir, input_path] + ttx.main(ttx_args) + # now parse the xml file to change its PS name. + tree = etree.parse(ttx_path) + root = tree.getroot() + for name in root.iter('name'): + update_tag(name, get_font_info(name)) + tree.write(ttx_path, xml_declaration=True, encoding='utf-8') + # generate the udpated font now. + ttx_args = ['-q', '-d', dest_dir, ttx_path] + ttx.main(ttx_args) + except InvalidFontException: + # In case of invalid fonts, we exit. + print filename + ' is not a valid font' + raise + except Exception as e: + print 'Error converting font: ' + filename + print e + # Some fonts are too big to be handled by the ttx library. + # Just copy paste them. + shutil.copy(input_path, dest_dir) + try: + # delete the temp ttx file is it exists. + os.remove(ttx_path) + except OSError: + pass + + +def get_font_info(tag): + """ Returns a list of FontInfo representing the various sets of namerecords + found in the name table of the font. """ + fonts = [] + font = None + last_name_id = sys.maxint + for namerecord in tag.iter('namerecord'): + if 'nameID' in namerecord.attrib: + name_id = int(namerecord.attrib['nameID']) + # A new font should be created for each platform, encoding and language + # id. But, since the nameIDs are sorted, we use the easy approach of + # creating a new one when the nameIDs reset. + if name_id <= last_name_id and font is not None: + fonts.append(font) + font = None + last_name_id = name_id + if font is None: + font = FontInfo() + if name_id == NAMEID_FAMILY: + font.family = namerecord.text.strip() + if name_id == NAMEID_STYLE: + font.style = namerecord.text.strip() + if name_id == NAMEID_FULLNAME: + font.ends_in_regular = ends_in_regular(namerecord.text) + font.fullname = namerecord.text.strip() + if name_id == NAMEID_VERSION: + font.version = get_version(namerecord.text) + if font is not None: + fonts.append(font) + return fonts + + +def update_tag(tag, fonts): + last_name_id = sys.maxint + fonts_iterator = fonts.__iter__() + font = None + for namerecord in tag.iter('namerecord'): + if 'nameID' in namerecord.attrib: + name_id = int(namerecord.attrib['nameID']) + if name_id <= last_name_id: + font = fonts_iterator.next() + font = update_font_name(font) + last_name_id = name_id + if name_id == NAMEID_FAMILY: + namerecord.text = font.family + if name_id == NAMEID_FULLNAME: + namerecord.text = font.fullname + + +def update_font_name(font): + """ Compute the new font family name and font fullname. If the font has a + valid version, it's sanitized and appended to the font family name. The + font fullname is then created by joining the new family name and the + style. If the style is 'Regular', it is appended only if the original font + had it. """ + if font.family is None or font.style is None: + raise InvalidFontException('Font doesn\'t have proper family name or style') + if font.version is not None: + new_family = font.family + font.version + else: + new_family = font.family + if font.style is 'Regular' and not font.ends_in_regular: + font.fullname = new_family + else: + font.fullname = new_family + ' ' + font.style + font.family = new_family + return font + + +def ends_in_regular(string): + """ According to the specification, the font fullname should not end in + 'Regular' for plain fonts. However, some fonts don't obey this rule. We + keep the style info, to minimize the diff. """ + string = string.strip().split()[-1] + return string is 'Regular' + + +def get_version(string): + # The string must begin with 'Version n.nn ' + # to extract n.nn, we return the second entry in the split strings. + string = string.strip() + if not string.startswith('Version '): + raise InvalidFontException('mal-formed font version') + return sanitize(string.split()[1]) + + +def sanitize(string): + return re.sub(r'[^\w-]+', '', string) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/tools/layoutlib/rename_font/build_font_single.py b/tools/layoutlib/rename_font/build_font_single.py new file mode 100755 index 0000000..5f7dad9 --- /dev/null +++ b/tools/layoutlib/rename_font/build_font_single.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python + +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +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. + +Usage: build_font_single.py /path/to/input_font.ttf /path/to/output_font.ttf + +""" + +import glob +import os +import re +import shutil +import sys +import xml.etree.ElementTree as etree + +# Prevent .pyc files from being created. +sys.dont_write_bytecode = True + +# fontTools is available at platform/external/fonttools +from fontTools import ttx + + +class FontInfo(object): + family = None + style = None + version = None + ends_in_regular = False + fullname = None + + +class InvalidFontException(Exception): + pass + + +# A constant to copy the font without modifying. This is useful when running +# locally and speed up the time to build the SDK. +COPY_ONLY = False + +# These constants represent the value of nameID parameter in the namerecord for +# different information. +# see http://scripts.sil.org/cms/scripts/page.php?item_id=IWS-Chapter08#3054f18b +NAMEID_FAMILY = 1 +NAMEID_STYLE = 2 +NAMEID_FULLNAME = 4 +NAMEID_VERSION = 5 + +# A list of extensions to process. +EXTENSIONS = ['.ttf', '.otf', '.xml'] + +def main(argv): + if len(argv) < 2: + print 'Incorrect usage: ' + str(argv) + sys.exit('Usage: build_font_single.py /path/to/input/font.ttf /path/to/out/font.ttf') + dest_path = argv[-1] + input_path = argv[0] + extension = os.path.splitext(input_path)[1].lower() + if extension in EXTENSIONS: + if not COPY_ONLY and extension == '.ttf': + convert_font(input_path, dest_path) + return + shutil.copy(input_path, dest_path) + + +def convert_font(input_path, dest_path): + filename = os.path.basename(input_path) + print 'Converting font: ' + filename + # the path to the output file. The file name is the fontfilename.ttx + ttx_path = dest_path[:-1] + 'x' + try: + # run ttx to generate an xml file in the output folder which represents all + # its info + ttx_args = ['-q', '-o', ttx_path, input_path] + ttx.main(ttx_args) + # now parse the xml file to change its PS name. + tree = etree.parse(ttx_path) + root = tree.getroot() + for name in root.iter('name'): + update_tag(name, get_font_info(name)) + tree.write(ttx_path, xml_declaration=True, encoding='utf-8') + # generate the udpated font now. + ttx_args = ['-q', '-o', dest_path, ttx_path] + ttx.main(ttx_args) + except InvalidFontException: + # In case of invalid fonts, we exit. + print filename + ' is not a valid font' + raise + except Exception as e: + print 'Error converting font: ' + filename + print e + # Some fonts are too big to be handled by the ttx library. + # Just copy paste them. + shutil.copy(input_path, dest_path) + try: + # delete the temp ttx file is it exists. + os.remove(ttx_path) + except OSError: + pass + + +def get_font_info(tag): + """ Returns a list of FontInfo representing the various sets of namerecords + found in the name table of the font. """ + fonts = [] + font = None + last_name_id = sys.maxint + for namerecord in tag.iter('namerecord'): + if 'nameID' in namerecord.attrib: + name_id = int(namerecord.attrib['nameID']) + # A new font should be created for each platform, encoding and language + # id. But, since the nameIDs are sorted, we use the easy approach of + # creating a new one when the nameIDs reset. + if name_id <= last_name_id and font is not None: + fonts.append(font) + font = None + last_name_id = name_id + if font is None: + font = FontInfo() + if name_id == NAMEID_FAMILY: + font.family = namerecord.text.strip() + if name_id == NAMEID_STYLE: + font.style = namerecord.text.strip() + if name_id == NAMEID_FULLNAME: + font.ends_in_regular = ends_in_regular(namerecord.text) + font.fullname = namerecord.text.strip() + if name_id == NAMEID_VERSION: + font.version = get_version(namerecord.text) + if font is not None: + fonts.append(font) + return fonts + + +def update_tag(tag, fonts): + last_name_id = sys.maxint + fonts_iterator = fonts.__iter__() + font = None + for namerecord in tag.iter('namerecord'): + if 'nameID' in namerecord.attrib: + name_id = int(namerecord.attrib['nameID']) + if name_id <= last_name_id: + font = fonts_iterator.next() + font = update_font_name(font) + last_name_id = name_id + if name_id == NAMEID_FAMILY: + namerecord.text = font.family + if name_id == NAMEID_FULLNAME: + namerecord.text = font.fullname + + +def update_font_name(font): + """ Compute the new font family name and font fullname. If the font has a + valid version, it's sanitized and appended to the font family name. The + font fullname is then created by joining the new family name and the + style. If the style is 'Regular', it is appended only if the original font + had it. """ + if font.family is None or font.style is None: + raise InvalidFontException('Font doesn\'t have proper family name or style') + if font.version is not None: + new_family = font.family + font.version + else: + new_family = font.family + if font.style is 'Regular' and not font.ends_in_regular: + font.fullname = new_family + else: + font.fullname = new_family + ' ' + font.style + font.family = new_family + return font + + +def ends_in_regular(string): + """ According to the specification, the font fullname should not end in + 'Regular' for plain fonts. However, some fonts don't obey this rule. We + keep the style info, to minimize the diff. """ + string = string.strip().split()[-1] + return string is 'Regular' + + +def get_version(string): + # The string must begin with 'Version n.nn ' + # to extract n.nn, we return the second entry in the split strings. + string = string.strip() + if not string.startswith('Version '): + raise InvalidFontException('mal-formed font version') + return sanitize(string.split()[1]) + + +def sanitize(string): + return re.sub(r'[^\w-]+', '', string) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/tools/layoutlib/rename_font/test.py b/tools/layoutlib/rename_font/test.py new file mode 100755 index 0000000..2ffddf4 --- /dev/null +++ b/tools/layoutlib/rename_font/test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +"""Tests build_font.py by renaming a font. + +The test copies Roboto-Regular.ttf to a tmp directory and ask build_font.py to rename it and put in another dir. +We then use ttx to dump the new font to its xml and check if rename was successful + +To test locally, use: +PYTHONPATH="$PYTHONPATH:/path/to/android/checkout/external/fonttools/Lib" ./test.py +""" + +import unittest +import build_font + +from fontTools import ttx +import os +import xml.etree.ElementTree as etree +import shutil +import tempfile + +class MyTest(unittest.TestCase): + def test(self): + font_name = "Roboto-Regular.ttf" + srcdir = tempfile.mkdtemp() + print "srcdir: " + srcdir + shutil.copy(font_name, srcdir) + destdir = tempfile.mkdtemp() + print "destdir: " + destdir + self.assertTrue(build_font.main([srcdir, destdir]) is None) + out_path = os.path.join(destdir, font_name) + ttx.main([out_path]) + ttx_path = out_path[:-1] + "x" + tree = etree.parse(ttx_path) + root = tree.getroot() + name_tag = root.find('name') + fonts = build_font.get_font_info(name_tag) + shutil.rmtree(srcdir) + shutil.rmtree(destdir) + self.assertEqual(fonts[0].family, "Roboto1200310") + self.assertEqual(fonts[0].fullname, "Roboto1200310 Regular") + + + +if __name__ == '__main__': + unittest.main() |